跨域AJAX请求无法工作

13

我正在通过jQuery的$.ajax函数调用第三方API的POST方法,但是当我发起请求时,出现以下错误:

XMLHttpRequest无法加载http://the-url.com。该请求已重定向到需要预检的跨域请求“http://the-url.com/anotherlocation”,这是不允许的。

我从这篇文章中了解到这可能是Webkit的一个bug,所以我在Firefox中尝试了一下(我是在Chrome中进行开发),结果得到了相同的结果。我也在Chrome和Firefox上尝试了这个请求,但是得到了相同的结果。

根据这篇文章,我还尝试将$.ajax函数的crossDomain属性设置为true并将dataType设置为jsonp,但是这导致了500个内部服务器错误。

当我使用--disable-web-security标志启动Chrome浏览器时,就没有任何问题。但是,如果我正常启动浏览器,就会出现错误。

因此,我想这可能是一个由两个部分组成的问题。我应该怎样才能进行跨域请求呢?如果JSONP是答案,那么如何确定第三方API是否设置正确以支持此操作?

编辑:这是使用禁用浏览器安全性的方法进行调用时的屏幕截图:https://drive.google.com/file/d/0Bzo7loNBQcmjUjk5YWNWLXM2SVE/edit?usp=sharing

这是启用浏览器安全功能(像正常情况一样)时的通话屏幕截图:https://drive.google.com/file/d/0Bzo7loNBQcmjam5NQ3BKWUluRE0/edit?usp=sharing
3个回答

20

针对你的第一个问题,

我该怎么做才能进行跨域请求?

首先向服务器发送以下请求头。当响应头不匹配时,即使有返回结果,UserAgent(浏览器)也会丢弃它并不将其回传给XHR回调。

Origin: http://yourdomain.com Access-Control-Request-Method: POST Access-Control-Request-Headers: X-Custom-Header

然后,你的服务器应当响应以下头:

Access-Control-Allow-Origin: http://yourdomain.com Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: X-Custom-Header

示例来源于 - https://dev59.com/Nmoy5IYBdhLWcg3wPLr0#8689332

你可以使用 Fiddler, Web Inspector 网络标签(Chrome)或 Firebug 的网络标签来查找服务器响应请求时发送的头信息。

第三方API服务器URL应该返回支持的值,如果没有,则这是你的问题。

JsonP 500服务器错误无法提供太多帮助,因为它表示内部服务器错误


更新

我看到了你的截图,以下几点需要注意:

  1. 使用了HTTP POST
  2. 两种模式的响应中均未发现Access-Control头。第二个状态码为302
  3. Origin 请求头为 null,因此我认为 HTML 文件是从文件系统而不是从网站打开的。

启用JSONP

为什么JSONP不起作用呢?原因是在指定的ASMX的Web服务配置中,请求没有启用GET模式。JSONP不能使用POST

为什么禁用安全性后会返回200

直接调用POST请求,没有触发OPTIONS或预检请求,所以服务器直接响应回来。

为什么启用安全性后会返回302状态码?

首先讲一下当未禁用安全标志时的情况。向URL发送一个OPTIONS请求,URL应该响应一个可以使用的HTTP方法列表,例如GETPOST等等。这个OPTIONS请求的状态码应该是200

在当前情况下,进行此操作时会发生重定向。这个重定向指向了一个自定义的HTTP服务器错误处理程序。我认为它是404错误,并被捕获并重定向。(可能是因为自定义处理程序设置为ResponseRedirect而不是ResponseRewrite)。404的原因是跨域访问未启用。

如果关闭自定义错误处理,则会返回HTTP 500代码。我认为可以通过在跨域请求后立即检查服务器日志来找到问题所在。如果有其他错误,请检查服务器日志。

可能的解决方案

为了启用访问,有两种方法- 详见此处。或者直接将访问控制标头添加到web.config文件的customheaders部分。

当启用跨域访问时,服务器应该响应 OPTIONS,并允许请求通过。这应该返回 HTTP 200,除非有其他错误。但是 AJAX 回调将无法访问响应。只能在设置 Access-Control-Allow-Origin 为 "*" 或 Origin 请求头值以及必要的 Access-Control 的情况下进行。然后 AJAX 回调将按预期接收响应。
第三点是 null Origin。如果 Origin 请求头值作为 Access-Control-Allow-Origin 返回,这将是一个问题。但我假设您将把 HTML 页面移动到 Web 服务器上,因此不应该是个问题。
希望这可以帮助!

1
谢谢。这很有帮助。我查看了Web Inspector中的网络调用,但是我没有看到必要的“Access-Control”标头。我想那可能是问题所在。如果标头存在,它们会显示在Web Inspector的网络选项卡中,对吗? - Don
2
没错,你可以在网络选项卡上尝试从CDN下载脚本的HTML页面,例如:http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js同样,第三方API应根据需要启用访问控制标头。 - aravind
好的,我明白你在说什么,我正在考虑联系API提供商,看看为什么他们不支持JSONP。我会在问题中附上网络选项卡的截图,请你看一下并告诉我你的想法,可以吗? - Don
当然没问题!这会有助于更好地理解问题。 - aravind
1
我看了你的答案,但不幸的是,我没有访问服务器的权限。因此,我想出的解决方案与@waki发布的类似,但稍作修改以支持SOAP。再次感谢你的帮助! - Don
显示剩余3条评论

3
我想到的解决方案是使用cURL(正如@waki所提到的),但稍微修改了一下,以支持SOAP。然后,不是像之前那样调用第三方API(因为它的配置有误),而是调用我的本地PHP文件,该文件再调用第三方API并将数据传回我的PHP文件中进行处理。这使我可以忘记CORS及其相关的所有复杂性。以下是代码(取自此问题并作出了修改,但不包括身份验证部分)。
$post_data = "Some xml here";
$soapUrl = "http://yoursite.com/soap.asmx"; // asmx URL of WSDL


$headers = array(
    "Content-type: text/xml;charset=\"utf-8\"",
    "Accept: text/xml",
    "Cache-Control: no-cache",
    "Pragma: no-cache",
    "SOAPAction: http://yoursite.com/SOAPAction",
    "Content-length: " . strlen($post_data),
); //SOAPAction: your op URL

$url = $soapUrl;

// PHP cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); // the SOAP request
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$response = curl_exec($ch);

/* Check for an error when processing the request. */
if(curl_errno($ch) != 0) {
   // TODO handle the error
}

curl_close($ch);

// TODO Parse and process the $response variable (returned as XML)

这样做是可行的,但我会称其为“变通方法”,而不是解决方案。有些人在某些情况下只能依赖前端,无法使用后端代理的选项。 - Yann
是的,我希望有更好的解决方案。然而,由于安全限制和无法控制服务器,通过我控制的服务器代理它是我找到的最佳解决方案。 - Don

1
你可以使用 cURL 吗? 通过 Ajax,你可以将数据发送到处理程序(在你的服务器上,让我们称之为 callback.php)。 在你的文件(callback.php)中,使用 cURL 处理你的数据:
$post_data = array( 
    'key' => 'key', 
    'test' => 'text'
    ); 

$curl = curl_init(); 
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); 
curl_setopt($curl, CURLOPT_POST, TRUE); 
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);  
curl_setopt($curl, CURLOPT_URL, 'http://www.domain.com/'); 
$return = json_decode(trim(curl_exec($curl)), TRUE); 
curl_close($curl); 

在你的 $return 变量中,你会得到你的数据。

1
非常好的建议,谢谢!我实际上采用了一个稍微修改过的版本,支持SOAP。 - Don

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