跨域资源共享(CORS)的POST请求可以在纯JavaScript中工作,但为什么不能在jQuery中工作?

251
我在本地局域网上有一台机器(机器A),上面有两个Web服务器。第一个是XBMC内置的服务器(端口8080),用于显示我们的库。第二个服务器是一个CherryPy的Python脚本(端口8081),我用它来按需触发文件转换。文件转换是通过从XBMC服务器提供的页面发出的AJAX POST请求来触发的。
  • 访问http://machineA:8080,显示库
  • 库被显示出来
  • 用户点击“转换”链接,发出以下命令 -

jQuery Ajax请求

$.post('http://machineA:8081', {file_url: 'asfd'}, function(d){console.log(d)})

浏览器发送一个带有以下头部的HTTP OPTIONS请求; 请求头部 - OPTIONS
Host: machineA:8081
User-Agent: ... Firefox/4.01
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Origin: http://machineA:8080
Access-Control-Request-Method: POST
Access-Control-Request-Headers: x-requested-with

服务器的回应如下; 响应头 - OPTIONS (状态 = 200 OK)
Content-Length: 0
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 1728000
Server: CherryPy/3.2.0
Date: Thu, 21 Apr 2011 22:40:29 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Content-Type: text/html;charset=ISO-8859-1
  • 对话随之停止。浏览器理论上应该发送一个POST请求,因为服务器响应了正确的CORS标头(Access-Control-Allow-Origin: *)

为了进行故障排除,我还从http://jquery.com发出了相同的$.post命令。这就是我困惑的地方,从jquery.com发送的POST请求有效,然后发送OPTIONS请求,然后再发送POST请求。此事务的标头如下;

请求头 - OPTIONS

Host: machineA:8081
User-Agent: ... Firefox/4.01
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Origin: http://jquery.com
Access-Control-Request-Method: POST

响应头 - OPTIONS(状态 = 200 OK)

Content-Length: 0
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 1728000
Server: CherryPy/3.2.0
Date: Thu, 21 Apr 2011 22:37:59 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Content-Type: text/html;charset=ISO-8859-1

请求头 - POST

