一个CORS POST请求使用纯JavaScript可以工作,但为什么不能用jQuery?

95

我正在尝试创建一个跨域post请求,我已经在普通的JavaScript中成功实现了:

var request = new XMLHttpRequest();
var params = "action=something";
request.open('POST', url, true);
request.onreadystatechange = function() {if (request.readyState==4) alert("It worked!");};
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
request.setRequestHeader("Content-length", params.length);
request.setRequestHeader("Connection", "close");
request.send(params);

但我想使用 jQuery,但我无法使其工作。这是我的尝试:

$.ajax(url, {
    type:"POST",
    dataType:"json",
    data:{action:"something"}, 
    success:function(data, textStatus, jqXHR) {alert("success");},
    error: function(jqXHR, textStatus, errorThrown) {alert("failure");}
});

这会导致失败。如果有人知道为什么jQuery不起作用,请让我们都知道。谢谢。

(我正在使用jQuery 1.5.1和Firefox 4.0,我的服务器正常响应Access-Control-Allow-Origin头)


这对我来说是解决方案(使用JavaScript的XMLHttpRequest),当我在Ionic框架3中面临CORS问题时。 - jeudyx
5个回答

75

更新:如TimK所指出的,这在jquery 1.5.2中不再需要。但是,如果您想添加自定义标头或允许使用凭据(用户名、密码或Cookie等),请继续阅读。


我认为我找到答案了!(经过4个小时和大量咒骂之后)

//This does not work!!
Access-Control-Allow-Headers: *

你需要手动指定你将接受的所有标头(至少在我使用的Firefox 4.0和Chrome 10.0.648.204中是这样的)。

jQuery的$.ajax方法发送“x-requested-with”标头以进行所有跨域请求(我认为它仅针对跨域)。

因此,响应OPTIONS请求所需的缺失标头是:

//no longer needed as of jquery 1.5.2
Access-Control-Allow-Headers: x-requested-with
如果你传递任何非“简单”的标头,你需要在列表中包含它们(我发送了一个额外的)。

如果你传递任何非“简单”的标头,你需要在列表中包含它们(我发送了一个额外的):

//only need part of this for my custom header
Access-Control-Allow-Headers: x-requested-with, x-requested-by

综上所述,这是我的PHP代码:

// * wont work in FF w/ Allow-Credentials
//if you dont need Allow-Credentials, * seems to work
header('Access-Control-Allow-Origin: http://www.example.com');
//if you need cookies or login etc
header('Access-Control-Allow-Credentials: true');
if ($this->getRequestMethod() == 'OPTIONS')
{
  header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
  header('Access-Control-Max-Age: 604800');
  //if you need special headers
  header('Access-Control-Allow-Headers: x-requested-with');
  exit(0);
}

5
请注意,jQuery 1.5.2更改了其行为。它不再添加“ X-Requested-With”头信息,因此这可能不再是一个问题。http://blog.jquery.com/2011/03/31/jquery-152-released/(错误8423) - Magmatic
1
@TimK,你是对的!我没有注意到他们发布了1.5.2版。话虽如此,如果需要进行预检,则这也可以起作用。我已经更新了我的答案。 - Will Mason
1
@Elisabeth 这种方法只在您控制所请求的目标时才有效...它不是中间脚本。它是我们所请求位置的 PHP 顶部。这样更清楚了吗? - Will Mason
2
是的!谢谢Will。我以为你可以从客户端控制一切,但听起来你需要控制两端。 - Elisabeth
这个头部是不是 $.ajax() 的一个属性? - PositiveGuy
显示剩余2条评论

19
另一种可能性是,设置 dataType: json 会导致 JQuery 发送 Content-Type: application/json 标头。这被 CORS 视为非标准标头,并需要 CORS 预检请求。因此有几件事情可以尝试:
1)尝试配置您的服务器以发送正确的预检响应。这将采用其他标头的形式,如 Access-Control-Allow-MethodsAccess-Control-Allow-Headers
2)删除 dataType: json 设置。JQuery 应默认请求 Content-Type: application/x-www-form-urlencoded,但为确保起见,您可以将 dataType: json 替换为 contentType: 'application/x-www-form-urlencoded'

