为什么会发送OPTIONS请求,我能禁用它吗?

593

我正在构建一个Web API。我发现每当我使用Chrome进行POST和GET请求到我的API时,总会在真正的请求之前发送一个OPTIONS请求,这相当令人烦恼。目前,我让服务器忽略任何OPTIONS请求。现在我的问题是,有什么好方法可以发送OPTIONS请求以增加服务器的负载?是否有任何方法完全停止浏览器发送OPTIONS请求?


4
最快的解决方法肯定是在CORS响应中使用Access-Control-Max-Age,这样它只会被请求一次。 - Ian
15个回答

512

编辑 2018-09-13: 在本回答的末尾添加了一些关于这个预检请求及如何避免它的详细信息。

OPTIONS请求在跨域资源共享(CORS)中被称为预检请求

在特定情况下,当你在不同的源之间进行请求时,它们是必需的。

这个预检请求是某些浏览器作为安全措施而发出的,以确保正在进行的请求是由服务器信任的。 这意味着服务器理解请求中发送的方法、来源和标头是安全的并可以采取行动。

每当你尝试进行跨源请求时,你的服务器不应该忽略而是处理这些请求。

可以在这里找到一个很好的资源 http://enable-cors.org/

处理这些请求的一种方法是确保对于任何带有OPTIONS方法的路径,服务器都会发送一个带有以下标头的响应:

Access-Control-Allow-Origin: *

这将告诉浏览器,服务器愿意来自任何来源的请求作出回应。

如需了解如何为您的服务器添加CORS支持的更多信息,请参阅以下流程图。

http://www.html5rocks.com/static/images/cors_server_flowchart.png

CORS Flowchart


编辑于2018-09-13

MDN文档所述,CORS OPTIONS请求仅在某些情况下触发:

有些请求不会触发CORS预检测,这在本文中被称为“简单请求”,尽管Fetch规范(定义了CORS)并没有使用这个术语。一个不会触发CORS预检测的请求 - 所谓的“简单请求” - 必须满足以下所有条件: 只允许的方法是:GET、HEAD、POST。 除了被用户代理自动设置的标头 (例如Connection,User-Agent或任何其他Fetch规范中定义为“禁止标头名称”的名称),唯一允许手动设置的标头是Fetch规范定义为“CORS安全请求标头”的那些标头,它们是: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对象。

15
将此 Chrome 标志设置为所有普通用户并不现实。 - user1663023
70
当进行跨域请求时,称预检请求是必需的是不正确的。只有在特定情况下才需要预检请求,例如当您设置自定义标头或进行除GET、HEAD和POST之外的请求时。 - Robin Clowers
5
有趣的是,在使用jQuery进行CORS请求时,JavaScript库会特意避免设置自定义标头,并向开发人员发出警告:对于跨域请求,由于预检条件类似于一道拼图,我们从不设置它以确保请求成功。 - Below the Radar
5
如果我使用curl命令调用API,它可以正常工作,但是从Chrome运行时出现错误,这是怎么回事? - SuperUberDuper
9
@SuperUberDuper 因为CORS和预检请求是浏览器相关的问题。您可以通过向请求添加“Origin”标头来模拟CORS,以模拟请求来自特定主机(例如yourwebsite.com)。您还可以通过将请求的HTTP方法设置为“OPTIONS”和“Access-Control-*”标头来模拟预检请求。 - Leo Correa
显示剩余12条评论

347

我遇到了这个问题,以下是我的结论和解决方案。

根据CORS策略(强烈推荐您了解一下),如果浏览器认为有必要发送OPTIONS请求,那么您不能强制停止它发送该请求。

有两种方法可以解决这个问题:

  1. 确保您的请求属于“简单请求”
  2. 为OPTIONS请求设置Access-Control-Max-Age

简单请求

简单跨站点请求是指同时满足以下所有条件的请求:

只允许以下方法:

  • GET
  • HEAD
  • POST

除了用户代理自动设置的标题之外(例如Connection、User-Agent等),只允许手动设置以下标题:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type

Content-Type标头的唯一允许值为:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

简单请求不会引起预检OPTIONS请求。

为OPTIONS检查设置缓存

您可以为OPTIONS请求设置Access-Control-Max-Age,以便在过期之前不会再次检查权限。

Access-Control-Max-Age给出了预检请求响应的缓存时间(以秒为单位),在此期间无需发送另一个预检请求。

