Angular发送的HTTP请求为什么是OPTIONS而不是POST?

34
我试图从我的 angular.js 应用程序向服务器发送一些 HTTP 请求,但我需要解决一些 CORS 错误。
使用以下代码进行 HTTP 请求:
functions.test = function(foo, bar) {
    return $http({
        method: 'POST',
        url: api_endpoint + 'test',
        headers: {
            'foo': 'value',
            'content-type': 'application/json'
        },
        data: {
            bar:'value'
        }
    });
};

第一次尝试遇到了一些CORS错误。因此,在我的PHP脚本中添加了以下几行:

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT');
header('Access-Control-Allow-Headers: X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding, X-Auth-Token, content-type');

第一个错误已经被消除。

现在Chrome的开发者工具显示以下错误:

angular.js:12011 OPTIONS http://localhost:8000/test (匿名函数)

423ef03a:1 XMLHttpRequest无法加载http://localhost:8000/test。 预检请求的响应无效,HTTP状态码为400

网络请求看起来符合预期(HTTP状态400也是预期的):

network request

我无法想象如何解决这个问题(以及如何理解),为什么请求会作为OPTIONS发送到本地主机,而作为POST发送到远程服务器。是否有解决此奇怪问题的方法?


1
请尝试在头部添加“Accept”:“application/json”。 - Nandan Bhat
4
OPTIONS是一个预检请求,在您的POST调用中设置了自定义标头,浏览器发送该请求;这是正常的。 我认为问题出在这些标头的内容上。 - Valentin Roudge
2
问题在于对预检(OPTIONS)请求的响应不成功,即HTTP 400。这就是为什么真正的(以下)请求失败的原因。您必须确保对预检请求的响应有效。 - David Ferenczy Rogožan
HTTP状态码“400”预期是什么意思? - David Ferenczy Rogožan
POST 请求会附加一个名为 "foo" 的头部,而该名称未列在 Access-Control-Allow-Headers 中。要么将 "foo" 添加到该列表中,要么不带该头部进行 POST 请求。 - georgeawg
1
你解决了吗?如果是的话,能否描述一下为什么你的Web服务器或应用程序一直返回HTTP 400错误,并且你是如何解决它的。你可以创建另一个答案来回答这个问题。 - David Ferenczy Rogožan
6个回答

76

简要回答

OPTIONS请求是预检请求,用于跨域资源共享(CORS)。浏览器使用它来检查请求是否允许从特定的域名发起

当浏览器发送一个请求到特定的URL时,首先会发送一个预检OPTIONS请求到同一URL。根据该预检请求的响应HTTP状态码进行以下操作:

  • 如果服务器返回非2XX状态响应,则浏览器不会发送实际请求(因为它现在知道请求会被拒绝)。
  • 如果服务器返回HTTP 200 OK(或其他2XX)响应,则浏览器将发送实际请求,如您的情况中的POST请求。

因此,在您的情况下,适当的标头已经存在,您只需确保预检请求的响应HTTP状态码是200 OK或其他成功的状态码(2XX)。


详细解释

简单请求

在某些情况下,浏览器不会发送预检请求,这些被称为简单请求,并在以下情况下使用:

  • 允许的方法之一: - GET - HEAD - POST
  • 除用户代理自动设置的标头(例如Connection、User-Agent等)外,唯一允许手动设置的标头是以下内容:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(但请注意下面的附加要求)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type标头的唯一允许值是:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • 没有在任何请求中使用的XMLHttpRequestUpload对象注册事件侦听器;这些可以使用XMLHttpRequest.upload属性来访问。
  • 请求中未使用ReadableStream对象。

这样的请求直接发送,服务器只需成功处理请求或在不符合CORS规则的情况下回复错误。在任何情况下,响应都将包含CORS标头Access-Control-Allow-*

预检请求

如果实际请求不符合“简单请求”的条件时(常见的有:使用自定义内容类型如application/xmlapplication/json等;请求方法为GETHEADPOST之外的其他方法;POST方法的内容类型不是application/x-www-form-urlencodedmultipart/form-datatext/plain),浏览器就会发送“预检”请求。
你需要确保对于“预检”请求的响应具备以下属性:
  • 成功的HTTP状态码,如200 OK
  • 头信息Access-Control-Allow-Origin: *(通配符*允许任何域名的请求,当然你可以在此处使用特定域名限制访问)
从另一方面来看,服务器可能会通过响应“预检”请求拒绝CORS请求,具体表现为:
  • 非成功的HTTP代码(如非2XX
  • 成功的HTTP代码(如200 OK),但没有任何CORS头信息(即Access-Control-Allow-*
详细信息请参阅Mozilla开发者网络文档或例如HTML5Rocks的CORS教程

1
感谢您清晰的描述,解决了我的问题。 - Mehdi Roostaeian

4

只需要放置

if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
header("HTTP/1.1 200 ");
exit;}

在你的服务器端应用程序的开头,你需要进行一些处理来确保它能正常运行。

4

我在编写一个使用NODE服务器作为API的Angular 2应用程序时遇到了类似的问题。

由于我是在本地机器上开发,当我尝试从Angular应用程序向API进行POST请求时,我一直遇到跨域头问题。

在节点服务器中设置以下标头适用于GET请求,但是我的PUT请求仍会将空对象发布到数据库中。

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT');
header('Access-Control-Allow-Headers: X-Requested-With, Content-Type, 
Origin, Authorization, Accept, Client-Security-Token, Accept-
Encoding, X-Auth-Token, content-type');

在阅读了Dawid Ferenczy的帖子后,我意识到预检请求将空白数据发送到我的服务器,这就是为什么我的数据库条目为空的原因,所以我添加了这行代码到NODE JS服务器中:

  if (req.method == "OPTIONS")
    {
        res.writeHead(200, {"Content-Type": "application/json"});
        res.end();
    }

现在我的服务器忽略了PREFLIGHT请求(并返回状态码200,以让浏览器知道一切正常...),这样真正的请求就可以通过,我可以将真实数据发布到我的数据库中!


好的,你没有忽略预检请求,而是正确地回复了它。预检请求的目的不是传输任何数据,而是检查当前域是否允许实际请求。 - David Ferenczy Rogožan

1

对于Spring Boot应用程序,要启用CORS请求,请在相应的控制器上使用@CrossOrigin(origins = "*", maxAge = 3600)

请参阅此文档


1
最好的方法是:

  1. have proxy.conf.json set:

    {
      "/api": {
        "target": "http://localhost:8080",
        "secure": false,
        "logLevel": "debug",
        "changeOrigin": true
      }
    } 
    
  2. And then to make sure that URL that you are using in angular to send a request is relative (/api/something) and not absolute (localhost:8080/api/something). Because in that case the proxy won't work.


0

从 Chrome v79+ 开始,OPTIONS Check(预检请求)将不再出现在网络选项卡中-源代码


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