Host: machineA:8081
User-Agent: ... Firefox/4.01
Accept: */*
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://jquery.com/
Content-Length: 12
Origin: http://jquery.com
Pragma: no-cache
Cache-Control: no-cache

响应头 - POST(状态 = 200 OK)

Content-Length: 32
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 1728000
Server: CherryPy/3.2.0
Date: Thu, 21 Apr 2011 22:37:59 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Content-Type: application/json

我弄不清楚为什么同样的请求在一个网站上可以工作,但在另一个网站上却不行。希望有人能指出我漏掉了什么。谢谢你的帮助!

如果两个Web服务器在同一台机器上,是否需要CORS? - jdigital
11
据我所知,由于不同的端口,它是一个CORS请求。另外,OPTIONS请求表明浏览器将其视为CORS请求。 - James
11个回答

175

我最终偶然发现了这个链接 "A CORS POST request works from plain javascript, but why not with jQuery?",它指出jQuery 1.5.1添加了

 Access-Control-Request-Headers: x-requested-with
对于所有CORS请求都设置响应头。jQuery 1.5.2没有这样做。另外,根据同一个问题,设置服务器响应头为
Access-Control-Allow-Headers: *

不允许响应继续。您需要确保响应头具体包括所需的标头。例如:

Access-Control-Allow-Headers: x-requested-with 

78

请求:

 $.ajax({
            url: "http://localhost:8079/students/add/",
            type: "POST",
            crossDomain: true,
            data: JSON.stringify(somejson),
            dataType: "json",
            success: function (response) {
                var resp = JSON.parse(response)
                alert(resp.status);
            },
            error: function (xhr, status) {
                alert("error");
            }
        });

回复:

response = HttpResponse(json.dumps('{"status" : "success"}'))
response.__setitem__("Content-type", "application/json")
response.__setitem__("Access-Control-Allow-Origin", "*")

return response

3
如果服务器不接受跨域请求,使用 crossdomain=true 并不能解决该问题。相比之下,使用 dataType:"jsonp" 并设置 callback 为 jsonpCallback: "response" 更好。参考:http://api.jquery.com/jquery.ajax/ - BonifatiusK

35

当我使用Google距离矩阵API时,通过在Jquery ajax中设置请求头解决了自己的问题。请看下面。

var settings = {
          'cache': false,
          'dataType': "jsonp",
          "async": true,
          "crossDomain": true,
          "url": "https://maps.googleapis.com/maps/api/distancematrix/json?units=metric&origins=place_id:"+me.originPlaceId+"&destinations=place_id:"+me.destinationPlaceId+"&region=ng&units=metric&key=mykey",
          "method": "GET",
          "headers": {
              "accept": "application/json",
              "Access-Control-Allow-Origin":"*"
          }
      }

      $.ajax(settings).done(function (response) {
          console.log(response);

      });

请注意我在设置中添加的内容。
**

"headers": {
          "accept": "application/json",
          "Access-Control-Allow-Origin":"*"
      }

**希望这可以帮到你。


1
这是我们唯一的解决方案,而且没有改变服务器端的任何内容...谢谢miracool。 - Timsta
3
@Timsta,很高兴我的建议对你有用。也感谢Stack Overflow。祝你有美好的一天。 - Miracool
4
设置dataType为"jsonp"解决了我的问题。 - Shemang David
似乎这个不再起作用了 https://chromestatus.com/feature/5629709824032768 - Manos

14

找到解决方法花了我一些时间。

如果你的服务器响应正确,而请求是问题所在,你应该在请求中的xhrFields中添加withCredentials: true

$.ajax({
    url: url,
    type: method,
    // This is the important part
    xhrFields: {
        withCredentials: true
    },
    // This is the important part
    data: data,
    success: function (response) {
        // handle the response
    },
    error: function (xhr, status) {
        // handle errors
    }
});

注意:需要使用 jQuery 版本大于等于 1.5.1。


jQuery 版本没问题吧?你设置了 withCredentials: true 吗?你确定有相关的头信息吗? - Dekel
是的,withCredential: truejquery版本:3.2.1。实际上它在Postman中可以工作,但在Chrome浏览器中无法通过。 - Mox Shah
1
我几乎可以确定Postman不应该有CORS问题,因为它不是浏览器,其行为是不同的。你确定服务器向客户端发送了正确且相关的标头吗?再次强调-注意这个变化并不足够。您需要确保服务器响应具有正确的标头。 - Dekel
你能告诉我服务器需要哪些响应头吗? - Mox Shah
@MoxShah 这是一个完全不同的问题,那里有很多资源 :) - Dekel

9

我曾经为这个问题苦恼了几周。

最简单、最合规、非hacky的方法可能是使用不进行基于浏览器调用并能处理跨源请求的提供程序JavaScript API。

例如,Facebook JavaScript API和Google JS API。

如果你的API提供商没有更新,并且其响应中不支持Cross Origin Resource Origin '*'标头,并且没有JS api(是的,我说的是Yahoo),则有三种选择:

  1. 在请求中使用jsonp,它会将回调函数添加到URL中,其中你可以处理你的响应。注意:这将更改请求URL,因此你的API服务器必须具备处理URL末尾的?callback=的能力。

  2. 将请求发送到由你控制的API服务器上,该服务器要么与客户端在同一域中,要么从那里启用了Cross Origin Resource Sharing,从而可以代理请求到第三方API服务器。

  3. 在需要处理用户交互并进行OAuth请求时,可能最有用的方式是使用window.open('url',"newwindowname",'_blank', 'toolbar=0,location=0,menubar=0')


7
以下是我为您翻译的内容:

这是我的经验总结:

定义一个新函数(包装 $.ajax 以简化):

jQuery.postCORS = function(url, data, func) {
  if(func == undefined) func = function(){};
  return $.ajax({
    type: 'POST', 
    url: url, 
    data: data, 
    dataType: 'json', 
    contentType: 'application/x-www-form-urlencoded', 
    xhrFields: { withCredentials: true }, 
    success: function(res) { func(res) }, 
    error: function() { 
            func({}) 
    }
  });
}

使用方法:

$.postCORS("https://example.com/service.json",{ x : 1 },function(obj){
      if(obj.ok) {
           ...
      }
});

还适用于.done.fail等:

$.postCORS("https://example.com/service.json",{ x : 1 }).done(function(obj){
      if(obj.ok) {
           ...
      }
}).fail(function(){
    alert("Error!");
});

在服务器端(就像 example.com 所托管的情况下),请设置以下标头(提供了一些 PHP 示例代码):

header('Access-Control-Allow-Origin: https://not-example.com');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 604800');
header("Content-type: application/json");
$array = array("ok" => $_POST["x"]);
echo json_encode($array);

我所知道的唯一一种可以从JS进行跨域POST的方法。

JSONP将POST转换为GET,可能会在服务器日志中显示敏感信息。


4
使用这个方法结合Laravel解决了我的问题。只需要在你的jquery请求中添加这个头部信息:Access-Control-Request-Headers: x-requested-with,并确保你的服务器端响应设置了这个头部信息:Access-Control-Allow-Headers: *

8
不需要手动添加CORS头信息。浏览器会为您自动添加必要的CORS头信息到请求中。 - Ray Nicholus
1
真正的挑战在于使服务器正确回复 Access-Control-Allow-Headers,并且 JQ 提供正确的 Access-Control-Request-Headers(以及你通过代码添加的任何内容),其中两者都不能是通配符。只要有一个不良头部,就会导致预检出现问题,例如使用 If-None-Match 进行条件 GET,如果服务器没有将其列出。 - escape-llc

2
我曾遭遇同样的问题,即使用jquery ajax时,仅对post请求产生cors问题而get请求正常——我尝试过上述所有方法但无果。我的服务器上有正确的头信息。改用XMLHTTPRequest代替jquery立即解决了我的问题。无论我使用哪个版本的jquery都不能解决问题。如果不需要向后兼容浏览器,则Fetch也可以无问题使用。
        var xhr = new XMLHttpRequest()
        xhr.open('POST', 'https://mywebsite.com', true)
        xhr.withCredentials = true
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 2) {// do something}
        }
        xhr.setRequestHeader('Content-Type', 'application/json')
        xhr.send(json)

希望这能帮助其他遇到同样问题的人。

1
这个函数将异步获取一个CORS启用页面的HTTP状态回复。只有具有适当标头的页面在通过XMLHttpRequest访问时返回200状态 - 无论使用GET还是POST。除非只需要JSON对象,否则客户端无法绕过此限制。以下内容可以修改以获取xmlHttpRequestObject对象中保存的数据:

function checkCorsSource(source) {
  var xmlHttpRequestObject;
  if (window.XMLHttpRequest) {
    xmlHttpRequestObject = new XMLHttpRequest();
    if (xmlHttpRequestObject != null) {
      var sUrl = "";
      if (source == "google") {
        var sUrl = "https://www.google.com";
      } else {
        var sUrl = "https://httpbin.org/get";
      }
      document.getElementById("txt1").innerHTML = "Request Sent...";
      xmlHttpRequestObject.open("GET", sUrl, true);
      xmlHttpRequestObject.onreadystatechange = function() {
        if (xmlHttpRequestObject.readyState == 4 && xmlHttpRequestObject.status == 200) {
          document.getElementById("txt1").innerHTML = "200 Response received!";
        } else {
          document.getElementById("txt1").innerHTML = "200 Response failed!";
        }
      }
      xmlHttpRequestObject.send();
    } else {
      window.alert("Error creating XmlHttpRequest object. Client is not CORS enabled");
    }
  }
}
<html>
<head>
  <title>Check if page is cors</title>
</head>
<body>
  <p>A CORS-enabled source has one of the following HTTP headers:</p>
  <ul>
    <li>Access-Control-Allow-Headers: *</li>
    <li>Access-Control-Allow-Headers: x-requested-with</li>
  </ul>
  <p>Click a button to see if the page allows CORS</p>
  <form name="form1" action="" method="get">
    <input type="button" name="btn1" value="Check Google Page" onClick="checkCorsSource('google')">
    <input type="button" name="btn1" value="Check Cors Page" onClick="checkCorsSource('cors')">
  </form>
  <p id="txt1" />
</body>
</html>


0
如果在尝试添加标头或设置控制策略时仍然无法实现,您可以考虑使用Apache ProxyPass...例如,在使用SSL的一个中添加以下两个指令:
SSLProxyEngine On
ProxyPass /oauth https://remote.tld/oauth

请确保以下 Apache 模块已加载(使用 a2enmod 命令进行加载):
  • proxy
  • proxy_connect
  • proxy_http
显然,您需要更改 AJAX 请求的 URL 以便使用 Apache 代理...

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