不一致 AJAX 预检选项请求

Inconsistent AJAX Preflight OPTIONS Requests

我希望这个问题不是太含糊,但我觉得我遗漏了一些基本的东西。这是我所处的情况。我们有一个胖客户端应用程序,它在每个用户的本地计算机 (Windows 7) 上运行,并提供 JavaScript API 来与应用程序交互。我们有一个托管在服务器上的 Web 应用程序,但向本地主机发出 AJAX 请求以与胖客户端 JS API 交互。

在一台机器上,我们加载网络应用程序。向 localhost 发出的所有 AJAX 请求都是在没有 OPTIONS 预检请求的情况下发出的。 JS 控制台的网络选项卡显示正在生成 POST,并且响应成功(我要告诉胖客户端要在响应中包含哪些 header,包括所有必要的 CORS headers).

在另一台机器上,我们从同一台服务器加载网络应用程序。但是,当向本地主机发出 AJAX 请求时,会发出(预期的)OPTIONS 请求。不幸的是,胖客户端没有以适当的 Access-Control-Allow-Origin header 响应,因此不允许进行后续的 POST。

浏览器无关紧要,我们已经测试了最新版本的 Firefox 和 Chrome,我们总是从机器上得到相同的行为。我们已经在 4 台机器上进行了测试——2 台发送 OPTIONS,2 台只发送 POST,前面没有 OPTIONS。 AJAX 目的地是否与原点不同以及它是否发送预检请求似乎取决于浏览器。我终其一生都无法弄清楚为什么我的机器不发送任何 OPTIONS 请求,即使 AJAX 请求(到本地主机或 lvh.me)是不同的 host/domain 而不是网页的来源,但是从另一台机器加载相同的页面确实会产生它们。

是否有某种浏览器设置对此有影响?还是 Windows 设置?为什么我们会看到不同的行为?

更新 1:

为了简化和阐明,我有两台机器,machineA 和 machineB。两台机器都从远程服务器 example.com 加载 Web 应用程序。两台机器都在其本地机器上安装了一个完整的厚应用程序,该应用程序提供了一个 Web 服务器和 API。从 example.com 加载的 Web 应用程序包含 JavaScript 代码,用于向本地主机提交 AJAX 请求,以便每个用户都可以与他们自己的胖客户端应用程序的本地实例进行交互。由于 localhost != example.com,请求应被视为 cross-domain。

当machineA向localhost提交一个标准的jQuery AJAX请求时(其中POST的payload只是"true",会导致胖客户端回显值——仅用于轮询以确定胖客户端是否可用),POST 是直接生成的,没有 OPTIONS 请求: POST http://localhost:4000/cgi-bin/Extensions/GeoLinkConsole/evaljs.html 200 正常 0 毫秒 有趣的是,POST 的响应必须包含 Access-Control-Allow-Origin header 和 example.com 以避免 CORS 错误。

当 machineB 从 example.com 加载相同的页面时,向本地主机提交相同的标准 jQuery AJAX 请求,具有相同的 "true" 负载,浏览器提交一个选项请求。这是我实际期望的行为。 OPTIONS 请求响应为 200 OK,但它不包括 Access-Control-Allow-Origin header,因此它阻止了 POST 通过。这不是我主要关心的问题,显然是胖客户端的问题。我最大的问题是为什么有些机器正在生成 OPTIONS 而有些则没有。我希望这有助于解释。

更新 2:

我包括了两组请求 header。我不知道实际请求 header 会影响是否会生成 OPTIONS 请求。马上,我看到发送 OPTIONS 的那个同时包含 Acces-Control-Request-Headers 和 Access-Control-Request_Method 而另一个没有...

从发送 POST 的机器请求 headers,没有选项:

Accept:    text/plain, */*; q=0.01
Accept-Encoding:    gzip, deflate
Accept-Language:    en-US,en;q=0.5
Content-Length:    354
Content-Type:   text/plain; charset=UTF-8
Host:    localhost:4000
Origin:    http://example.com:7802
Referer:    http://example.com:7802/
User-Agent:    Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0

从发送 OPTIONS 的机器请求 headers:

Accept:    */*
Accept-Encoding:    gzip, deflate, sdch
Accept-Language:    en-US,en;q=0.8
Access-Control-Request-Headers:    accept, content-type, x-csrftoken
Access-Control-Request-Method:    POST
Connection:    keep-alive
Host:    localhost:4000
Origin:    http://example.com:7802
Referer:    http://example.com:7802/
User-Agent:    Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36

此外,机器有一个 "general" headers 块——我假设我刚刚发布的请求 headers 是针对原始 POST请求,这些与预检选项请求有关:

Request URL:    http://localhost:4000/cgi-bin/Extensions/GeoLinkConsole/evaljs.html
Request Method:   OPTIONS
Status Code:    200 OK
Remote Address:    127.0.0.1:4000

(您可能有兴趣阅读我在 How does Access-Control-Allow-Origin header work? the HTML5 writeup on CORS 上的回答的 "non-simple" 部分,以大致了解为什么会发生 OPTIONS 预检请求。)

如您所见,OPTIONS 请求包括 Access-Control-Request-Headers,其中包括 header 名称 x-csrftoken。这意味着 JavaScript 正在尝试发送一个名为 x-csrftoken 的 header。由于这不是简单的 CORS header(即 AcceptAccept-LanguageContent-Language,有时 Content-Type),它必须首先被预检允许选项请求。

除了正常的 Access-Control-Allow-Origin header 之外,服务器还应使用 Access-Control-Allow-Headers header 包含 x-csrftoken header名字。完成后,浏览器将允许实际的 POST 请求通过服务器。

或者,完全删除 non-simple x-csrftoken header,这样就不需要预检了。