为什么一个POST请求实际发生了两个请求

当您在浏览器发送一个 POST 请求时,可能会首先发送了一个 OPTIONS 请求,紧接着才是实际的 POST 请求。这个现象是由浏览器实现的一种机制,称为“预检请求”(preflight request),它是跨源资源共享(CORS, Cross-Origin Resource Sharing)规范的一部分。我将解释这个现象的原因,并用代码示例展示其工作原理。

为什么会发生预检请求?

当您的网页尝试执行跨源(即不同域、协议或端口)HTTP 请求时,浏览器安全策略默认会阻止这类请求。CORS 是一种机制,允许网页从另一个域名的服务器安全地获取资源。在某些情况下,浏览器为了确认安全性,会先发送一个 OPTIONS 请求到服务器,以确保真正的请求是安全可接受的。这个 OPTIONS 请求就是预检请求。

预检请求主要发生在以下情况:

  • HTTP 请求方法不是 GET、HEAD 或 POST。
  • POST 请求的 Content-Type 不是 application/x-www-form-urlencoded, multipart/form-data, 或 text/plain
  • 请求包含了除了 CORS 安全列表以外的 HTTP 头。

如何处理预检请求?

服务器需要正确响应 OPTIONS 请求,提供允许的源、方法和头信息。如果预检请求失败,主请求(如 POST 请求)不会被发送。

代码示例

假设您有一个前端应用,尝试向另一个域的服务器发送 POST 请求,并有一个简单的后端服务器来处理这些请求。

前端(JavaScript)
// 使用 fetch 发送 POST 请求
fetch('https://example.com/api/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
后端(Node.js 示例)
const express = require('express');
const cors = require('cors');
const app = express();

app.use(express.json());

// CORS 中间件配置
const corsOptions = {
    origin: 'https://yourdomain.com', // 允许的源
    methods: 'POST', // 允许的方法
    allowedHeaders: ['Content-Type'] // 允许的头信息
};

app.options('/api/data', cors(corsOptions)); // 处理预检请求

app.post('/api/data', cors(corsOptions), (req, res) => {
    // 处理 POST 请求
    console.log(req.body);
    res.json({ message: 'Data received' });
});

const PORT = 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

在这个例子中,后端使用了 Express.js 和 CORS 中间件来处理跨域请求。当前端发起 POST 请求时,浏览器首先发送 OPTIONS 请求到 /api/data 路径。服务器响应这个预检请求,表明它接受来自指定源的 POST 请求和特定的头信息。一旦预检请求成功,浏览器随后发送实际的 POST 请求。

如何避免预检请求

要避免浏览器在发送跨域 POST 请求时先发出 OPTIONS 预检请求,您可以采取以下策略:

  1. 调整请求类型和头信息

    • 使用简单请求。简单请求不会触发预检。简单请求的条件包括:
      • 使用 GET、HEAD 或 POST 方法。
      • POST 请求的 Content-Type 必须是 application/x-www-form-urlencoded, multipart/form-data, 或 text/plain
      • 请求头信息不超出 CORS 安全列表(如 Accept, Accept-Language, Content-Language, Content-Type 等)。
  2. 相同源请求

    • 如果可能,将 API 部署在与前端应用相同的域上。这样,请求就不是跨域的,因此不会触发 CORS 预检。
  3. 服务器端配置

    • 如果您控制服务器端,并且确信安全风险可接受,可以在服务器端配置 CORS,允许来自所有源的请求。但这可能带来安全隐患,需谨慎操作。
  4. 使用代理服务器

    • 在前端服务器上设置代理,将请求转发到目标服务器。这样,前端到代理服务器的请求是同源的,代理服务器再将请求转发到实际的后端服务器。
  5. 减少预检请求的影响

    • 即使不能避免预检请求,也可以通过在服务器端设置适当的 Access-Control-Max-Age 头信息来减少影响。这个头信息指示浏览器可以缓存预检请求的结果多长时间,在这个时间内相同的跨域请求不会触发额外的预检请求。

示例

  1. 调整 POST 请求(简单请求示例)

    fetch('https://example.com/api/data', {
        method: 'POST',
        headers: {
            'Content-Type': 'text/plain'
        },
        body: 'Simple text data'
    })
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Error:', error));
    

    在这个示例中,POST 请求的 Content-Type 被设置为 text/plain,这是一个简单请求的条件之一。

  2. 服务器端 CORS 配置(慎重操作)

    const corsOptions = {
        origin: '*', // 警告:允许所有源,可能有安全风险
        methods: ['GET', 'POST', 'HEAD'] // 允许的方法
    };
    
    app.use(cors(corsOptions));
    

    这个示例中,服务器接受任何源的请求,这样做可能有安全风险,因此需谨慎考虑。

结论

虽然可以通过这些方法避免预检请求,但在调整策略时需要考虑安全性和实用性。CORS 预检是为了增强网络安全而设计的,因此在不同场景下选择合适的方法非常重要。预检请求是一种重要的机制,确保了跨域请求的安全性。通过正确配置服务器来处理这些预检请求,您可以确保您的 web 应用能够安全地与其他域的服务器交互。虽然这可能增加了一些开发的复杂性,但它是保护网页不受恶意跨站请求的重要部分。

你可能感兴趣的:(南城前端专栏,前端JS那些事,前端,javascript,http)