跨域XHR请求失败

6

我有一个 API 托管在一个启用了 CORS 的域上,具体的头信息如下:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Max-Age: 1728000

我能够从hackst.com发起GET或POST请求,它们可以正常工作。链接:http://hackst.com/#w3SbV

从我的backbone应用程序托管在另一个域上时,GET请求可以正常工作。但是当我尝试创建和保存新模型(即发起POST请求)时,会出现以下错误:

Failed to load resource: the server responded with a status of 501 (Not Implemented) http://projectwhatup.us:5000/api/posts
XMLHttpRequest cannot load http://projectwhatup.us:5000/api/posts. Origin http://ayush.projectwhatup.us is not allowed by Access-Control-Allow-Origin.

我的相关Backbone代码:

var newPostData = {
    topic : "New Post",
    body : "new body",          
    user_id : 1,
};  

var newPostModel = new Post(newPostData);
this.model.create(newPostModel);

我甚至尝试重写create方法并手动发起POST请求,如下:

create : function(data) {
    console.log('overriden create');

    $.ajax({
        "url" : this.url,
        "async" : true,
        "beforeSend" : function(obj){
            console.log(obj);
        },
        "contentType" : 'application/json',
        //"crossDomain" : true,  // uncommenting this doesnt help either
        "headers" : {

        },
        "dataType" : 'json',
        "type" : 'POST',
        "data" : JSON.stringify(data),
        "error" : function(err){
            console.log('new post creation failed');
            console.log(err);
        },
        "success" : function(resp){
            console.log('new post created');
            console.log(resp);
        }
    });
}

出现了相同的错误。

我也在JSFiddle上尝试了一个独立的GET请求(http://jsfiddle.net/X9cqh/5/),但是它失败了,尽管我的backbone应用程序可以正常进行GET请求。

此时我完全不知所措。有什么提示、指针或解决方案吗?


查看hurl.it对您的服务器发出HEAD请求,看起来CORS仅针对http://ayush.projectwhatup.us启用。 - teddybeard
为什么来自hackst.com的请求可以通过呢? - Ayush
这是因为请求不是由您的网络浏览器发出的。与Hurl.it类似,当您创建hackst.com请求时,它会在其服务器上执行,然后在您的网络浏览器中显示结果。 - teddybeard
CORS 能够让一个网页直接在浏览器内部访问另一个域名下的信息,例如 ajax 请求。在浏览器外部,任何人都可以随时向任何服务器发出 HTTP 请求。例如,你可以从任何计算机上立即 telnet 80 到 projectwhatup.us,无论它们是否启用了 CORS。 - teddybeard
啊!我没有想到hurl和hackst是通过后端发出请求的。 - Ayush
更新:设置 Access-Control-Allow-Origin: *,jsfiddle 仍然失败 http://jsfiddle.net/X9cqh/9/ - Ayush
3个回答

6
服务器还应该使用以下标头回复预检请求:
Access-Control-Allow-Headers: Content-Type

这是必要的,因为内容类型为application/json,超出了CORS规范(http://www.w3.org/TR/cors/)定义的可接受值。


我们已经在服务器上添加了 Access-Control-Allow-Headers: Content-TypeAccess-Control-Allow-Headers: Content-Type, X-Requested-With(二者选一,不能同时存在),但是在发出请求时指定 content-type 会导致请求失败。 - Ayush
1
“either-or”是什么意思?你应该在头部包含所有这些值。 - monsur
哦,是啊,兄弟!我浪费了两天的时间才解决这个问题。谢谢你! - Stepan Yudin
这对我有效,遇到了同样的问题,答案也有效,非常感谢。 - Buddhika Alwis

1

您的服务器设置正常。JSFiddle 显然不会发出 ajax 请求,但您可以通过在 Chrome 控制台或 Safari 开发者控制台中输入以下四行代码来快速测试它是否正常工作:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://projectwhatup.us:5000/api/posts', false);
xhr.send();
xhr.responseText;

enter image description here

如果您尝试在不允许CORS的域上进行此操作,它将会出错。

发现将 Content-Type: application/json 添加到请求中会导致 CORS 失败。如果没有它,ajax 调用就可以工作 (http://jsfiddle.net/X9cqh/11/)。问题在于当进行 POST 请求时,API 需要设置该标头,否则它会响应 400 Bad Request - Ayush
此答案中的代码不会触发预检请求(没有额外的头部信息,使用简单的 GET 方法),这就是为什么它似乎有效的原因。 - kybernetikos

1

如果添加“Content-Type”标头使您的CORS请求失败,原因是您的服务器设置错误。

如果客户端希望指定特定标头或使用不寻常的HTTP方法动词(例如PUT),则浏览器将首先进行“preflight”OPTIONS调用以确保允许设置这些标头。您的服务器需要使用适当的标头响应此OPTIONS调用。如果您想确认问题所在,可以在Chrome开发人员工具或Firebug的网络选项卡中看到该选项调用。

您可能会对我更详细的答案此处感兴趣。


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