关于http:为什么发送OPTIONS请求,我可以禁用它吗?

Why is an OPTIONS request sent and can I disable it?

我正在构建一个Web API。 我发现每当我使用Chrome进行POST,GET到我的API时,总会在真实请求之前发送OPTIONS请求,这非常烦人。 目前我让服务器忽略任何OPTIONS请求。 现在我的问题是发送OPTIONS请求以加倍服务器负载是什么好事? 有没有办法完全阻止浏览器发送OPTIONS请求?


编辑2018-09-13:增加了关于此飞行前请求的一些准确性以及如何在此响应结束时避免它。

OPTIONS请求是我们在Cross-origin resource sharing (CORS)中称为pre-flight的请求。

当您在特定情况下跨不同来源提出请求时,它们是必需的。

某些浏览器将此飞行前请求作为安全措施,以确保服务器信任所完成的请求。
这意味着服务器了解在请求上发送的方法,来源和标头是安全的。

当您尝试执行跨源请求时,您的服务器不应忽略但处理这些请求。

这里有一个很好的资源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

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

Some requests don’t trigger a CORS preflight. Those are called"simple requests" in this article, though the Fetch spec (which defines CORS) doesn’t use that term. A request that doesn’t trigger a CORS preflight—a so-called"simple request"—is one that meets all the following conditions:

The only allowed methods are:

  • GET
  • HEAD
  • POST

Apart from the headers set automatically by the user agent (for example, Connection, User-Agent, or any of the other headers with names defined in the Fetch spec as a"forbidden header name"), the only headers which are allowed to be manually set are those which the Fetch spec defines as being a"CORS-safelisted request-header", which are:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type (but note the additional requirements below)
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width

The only allowed values for the Content-Type header are:

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

No event listeners are registered on any XMLHttpRequestUpload object used in the request; these are accessed using the XMLHttpRequest.upload property.

No ReadableStream object is used in the request.


经历过这个问题,下面是我对这个问题和我的解决方案的结论。

根据CORS策略(强烈建议您阅读它)如果它认为需要,您不能强制浏览器停止发送OPTIONS请求。

