谷歌浏览器中的AJAX发送OPTIONS而不是GET/POST/PUT/DELETE?

111

我正在工作中开发一款内部网站应用程序。在IE10中,请求可以正常工作,但是在Chrome中,所有的AJAX请求(有很多)都会使用OPTIONS而不是我指定的其他方法发送。从技术上讲,我的请求是“跨域”的。该站点在localhost:6120上提供服务,而我正在发起AJAX请求的服务位于57124上。 这个已关闭的jquery bug 定义了这个问题,但没有一个真正的解决方法。

我该怎么做才能在ajax请求中使用正确的http方法?

编辑:

这是每个页面文档加载时的情况:

jQuery.support.cors = true;

每个 AJAX 的构建方式都类似:

var url = 'http://localhost:57124/My/Rest/Call';
$.ajax({
    url: url,
    dataType: "json",
    data: json,
    async: true,
    cache: false,
    timeout: 30000,
    headers: { "x-li-format": "json", "X-UserName": userName },
    success: function (data) {
        // my success stuff
    },
    error: function (request, status, error) {
        // my error stuff
    },
    type: "POST"
});

2
那个 Bug 报告中的最后一条评论解释得很清楚了。 - Kevin B
1
它让我感到困惑,因为我所做的一切都是如此基础(而我的代码与jquery bug中的代码相似)。尽管如此,这并不是不包含它的借口。BRB,获取一些示例代码。 - Corey Ogburn
3
请注意,IE 在确定请求是否跨域时不考虑端口号。 - Ray Nicholus
@KevinB:我们的REST服务利用不同的请求根据http方法执行不同的操作。将所有内容切换为GET并不是一个有效的解决方案。此外,根据Dark Falcon的回答,这也不会有所帮助,因为请求中包含X-UserName和其他自定义头信息。 - Corey Ogburn
无论如何,如果您想进行跨域请求,您必须遵循适用于跨域请求的所有规则,以使其正常工作。跨域请求通常涉及OPTIONS请求。正确处理它,问题就会消失。解决此问题的唯一其他方法(而不更改API)是在与主页面相同的服务器上拥有一个脚本,该脚本使用服务器端代码与API交互。 - Kevin B
由于这是一个内部Web应用程序,您可能只需配置Web服务器以允许通过添加自定义标头Access-Control-Allow-Origin:*的跨域请求。我知道这是一篇旧帖子,但今天谷歌将我带到了这里,我没有看到列出这种可能性。 - axawire
11个回答

140

Chrome正在进行请求的预检以查找CORS头信息。如果请求是可接受的,它将发送真实的请求。如果您在跨域请求中这样做,您将不得不处理它,否则找到一种方法使请求非跨域。这就是为什么jQuery Bug被关闭为不修复的原因,这是设计上的考虑。

与简单请求(如上所述)不同,“预检”请求首先通过OPTIONS方法向其他域上的资源发送HTTP请求,以确定实际请求是否安全发送。跨站点请求像这样进行预检,因为它们可能会对用户数据产生影响。特别地,如果满足以下条件,则进行预检请求:

  • 使用除GET、HEAD或POST之外的其他方法。此外,如果使用POST来使用Content-Type之外的请求数据发送请求,例如multipart/form-data或text/plain,例如如果POST请求使用application/xml或text/xml向服务器发送XML有效负载,则该请求将进行预检。
  • 设置自定义标头的请求(例如,请求使用X-PINGOTHER等标头)

21
自定义标头。这可能是触发预检OPTIONS调用的原因。 - Corey Ogburn

18

基于请求未发送至默认端口80/443,这个Ajax调用会自动被视为跨域资源(CORS)请求。换句话说,即使您设置了,在服务器/Servlet的一侧也会自动发出OPTIONS请求以检查CORS标头。

即使您设置了

crossOrigin: false;

即使您省略它,也是如此。

原因很简单:localhost != localhost:57124。 尝试仅将其发送到没有端口的localhost - 它将失败,因为无法到达请求的目标,但请注意,如果域名相等,则在POST之前不需要OPTIONS请求即可发送请求。


