为什么浏览器在我的前端代码只发起了POST请求时还会发送一个OPTIONS请求?

17

我的前端代码:

<form action="" onSubmit={this.search}>
  <input type="search" ref={(input) => { this.searchInput = input; }}/>
  <button type="submit">搜索</button>
</form>

// search method:
const baseUrl = 'http://localhost:8000/'; // where the Express server runs
search(e) {
  e.preventDefault();
  let keyword = this.searchInput.value;
  if (keyword !== this.state.lastKeyword) {
    this.setState({
      lastKeyword: keyword
    });
    fetch(`${baseUrl}search`, {
      method: 'POST',
      // mode: 'no-cors',
      headers: new Headers({
      'Content-Type': 'application/json'
      }),
      // credentials: 'include',
      body: JSON.stringify({keyword})
    })
  }
}

以下是我的 Express.js 服务器端代码:

app.all('*', (req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  // res.header('Access-Control-Allow-Credentials', true);
  res.header('Content-Type', 'application/json; charset=utf-8')
  next();
});

当我提交表单时,我会收到两个请求。其中一个是OPTIONS请求,另一个是POST请求,对它的响应是正确的:

two requests to /search with response codes 200 OPTIONS request to localhost:8000/search POST request to localhost:8000/search

正如您所看到的,Express服务器运行在8000端口上,React开发服务器运行在3000端口上。localhost:3000正在请求localhost:8000/search,而localhost:8000是通过使用POST方法请求另一个来源。但是,只有第二个请求有效。我不知道为什么会这样。当然,如果我使用查询字符串进行GET请求,一切正常。但我也想知道如何使用请求正文进行POST请求。

2个回答

32

您的浏览器会在您的代码尝试POST请求之前自动发送OPTIONS请求。这被称为CORS预检请求。https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests有详细信息。

在这种特定情况下,问题的要点是代码添加的Content-Type: application/json请求头会触发浏览器执行该预检OPTIONS请求。

因此,该特定预检请求的目的是让浏览器询问服务器:“您是否允许跨源POST请求,其Content-Type标头的值不是application/x-www-form-urlencodedmultipart/form-datatext/plain?”

为了使浏览器认为预检成功,服务器必须返回一个带有Access-Control-Allow-Headers头的响应,其中包括Content-Type在其值中。

我看到你在当前服务器代码的http://localhost:8000/上设置了res.header('Access-Control-Allow-Headers', 'Content-Type'),如果您要手动编写代码,这是正确的值。但我认为它不起作用的原因是您还没有处理OPTIONS请求的代码。

为了解决这个问题,您可以尝试安装npm cors软件包:

npm install cors

然后像这样做:

var express = require('express')
  , cors = require('cors')
  , app = express();
const corsOptions = {
  origin: true,
  credentials: true
}
app.options('*', cors(corsOptions)); // preflight OPTIONS; put before other routes
app.listen(80, function(){
  console.log('CORS-enabled web server listening on port 80');
});

这将处理OPTIONS请求,同时发送正确的标头和值。


{btsdaf} - Li Enze
1
{btsdaf} - sideshowbarker
在我的情况下,Access-Control-Allow-OriginAccess-Control-Allow-HeadersAccess-Control-Allow-Method是不够的,我必须添加Access-Control-Max-Age: 86400才能使其工作。我猜测浏览器需要知道“访问权限”有效期多长时间的信息。 - Sasa Blagojevic

-2

我最近遇到了类似的问题,使用FormData发送请求有效载荷,您不需要发送自定义标头。 FormData对象会为您处理身份验证凭据,但它使用SSL并且只能与https一起使用,而localhost不提供https协议,因为本地主机使用http协议。 请查看form-data npm documentation以获取有关如何在Express应用程序中安装和使用它的详细信息,但是FormData已经可以在主要浏览器中使用。 这里是一个简单的代码示例 npm install --save form-data

`

var FormData = require('form-data');
var http = require('http'); 
var form = new FormData();
 
http.request('http://nodejs.org/images/logo.png', function(response) {
  form.append('my_field', 'my value');
  form.append('my_buffer', new Buffer(10));
  form.append('my_logo', response);
});

`


1
“你不需要发送自定义标头” — 是的,你需要。 - Quentin
"FormData对象不会为您处理身份验证凭据" - 不,它不会。 - Quentin
"只能使用 HTTPS 进行工作,而本地主机使用 HTTP 协议,因此无法提供 HTTPS。但这并不妨碍您安装支持 HTTPS 并在 localhost 上侦听的服务器... 另外,FormData 在 HTTP 中也可以正常工作。" - Quentin
1
请查看 form-data npm 文档以获取有关如何在您的 express 应用程序中安装和使用它的详细信息。他们是从浏览器发出请求,因此这并没有帮助。 - Quentin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接