谢谢您的建议。我尝试了不设置dataType,将其设置为application/x-www-form-urlencoded甚至是text/plain。我还尝试添加响应头Access-Control-Allow-Methods "POST, GET, OPTIONS",但都没有起作用。 - Magmatic
你能否查看JavaScript错误控制台(或Firebug的控制台),并查看请求期间是否有任何错误?此外,如果您知道如何使用Wireshark,您可以使用它来查看实际通过网络传输的HTTP请求。 - monsur
1
另一个可能性是设置dataType: json会导致JQuery发送Content-Type: application/json头部信息——这种情况并不会发生。dataType影响的是Accept请求头,而不是Content-Type请求头。 - Quentin

9
你正在使用JS发送“params”: request.send(params); 但在jQuery中使用“data”。已定义数据吗? data:data, 此外,URL存在错误:
$.ajax( {url:url,
         type:"POST",
         dataType:"json",
         data:data, 
         success:function(data, textStatus, jqXHR) {alert("success");},
         error: function(jqXHR, textStatus, errorThrown) {alert("failure");}
});

您将语法与$.post的语法混淆了


更新: 我根据 monsur 的回答进行了谷歌搜索,发现您需要添加 Access-Control-Allow-Headers: Content-Type (以下是完整段落)

http://metajack.im/2010/01/19/crossdomain-ajax-for-xmpp-http-binding-made-easy/

How CORS Works

CORS works very similarly to Flash's crossdomain.xml file. Basically, the browser will send a cross-domain request to a service, setting the HTTP header Origin to the requesting server. The service includes a few headers like Access-Control-Allow-Origin to indicate whether such a request is allowed.

For the BOSH connection managers, it is enough to specify that all origins are allowed, by setting the value of Access-Control-Allow-Origin to *. The Content-Type header must also be white-listed in the Access-Control-Allow-Headers header.

Finally, for certain types of requests, including BOSH connection manager requests, the permissions check will be pre-flighted. The browser will do an OPTIONS request and expect to get back some HTTP headers that indicate which origins are allowed, which methods are allowed, and how long this authorization will last. For example, here is what the Punjab and ejabberd patches I did return for OPTIONS:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type 
Access-Control-Max-Age: 86400

1
抱歉。是的。var data = {action:"something"} - Magmatic
您可以在此处比较这两个函数的语法:http://api.jquery.com/jQuery.post/ - Aleadam
我刚刚尝试了在设置中使用的URL,但是问题依旧。.ajax函数可以两种方式处理它。 - Magmatic
我已经有了其中两个头文件。我添加了另外两个。使用jQuery仍然出现“失败”。纯JavaScript仍然可以工作。 - Magmatic
我能想到的最后一件事是使用http://api.jquery.com/jQuery.ajaxSetup来设置`jQuery.ajaxSetup({'beforeSend': function(xhr) {xhr.setRequestHeader(string, string)}})`并尝试发送不同的标头(这里有一个Rails的例子:http://railscasts.com/episodes/136-jquery)。 - Aleadam

1
Cors会在请求完成之前将请求方法从POST更改为OPTIONS,因此您的POST数据将不会被发送。解决这个cors问题的方法是使用ajax执行请求,它不支持OPTIONS方法。示例代码:
        $.ajax({
            type: "POST",
            crossdomain: true,
            url: "http://localhost:1415/anything",
            dataType: "json",
            data: JSON.stringify({
                anydata1: "any1",
                anydata2: "any2",
            }),
            success: function (result) {
                console.log(result)
            },
            error: function (xhr, status, err) {
                console.error(xhr, status, err);
            }
        });

使用C#服务器上的这些头部信息:
                    if (request.HttpMethod == "OPTIONS")
                    {
                          response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
                          response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
                          response.AddHeader("Access-Control-Max-Age", "1728000");
                    }
                    response.AppendHeader("Access-Control-Allow-Origin", "*");

-2

按照以下方式修改您的JQuery:

$.ajax({
            url: someurl,
            contentType: 'application/json',
            data: JSONObject,
            headers: { 'Access-Control-Allow-Origin': '*' }, //add this line
            dataType: 'json',
            type: 'POST',                
            success: function (Data) {....}
});

为什么我想让我的Ajax调用同步呢? - Radko Dinev
contentType: 'application/json', data: JSONObject,服务器不期望JSON,因此发送JSON没有意义。另外,JSON对象并不存在 - Quentin
1
头部信息:{ 'Access-Control-Allow-Origin': '*' },//不要这样做。Access-Control-Allow-Origin是一个响应头部信息,而不是请求头部信息。最好的情况下,这将不起任何作用。最坏的情况下,它会将请求从简单请求转换为预检请求,这使得在服务器上处理它变得更加困难。 - Quentin

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