ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

POST请求为何发送两次?解析CORS预检机制

POST请求为何发送两次?解析CORS预检机制

1. 为什么POST请求会发送两次?

这个问题在前端开发面试中经常被问到,也是实际项目中常见的现象。当你在浏览器控制台的Network面板中看到同一个POST请求出现了两次时,这通常与浏览器的安全机制有关。

1.1 CORS预检请求机制

现代浏览器在发送某些跨域请求前,会先发送一个OPTIONS方法的预检请求(preflight request)。这个机制是跨域资源共享(CORS)规范的一部分,目的是确保服务器理解并允许实际的跨域请求。

预检请求会在以下情况触发:

  • 使用了PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH方法
  • 设置了自定义请求头(非简单头部)
  • Content-Type不是application/x-www-form-urlencoded、multipart/form-data或text/plain

提示:简单请求不会触发预检,GET、HEAD、POST(使用上述Content-Type)以及只包含简单头部的请求属于简单请求。

1.2 预检请求的工作流程

当浏览器检测到需要预检的请求时,会按以下顺序执行:

  1. 首先发送OPTIONS请求,包含:

    • Access-Control-Request-Method:告知服务器实际请求将使用的方法
    • Access-Control-Request-Headers:告知服务器实际请求将携带的自定义头部
    • Origin:请求来源
  2. 服务器响应OPTIONS请求,返回:

    • Access-Control-Allow-Methods:允许的方法列表
    • Access-Control-Allow-Headers:允许的头部列表
    • Access-Control-Max-Age:预检结果缓存时间
  3. 如果预检通过,浏览器才会发送实际的POST请求

2. 如何识别和调试预检请求

2.1 浏览器开发者工具中的表现

在Chrome开发者工具的Network面板中:

  • 预检请求会显示为OPTIONS方法
  • 状态码通常是204或200
  • 请求头包含Access-Control-Request-*系列头部
  • 响应头包含Access-Control-Allow-*系列头部

2.2 常见问题排查

如果遇到预检问题,可以检查以下方面:

  1. 服务器是否正确响应OPTIONS请求
  2. Access-Control-Allow-Methods是否包含POST
  3. Access-Control-Allow-Headers是否包含所有自定义头部
  4. Access-Control-Allow-Origin是否正确设置

3. 服务器端配置示例

3.1 Node.js Express配置

const express = require('express'); const app = express(); // 处理OPTIONS预检请求 app.options('/api', (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); res.sendStatus(204); }); // 处理实际POST请求 app.post('/api', (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.json({ message: 'Success' }); }); app.listen(3000);

3.2 Nginx配置示例

location /api { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'Content-Type'; add_header 'Access-Control-Max-Age' 86400; return 204; } add_header 'Access-Control-Allow-Origin' '*'; proxy_pass http://backend; }

4. 性能优化建议

虽然预检请求是必要的安全机制,但我们可以优化其性能影响:

  1. 设置适当的Access-Control-Max-Age头部,让浏览器缓存预检结果
  2. 尽量使用简单请求避免预检
  3. 合并自定义请求头,减少需要预检的头部数量
  4. 对于频繁使用的API,考虑使用同源策略

5. 幂等性考虑

由于POST方法本身不是幂等的,而OPTIONS是安全的,这种两次请求机制不会导致数据不一致。但开发时仍需注意:

  1. 确保POST接口的幂等性设计
  2. 对于非幂等操作,考虑使用PUT方法替代
  3. 在服务端记录请求ID,防止重复处理

6. 实际项目中的经验

在最近的一个电商项目中,我们遇到了上传商品图片时触发预检请求的问题。解决方案是:

  1. 将自定义的X-Request-ID头部改为简单头部
  2. 设置Access-Control-Max-Age为86400秒(1天)
  3. 在后端统一处理OPTIONS请求

这样优化后,同一用户一天内只需要发送一次预检请求,显著提升了用户体验。

返回列表