有限制要注意

  • 对于Chrome浏览器,根据chrome源代码Access-Control-Max-Age的最大秒数为600,即10分钟。
  • Access-Control-Max-Age每次只适用于一个资源,例如具有相同URL路径但不同查询的GET请求将被视为不同的资源。因此,对第二个资源的请求仍将触发预检请求。

9
是的,这应该是被接受的答案并且与问题最相关。 - Rajesh Mbm
9
谢谢您提到 Access-Control-Max-Age。这是关键之处,它有助于避免过多的预检请求。 - Idris Mokhtarzada
1
我正在使用axios调用get请求。在axios请求中,我应该在哪里设置Access-Control-Max-Age? - Mohit Shah
3
@VitalyZdanevich 不要因为使用 application/json 会使你的请求变得不 "simple"(从而触发CORS)而回避它。浏览器在执行它的工作。请设置服务器返回一个类似于 Access-Control-Max-Age: 86400 的头部,浏览器将不会在24小时内重新发送OPTIONS请求。 - colm.anseo
1
非常好的答案,谢谢,终于我对CORS有了一些理解。 - Billizzard
显示剩余5条评论

153
请参考关于预检 OPTIONS 请求的实际需求的答案:什么是引入预检 CORS 请求的动机? 为了禁用 OPTIONS 请求,ajax 请求必须满足以下条件:
  1. 请求没有设置 Content-Type HTTP 头,如 'application/xml'、'application/json' 或任何其他自定义的 HTTP 头等
  2. 请求方法必须是 GET、HEAD 或 POST 之一。如果是 POST,内容类型应为 application/x-www-form-urlencodedmultipart/form-datatext/plain 之一

Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS


15
支持“自定义HTTP头”!在我的情况下,它们引发了预检请求。我重构了请求,将头部发送的内容作为请求体发送,这样OPTIONS请求就不再被发送了。 - Andre
33
application/xmlapplication/json不是“自定义HTTP头”。头本身应该是Content-Type,将该头称为“自定义”会引起误解。 - Leo Correa
1
移除自定义的HTTP头部后,这个问题迎刃而解! - Tim D
@LeoCorrea 当我去除了这些标题后,对于我来说OPTIONS请求就不会被发出。因此,“custom”在某种意义上是相当直观的,它意味着“用户提供”的,而不是“浏览器生成的”。 - Tails
从字面上看,我一直在使用Axios + Elasticsearch调试CORS。删除所有标头并简单地发布到URL即可解决问题。 - FreeSoftwareServers
我有一个使用C#服务器处理HTTP请求的项目。当我尝试使用ajax进行POST时遇到了问题。我只是删除了'application/json'...voila,它就起作用了。 - Aimkiller

67
当您打开调试控制台并启用“禁用缓存”选项时,预检请求将始终被发送(即在每个请求之前)。如果您不禁用缓存,则只会发送一次预检请求(每个服务器)。

4
哦,我在想什么。调试了几个小时后,这是我的解决方案。由于调试控制台,缓存被禁用了。 - mauris
2
即使调试控制台关闭,预检请求也会被发送。 - Luca Perico
2
Luca:没错,但关键是当开发工具关闭时,“禁用缓存”没有任何效果。如果未禁用缓存,则会发送预检请求一次(每个服务器当然只有一次),如果禁用缓存,则在每个请求之前都会发送预检请求。 - Nir
1
那真的很有帮助。 - Anuraag Patil

37

是的,可以避免Options请求。当您向另一个域发送(发布)任何数据时,Options请求是预检请求。这是浏览器安全问题。但是我们可以使用另一种技术:iframe传输层。我强烈建议您忘记任何CORS配置并使用现成的解决方案,它可以在任何地方使用。

请看这里: https://github.com/jpillora/xdomain

以及工作示例: http://jpillora.com/xdomain/


这实际上是一种即插即用的代理吗? - matanster
20
“Options请求是在向另一个域发送(提交)任何数据时进行的预检请求。” — 这并不正确。您可以使用XHR发送任何与普通HTML表单相同的POST请求,而不触发预检请求。只有当你开始做一些表单无法做到的事情(例如自定义内容类型或额外的请求头)时,才会发送预检请求。 - Quentin
这里的解决方案似乎依赖于一个iframe shim,在某些情况下可以工作,但有一些重大限制。如果您想知道响应的HTTP状态代码或另一个HTTP响应头的值,会发生什么? - Stephen Crosby
11
iFrames并非为此而设计。 - Romko
1
这是一个软件实现,并回答了最终的问题:“有没有完全停止浏览器发送OPTIONS请求的方法?”长话短说,在Mozilla或Chromium中无法禁用它,它已经在代码中实现,唯一“有效”的选项只是规避该行为。 - scavenger
仅仅向另一个域POST请求并不是预检请求的原因,在我看来,强烈推荐使用iframe解决方案而不解释原因是适得其反的。 - So You're A Waffle Man