有两种方法可以解决它:

  • 确保您的请求是"简单请求"
  • 为OPTIONS请求设置Access-Control-Max-Age
  • 简单的要求

    一个简单的跨站点请求是满足以下所有条件的请求:

    唯一允许的方法是:

    • 得到
    • POST

    除了由用户代理自动设置的标头(例如,连接,用户代理等)之外,允许手动设置的唯一标头是:

    • 接受
    • 接受语言
    • 内容语言
    • 内容类型

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

    • 应用程序/ x-WWW窗体-urlencoded
    • 多部分/格式数据
    • 纯文本/

    简单的请求不会导致飞行前的OPTIONS请求。

    为OPTIONS检查设置缓存

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

    Access-Control-Max-Age gives the value in seconds for how long the response to the preflight request can be cached for without sending another preflight request.

    限制注意到

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


    请参考这个答案,了解预先确定的OPTIONS请求的实际需求:CORS - 引入预检请求背后的动机是什么?

    要禁用OPTIONS请求,必须满足以下条件:ajax请求:

  • 请求不会设置自定义HTTP标头,如'application / xml'或'application / json'等
  • 请求方法必须是GET,HEAD或POST之一。如果是POST,则内容类型应为application/x-www-form-urlencodedmultipart/form-datatext/plain之一
  • 参考:
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS


    打开调试控制台并打开Disable Cache选项后,将始终发送预检请求(即在每个请求之前)。如果您不禁用缓存,则只会发送一次飞行前请求(每台服务器)


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

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

    工作实例:
    http://jpillora.com/xdomain/


    对于了解其存在的原因但需要访问不使用auth处理OPTIONS调用的API的开发人员,我需要一个临时答案,以便我可以在本地开发,直到API所有者添加适当的SPA CORS支持或我获得代理API启动并运行。

    我发现你可以在Safari上禁用CORS,在Mac上禁用Chrome。

    在Chrome中停用相同的来源政策

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

    Safari:在Safari中禁用同源策略

    If you want to disable the same-origin policy on Safari (I have 9.1.1), then you only need to enable the developer menu, and select"Disable Cross-Origin Restrictions" from the develop menu.


    正如之前的帖子中所提到的,OPTIONS请求是有原因的。如果您的服务器响应时间很长(例如海外连接),您也可以让浏览器缓存预检请求。

    让您的服务器使用Access-Control-Max-Age标头回复,对于转到同一端点的请求,预检请求将被缓存而不再发生。


    我已经解决了这个问题。

    1
    2
    3
    4
    5
    6
    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();
    }

    它只是为了发展。有了这个我等待9ms和500ms而不是8s和500ms。我可以这样做,因为生产JS应用程序将与生产在同一台机器上,所以没有OPTIONS但是开发是我的本地。


    你不能但是你可以使用JSONP来避免使用CORS。


    花了整整一天半的时间试图解决类似的问题,我发现它与IIS有关。

    我的Web API项目设置如下:

    1
    2
    3
    4
    5
    6
    7
    // WebApiConfig.cs
    public static void Register(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute("*","*","*");
        config.EnableCors(cors);
        //...
    }

    我在web.config> system.webServer节点中没有像我在很多帖子中看到过的CORS特定配置选项

    global.asax或控制器中没有CORS特定代码作为装饰器

    问题是应用程序池设置。

    托管管道模式设置为classic(将其更改为集成),Identity设置为Network Service(将其更改为ApplicationPoolIdentity)

    更改这些设置(并刷新应用程序池)为我修复了它。


    对我有用的是导入"github.com/gorilla/handlers",然后以这种方式使用它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    router := mux.NewRouter()
    router.HandleFunc("/config", getConfig).Methods("GET")
    router.HandleFunc("/config/emcServer", createEmcServers).Methods("POST")

    headersOk := handlers.AllowedHeaders([]string{"X-Requested-With","Content-Type"})
    originsOk := handlers.AllowedOrigins([]string{"*"})
    methodsOk := handlers.AllowedMethods([]string{"GET","HEAD","POST","PUT","OPTIONS"})

    log.Fatal(http.ListenAndServe(":" + webServicePort, handlers.CORS(originsOk, headersOk, methodsOk)(router)))

    一旦我执行了Ajax POST请求并将JSON数据附加到它,Chrome就会始终添加Content-Type标头,这不在我以前的AllowedHeaders配置中。


    在使用拦截请求并写入适当头的代理的情况下,可以解决这个问题。
    在Varnish的特殊情况下,这些将是规则:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    if (req.http.host =="CUSTOM_URL" ) {
    set resp.http.Access-Control-Allow-Origin ="*";
    if (req.method =="OPTIONS") {
       set resp.http.Access-Control-Max-Age ="1728000";
       set resp.http.Access-Control-Allow-Methods ="GET, POST, PUT, DELETE, PATCH, OPTIONS";
       set resp.http.Access-Control-Allow-Headers ="Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since";
       set resp.http.Content-Length ="0";
       set resp.http.Content-Type ="text/plain charset=UTF-8";
       set resp.status = 204;
    }

    }


    我过去使用的一个解决方案 - 假设您的网站位于mydomain.com上,您需要向foreigndomain.com发出ajax请求

    配置从您的域到外部域的IIS重写 - 例如

    1
    2
    3
    4
    5
    6
    7
    8
    <rewrite>
      <rules>
        <rule name="ForeignRewrite" stopProcessing="true">
            <match url="^api/v1/(.*)$" />
           
        </rule>
      </rules>
    </rewrite>

    在你的mydomain.com网站上 - 你可以提出相同的原始请求,并且不需要任何选项请求:)


    可能有一个解决方案(但我没有测试它):您可以使用CSP(内容安全策略)来启用您的远程域,浏览器可能会跳过CORS OPTIONS请求验证。

    如果找到一些时间,我会测试并更新这篇文章!

    CSP:https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Security-Policy

    CSP规范:https://www.w3.org/TR/CSP/