3
我同意Kevin B的观点,bug报告已经说得很清楚了。听起来你正在尝试进行跨域ajax调用。如果你不熟悉同源策略,可以从这里开始了解:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Same_origin_policy_for_JavaScript
如果这不是一个跨域ajax调用,尝试将目标url设置为相对路径,看看问题是否解决。如果你真的很绝望,可以考虑使用JSONP,但要小心,可能会出现混乱。我们真的没有太多可以帮助你的了。

1
我们的系统结构是我无法改变的。使用不同的端口是我们架构的要求。我理解同源策略,但认为我们实现的CORS足够了。显然不是这样。 - Corey Ogburn
2
如果您的服务器返回JSON响应,您可以研究JSONP方法,只要负责使用即可。 - jgitter
1
我并不想和你争论,但是JSONP使用脚本标签从另一个域中获取数据,然后将结果发送到回调函数。如果结果不是json格式,那就更难了。 - jgitter
1
不,这并不更难。实际上,在任何情况下,响应都不应该是有效的JSON。相反,服务器应该返回类似于callbackfunc(somedata)的内容。正如你所看到的,这不是有效的JSON。而且,somedata可以是一个字符串、一个数字,或者任何你想要的东西。 - Ray Nicholus
1
我正在使用Postman,请求方法被正确地发送(例如“PUT”,“DELETE”等)。但是当我尝试从我的代码中执行时,它总是使用请求方法OPTIONS发送。我不知道Postman如何做到这一点。 - goerwin
显示剩余5条评论

1
如果可能的话,可以通过常规的GET/POST方式传递参数,并使用不同的名称让您的服务器端代码处理它。我曾经遇到过类似的问题,我的代理绕过CORS也在Chrome中出现了POST->OPTION的错误。在我的情况下是Authorization头部(在这里是"x-li-format"和"X-UserName")。最终我选择以虚拟格式传递它(例如,在GET中使用“AuthorizatinJack”),并更改了代理代码,使其在调用目标时将其转换为头部。以下是PHP示例:
if (isset($_GET['AuthorizationJack'])) {
    $request_headers[] = "Authorization: Basic ".$_GET['AuthorizationJack'];
}

1
在我的情况下,我正在调用由AWS(API网关)托管的API。当我尝试从API所属的域之外的域调用API时,发生了错误。由于我是API所有者,我已经为测试环境启用了CORS,如Amazon Documentation中所述。 在生产中,由于请求和API将位于同一域中,这个错误不会发生。 希望这可以帮助!

0

考虑使用axios

axios.get( url,
{ headers: {"Content-Type": "application/json"} } ).then( res => {

  if(res.data.error) {

  } else { 
    doAnything( res.data )
  }

}).catch(function (error) {
   doAnythingError(error)
});

我在使用fetch时遇到了问题,而axios完美地解决了这个问题。


5
Axios 也使用首个 OPTIONS。 - Skylin R

0

正如 @Dark Falcon 所回答,我简单地处理了它

在我的情况下,我正在使用 node.js 服务器,并且如果会话不存在,则创建一个会话。由于 OPTIONS 方法没有会话详细信息,它最终为每个 POST 方法请求创建了一个新会话。

因此,在我的应用程序例程中创建-如果不存在会话时,我只是添加了一个检查来查看方法是否为OPTIONS,如果是,则跳过会话创建部分:

    app.use(function(req, res, next) {
        if (req.method !== "OPTIONS") {
            if (req.session && req.session.id) {
                 // Session exists
                 next();
            }else{
                 // Create session
                 next();
          }
        } else {
           // If request method is OPTIONS, just skip this part and move to the next method.
           next(); 
        }
    }

0

使用 Fetch 代替 XHR,即使跨域请求,也不会进行预检请求。


0

1
你能再添加一些信息吗?你的回答看起来像是一条评论。 :) - Badacadabra

0

我遇到了一个非常类似的问题。我花了将近半天的时间来理解为什么在Firefox中一切都正常工作,而在Chrome中失败。在我的情况下,这是因为我的请求头中存在重复(或者可能是打错)的字段。


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