21

对于一个理解API存在原因但需要访问不支持不带认证的OPTIONS调用的API的开发人员,我需要一个临时的解决方案,以便在API所有者添加适当的SPA CORS支持或我启动代理API之前可以在本地开发。

我发现您可以在Mac上的Safari和Chrome中禁用CORS。

在Chrome中禁用同源策略

Chrome:退出Chrome,打开终端并粘贴此命令:open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir

Safari:在Safari中禁用同源策略

 

如果要在Safari(我的版本是9.1.1)中禁用同源策略,则只需启用开发人员菜单,然后从“开发”菜单中选择“禁用跨域限制”即可。


7
你应该更加强调“这绝对不是永久解决方案!”的那部分内容。同源策略是一项非常重要的浏览器安全措施,通常浏览互联网时不应禁用它。 - jannis
我希望网络能够以这种方式工作,这样我们就可以从服务器请求所需的数据而不需要额外的麻烦。 - jemiloii
'--disable-web-security' 已经生效,谢谢。 - pf_miles
请注意,您现在需要使用 --user-data-dir="/some folder"。 - zacaj

16
如先前的文章所述,“OPTIONS”请求是有其存在意义的。如果您的服务器响应时间过长(例如海外连接等),您可以让浏览器缓存预检请求。
使用“Access-Control-Max-Age”头使您的服务器作出回复,对于发送到同一端点的请求,预检请求将被缓存,不再发生。

1
谢谢您!在我阅读的所有CORS文档中,OPTIONS请求将使用此标头缓存的事实非常不透明。 - joshperry
1
缓存仅对完全相同的URL生效。我想要一个基于域名的预检缓存,这样可以真正减少往返次数。(CORS很傻!) - wonder

10

我已经解决了这个问题,方法如下。

if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && ENV == 'devel') {
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Headers: X-Requested-With');
    header("HTTP/1.1 200 OK");
    die();
}

这只是为了开发使用。这样我可以等待9毫秒和500毫秒而不是8秒和500毫秒。我可以这样做是因为生产环境中JS应用程序将在与生产环境相同的机器上运行,因此不会有OPTIONS,但开发环境是我的本地环境。


1

你不能直接解决CORS问题,但可以使用JSONP避免它。


5
只有当你进行一些不简单的操作时,才会收到OPTIONS请求。只能使用JSONP进行简单请求(GET,无自定义标头,无身份验证数据),因此无法用JSONP来代替。 - Quentin
是的,我知道,但我不知道确切的项目要求。我知道这不是简单的避免CORS,而是取决于项目。在最坏的情况下,为了避免CORS,您需要使用get参数传递数据。因此,根据项目要求(如您所说,使用简单请求),JSONP可以替代cors。 - Jose Mato
我不理解所谓的预检设计。如果客户端决定向服务器发送数据,会导致什么不安全因素呢?我认为在电线上增加负载没有任何意义。 - user1663023
@ElgsQianChen 这篇文章可能会回答你的问题 https://dev59.com/iGUp5IYBdhLWcg3wRWFD - Leo Correa

0

OPTIONS请求是Web浏览器的一个功能,因此很难禁用它。但我找到了一种使用代理进行重定向的方法。这在服务端点无法处理CORS/OPTIONS请求的情况下非常有用,可能仍在开发中或配置不正确。

步骤:

  1. 使用所选工具(nginx、YARP等)为此类请求设置反向代理
  2. 创建一个端点来处理OPTIONS请求。创建一个普通的空端点可能更容易,并确保它能够很好地处理CORS。
  3. 为代理配置两组规则。一组是将所有OPTIONS请求路由到上述虚拟端点。另一组是将所有其他请求路由到实际端点。
  4. 更新网站以使用代理。

基本上,这种方法是欺骗浏览器认为OPTIONS请求有效。考虑到CORS并不是为了增强安全性,而是为了放宽同源策略,我希望这个技巧能够持续一段时间。 :)


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