如何通过JavaScript发送跨域POST请求?


568

如何通过JavaScript发送跨域POST请求?

注意-它不应该刷新页面,之后我需要抓取并解析响应。


我想稍微了解一下使您可以尝试使用的用例。你能谈谈吗?
mkoeller

基本上,我正在处理一个脚本,该脚本需要将一些文本从HTML文件发送到另一台服务器进行处理。
伊多·沙卡姆

3
您可以设置一个在服务器端执行此操作的代理,并仅将脚本结果提供给您吗?还是需要100%使用JavaScript?
2009年

Answers:


382

更新:在继续之前,每个人都应该阅读和理解CORS上的html5rocks教程。这很容易理解,也很清楚。

如果您控制要发布的服务器,只需在服务器上设置响应头即可利用“跨域资源共享标准”。在该主题的其他答案中讨论了这个答案,但我认为不是很清楚。

简而言之,这里是如何完成从from.com/1.html到to.com/postHere.php的跨域POST(以PHP为例)。注意:您只需要设置Access-Control-Allow-OriginNON OPTIONS请求-此示例始终为较小的代码段设置所有标头。

  1. 在postHere.php中,设置以下内容:

    switch ($_SERVER['HTTP_ORIGIN']) {
        case 'http://from.com': case 'https://from.com':
        header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);
        header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS');
        header('Access-Control-Max-Age: 1000');
        header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
        break;
    }
    

    这使您的脚本可以进行跨域的POST,GET和OPTIONS。当您继续阅读时,这将变得很清楚...

  2. 从JS设置跨域POST(jQuery示例):

    $.ajax({
        type: 'POST',
        url: 'https://to.com/postHere.php',
        crossDomain: true,
        data: '{"some":"json"}',
        dataType: 'json',
        success: function(responseData, textStatus, jqXHR) {
            var value = responseData.someKey;
        },
        error: function (responseData, textStatus, errorThrown) {
            alert('POST failed.');
        }
    });
    

在步骤2中执行POST时,浏览器将向服务器发送“ OPTIONS”方法。这是浏览器的“嗅探”,以查看服务器是否在您发布时保持冷静。如果请求来自“ http://from.com ”或“ https://from.com ” ,服务器将以“ Access-Control-Allow-Origin”响应,告知浏览器可以执行POST | GET | ORIGIN 。由于服务器可以使用,因此浏览器将发出第二个请求(这次是POST)。好的做法是让客户端设置要发送的内容类型-因此您也需要允许它。

MDN撰写了大量有关HTTP访问控制的文章,其中详细介绍了整个流程的工作方式。根据他们的文档,它应该“在支持跨站点XMLHttpRequest的浏览器中工作”。但是,这有点误导,因为我认为只有现代浏览器才允许跨域POST。我仅使用Safari,chrome,FF 3.6验证了此功能。

如果这样做,请记住以下几点:

  1. 您的服务器每个操作将必须处理2个请求
  2. 您将不得不考虑安全隐患。在执行“访问控制允许来源:*”之类的操作之前,请务必小心。
  3. 这不适用于移动浏览器。以我的经验,它们根本不允许跨域POST。我已经测试了android,iPad,iPhone
  4. FF <3.6中存在一个很大的错误,如果服务器返回非400响应代码并且存在响应正文(例如验证错误),则FF 3.6不会获得响应正文。这是一个巨大的麻烦,因为您不能使用良好的REST实践。在此处查看错误(它已归档在jQuery下,但我猜是它的FF错误-似乎已在FF4中修复)。
  5. 始终返回上面的标头,而不仅仅是返回OPTION请求。FF在POST的响应中需要它。

例如,它可以返回html吗?我需要返回html,但某些操作不起作用...
denis_n 2011年

是的,您应该能够。从来没有尝试过。您的服务器返回200?您的服务器是否还在OPTIONS AND POST请求上返回标头?我已经更新了我的答案,并对此有更多详细信息。确保您的服务器也以正确的内容类型标题(例如text / html)进行响应。我的建议是使用谷歌浏览器,右键单击页面>检查元素。单击网络选项卡,然后查看POST和响应。应该给你有关出了什么问题的信息。
rynop 2011年

我已经尝试过了,但是仍然可以400 Bad Request提出OPTIONS要求。而在firefox的第二个请求中POST从未提出。:(
Zain Shaikh

有没有一种方法可以在上面的case语句中标注本地计算机?或者在这种情况下,您只需要使用*作为允许来源。
Todd Vance 2012年

1
这是4年前最后一次编辑-现在可以在移动浏览器上使用吗?
frankpinto

121

如果您控制远程服务器,则可能应该使用CORS,如本答案中所述;IE8及更高版本以及FF,GC和Safari的所有最新版本均支持该功能。(但是在IE8和9中,CORS不允许您在请求中发送Cookie。)

因此,如果您控制远程服务器,或者必须支持IE7,或者需要Cookie并且必须支持IE8 / 9,则可能要使用iframe技术。

  1. 创建具有唯一名称的iframe。(iframe在整个浏览器中都使用全局名称空间,因此请选择一个其他网站都不会使用的名称。)
  2. 使用iframe构造带有隐藏输入的表单。
  3. 提交表格。

这是示例代码;我在IE6,IE7,IE8,IE9,FF4,GC11,S5上进行了测试。

function crossDomainPost() {
  // Add the iframe with a unique name
  var iframe = document.createElement("iframe");
  var uniqueString = "CHANGE_THIS_TO_SOME_UNIQUE_STRING";
  document.body.appendChild(iframe);
  iframe.style.display = "none";
  iframe.contentWindow.name = uniqueString;

  // construct a form with hidden inputs, targeting the iframe
  var form = document.createElement("form");
  form.target = uniqueString;
  form.action = "http://INSERT_YOUR_URL_HERE";
  form.method = "POST";

  // repeat for each parameter
  var input = document.createElement("input");
  input.type = "hidden";
  input.name = "INSERT_YOUR_PARAMETER_NAME_HERE";
  input.value = "INSERT_YOUR_PARAMETER_VALUE_HERE";
  form.appendChild(input);

  document.body.appendChild(form);
  form.submit();
}

谨防!由于iframe位于单独的域中,因此您将无法直接读取POST的响应。不允许框架从不同的域相互通信;这是同源政策

如果您控制远程服务器但不能使用CORS(例如,因为您使用的是IE8 / IE9,并且需要使用Cookie),则可以使用多种方法来解决同源策略,例如通过使用window.postMessage和/或允许您在较旧的浏览器中发送跨域跨框架消息的众多库之一:

如果您不控制远程服务器,那么您将无法读取POST周期的响应。否则会引起安全问题。


2
您需要将form.target设置为其他内容,否则浏览器将从您的站点导航到表单操作URL。此外,字符串必须唯一。如果还有其他使用相同名称的框架或窗口,则表单可能会发布到该窗口而不是您的iframe。但是它必须有多独特?可能不是很好。破坏的可能性很小。耸耸肩
Dan Fabulich 2012年

1
@Nawaz正如我在回答中说的那样,您必须进行跨域跨框架通信才能在网页中获得结果。它要求您控制远程Web服务器,以便您可以修改其响应以允许与您的网页进行通信。(一方面,服务器需要使用HTML进行回复;如果服务器使用原始XML进行回复,则无法进行跨框架通信。)
Dan Fabulich 2012年

1
+1-如果您无法访问服务器,这是我找到的最佳解决方案
James Long

1
@VojtechB不,那是安全漏洞。
Dan Fabulich 2014年

1
@Andrus您可以读取POST的结果,但前提是您控制服务器!看到该答案,它说“在发送者[客户端]上执行X,在接收者[服务器]上执行Y”。如果您不控制接收器/服务器,则无法执行Y,因此无法读取POST的结果。
Dan Fabulich

48
  1. 创建一个iFrame,
  2. 使用隐藏的输入将表格放入其中,
  3. 将表单的操作设置为URL,
  4. 将iframe添加到文档
  5. 提交表格

伪码

 var ifr = document.createElement('iframe');
 var frm = document.createElement('form');
 frm.setAttribute("action", "yoururl");
 frm.setAttribute("method", "post");

 // create hidden inputs, add them
 // not shown, but similar (create, setAttribute, appendChild)

 ifr.appendChild(frm);
 document.body.appendChild(ifr);
 frm.submit();

您可能需要设置iframe的样式,使其处于隐藏状态并绝对定位。不确定浏览器是否允许跨站点发布,但是如果是这样,那就是这样做的方法。


4
实际上,这有点不准确,因为ifr.appendChild(frm); 不管用。iframe是对窗口对象的引用,并且不存在appendChild方法。您需要首先在iframe中获取文档节点。这需要功能检测才能跨浏览器工作。
Rakesh Pai


19
问题!iframe中收到的响应位于不同的域,因此主窗口无权访问它,iframe也无权访问主窗口。因此,此解决方案似乎仅适合进行POST,但您之后无法解析响应:(
Ido Schacham

2
尝试在响应的body标签中设置对JavaScript函数的onload,该JavaScript函数使用响应字符串在父级中调用函数。
Lou Franco

这个答案对我不起作用。我在下面发布了自己的版本。
Dan Fabulich 2011年

24

把事情简单化:

  1. 跨域POST:
    使用crossDomain: true,

  2. 不应该刷新页面:
    不,它不会刷新页面,因为服务器发送回响应时将调用successerror异步回调。


示例脚本:

$.ajax({
        type: "POST",
        url: "http://www.yoururl.com/",
        crossDomain: true,
        data: 'param1=value1&param2=value2',
        success: function (data) {
            // do something with server response data
        },
        error: function (err) {
            // handle your error logic here
        }
    });

8
crossDomain: true奇怪的是,与真正的跨域请求绝对无关。如果请求是跨域的,则jquery会自动将其设置为true。
凯文B

16

如果您有权访问所有涉及的服务器,请将以下内容放入其他域中所请求页面的答复标题中:

PHP:

header('Access-Control-Allow-Origin: *');

例如,在Drupal的xmlrpc.php代码中,您可以这样做:

function xmlrpc_server_output($xml) {
    $xml = '<?xml version="1.0"?>'."\n". $xml;
    header('Connection: close');
    header('Content-Length: '. strlen($xml));
    header('Access-Control-Allow-Origin: *');
    header('Content-Type: application/x-www-form-urlencoded');
    header('Date: '. date('r'));
    // $xml = str_replace("\n", " ", $xml); 

    echo $xml;
    exit;
}

这可能会导致安全问题,因此您应确保采取适当的措施来验证请求。



6
  1. 创建两个隐藏的iframe(在CSS样式中添加“ display:none;”)。使第二个iframe指向您自己域中的某个内容。

  2. 创建一个隐藏的表单,将其方法设置为target =您的第一个iframe进行“发布”,还可以选择将enctype设置为“ multipart / form-data”(我想您要进行POST,因为您想发送图片等分段数据?)

  3. 准备好后,将表单Submit()设为POST。

  4. 如果您可以让另一个域返回将与iframe进行跨域通信的JavaScript(http://softwareas.com/cross-domain-communication-with-iframes),那么您很幸运,可以捕获响应也一样

当然,如果要将服务器用作代理,则可以避免所有这些情况。只需将表单提交到您自己的服务器,该服务器会将请求代理到另一台服务器(假设未将另一台服务器设置为注意到IP差异),获取响应并返回您想要的任何内容。


6

还有一件重要的事情要注意!!!在上面的示例中,描述了如何使用

$.ajax({
    type     : 'POST',
    dataType : 'json', 
    url      : 'another-remote-server',
    ...
});

JQuery 1.6及更低版本存在跨域XHR的错误。据Firebug称,除了OPTIONS之外,没有其他请求被发送。没有开机自检。完全没有

花了5个小时测试/调整我的代码。在远程服务器上添加大量标头(脚本)。没有任何效果。但是后来,我将JQuery lib更新为1.6.4,所有内容都像一个魅力。


糟糕,而不是Opera 10.61中的。我最终的决定是在域上使用PHP代理。
BasTaller 2012年

您是如何使用PHP代理的?你能指导我吗?
Zoran777

请参阅下面的答案,例如Ivan Durst
BasTaller

5

如果要在具有JQuery AJAX的ASP.net MVC环境中执行此操作,请按照下列步骤操作:(这是线程提供的解决方案的摘要)

假设“ caller.com”(可以是任何网站)都需要发布到“ server.com”(ASP.net MVC应用程序)

  1. 在“ server.com”应用程序的Web.config上,添加以下部分:

      <httpProtocol>
          <customHeaders>
              <add name="Access-Control-Allow-Origin" value="*" />
              <add name="Access-Control-Allow-Headers" value="Content-Type" />
              <add name="Access-Control-Allow-Methods" value="POST, GET, OPTIONS" />
          </customHeaders>
      </httpProtocol>
  2. 在“ server.com”上,我们将对要发布到的控制器(称为“ Home”)执行以下操作:

    [HttpPost]
    public JsonResult Save()
    {
        //Handle the post data...
    
        return Json(
            new
            {
                IsSuccess = true
            });
    }
  3. 然后从“ caller.com”将数据从表单(具有html id“ formId”)发送到“ server.com”,如下所示:

    $.ajax({
            type: "POST",
            url: "http://www.server.com/home/save",
            dataType: 'json',
            crossDomain: true,
            data: $(formId).serialize(),
            success: function (jsonResult) {
               //do what ever with the reply
            },
            error: function (jqXHR, textStatus) {
                //handle error
            }
        });

4

还有另一种方法(使用html5功能)。您可以使用托管在该其他域上的代理iframe,然后使用postMessage将消息发送到该iframe,然后该iframe可以执行POST请求(在同一域上),并以回购的方式将postMessage返回到父窗口。

sender.com上的父级

var win = $('iframe')[0].contentWindow

function get(event) {
    if (event.origin === "http://reciver.com") {
        // event.data is response from POST
    }
}

if (window.addEventListener){
    addEventListener("message", get, false)
} else {
    attachEvent("onmessage", get)
}
win.postMessage(JSON.stringify({url: "URL", data: {}}),"http://reciver.com");

在reciver.com上的iframe

function listener(event) {
    if (event.origin === "http://sender.com") {
        var data = JSON.parse(event.data);
        $.post(data.url, data.data, function(reponse) {
            window.parent.postMessage(reponse, "*");
        });
    }
}
// don't know if we can use jQuery here
if (window.addEventListener){
    addEventListener("message", listener, false)
} else {
    attachEvent("onmessage", listener)
}

stackoverflow.com/questions/38940932/…中有相关的问题。是否可以根据您的示例创建一些插件或通用函数?
安德鲁斯


此代码需要修改接收者页面。如果接收者页面无法修改,如何读取响应?
Andrus

@Andrus,您不需要访问recever.com iframe即可在那里发送ajax请求。没有iframe,就不会有任何请求。
jcubic

3

高级...。您需要在服务器上进行cname设置,以便other-serve.your-server.com指向other-server.com。

您的页面动态创建了一个不可见的iframe,该iframe充当您到other-server.com的传输。然后,您必须通过JS从您的页面与other-server.com进行通信,并具有将数据返回到页面的回调。

可能,但需要your-server.com和other-server.com的协调


甚至没有想到使用CNAME进行重定向。好决定!我还没有尝试过,但是我假设CNAME会欺骗浏览器,使其认为它与同一个站点进行交互?我将使用它发布到Amazon S3,所以我希望它能起作用。
SpencerElliott

1
我看不出这将如何解决任何问题。跨入另一个子域与跨入另一个域具有相同的问题。
Octopus


2

这是一个老问题,但是某些新技术可能会帮助某人。

如果您具有对另一台服务器的管理访问权限,则可以使用开源Forge项目来完成跨域POST。Forge提供了一个跨域JavaScript XmlHttpRequest包装器,该包装器利用了Flash的原始套接字API。POST甚至可以通过TLS完成。

您需要对要发布到的服务器进行管理访问的原因是,您必须提供允许从您的域进行访问的跨域策略。

http://github.com/digitalbazaar/forge


2

我知道这是一个老问题,但是我想分享我的方法。我使用cURL作为代理,非常简单且一致。创建一个名为Submit.php的php页面,并添加以下代码:

<?

function post($url, $data) {
$header = array("User-Agent: " . $_SERVER["HTTP_USER_AGENT"], "Content-Type: application/x-www-form-urlencoded");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}

$url = "your cross domain request here";
$data = $_SERVER["QUERY_STRING"];
echo(post($url, $data));

然后,在您的js(此处为jQuery)中:

$.ajax({
type: 'POST',
url: 'submit.php',
crossDomain: true,
data: '{"some":"json"}',
dataType: 'json',
success: function(responseData, textStatus, jqXHR) {
    var value = responseData.someKey;
},
error: function (responseData, textStatus, errorThrown) {
    alert('POST failed.');
}
});

1

使用YQL自定义表+ JS XHR应该可以,请查看:http : //developer.yahoo.com/yql/guide/index.html

我用它来做一些客户端(js)html抓取,工作正常(我有一个完整的音频播放器,可以搜索互联网/播放列表/歌词/最新的FM信息,所有客户端js + YQL)


1

CORS适合您。CORS是“跨源资源共享”,是一种发送跨域请求的方法。现在XMLHttpRequest2和Fetch API都支持CORS,并且可以发送POST和GET请求

但是它有其局限性。服务器需要明确声明Access-Control-Allow-Origin,并且不能将其设置为'*'。

而且,如果您希望任何来源都可以向您发送请求,则需要JSONP(还需要设置Access-Control-Allow-Origin,但可以为'*')

对于很多不知道如何选择的请求方式,我认为您需要一个功能完整的组件来完成。让我介绍一个简单的组件https://github.com/Joker-Jelly/catta


如果您使用的是现代浏览器(> IE9,Chrome,FF,Edge等),非常推荐您使用简单但美观的组件https://github.com/Joker-Jelly/catta。它没有依赖性,更少大小超过3KB,并以相同的致命示例语法和选项支持Fetch,AJAX和JSONP。

catta('./data/simple.json').then(function (res) {
  console.log(res);
});

它还支持所有导入到项目的方式,例如ES6模块,CommonJS甚至<script>HTML。


1

如果您可以访问跨域服务器,并且不想在服务器端进行任何代码更改,则可以使用一个名为-'xdomain'的库。

怎么运行的:

步骤1:服务器1:包括xdomain库并将跨域配置为从属:

<script src="js/xdomain.min.js" slave="https://crossdomain_server/proxy.html"></script>

步骤2:在跨域服务器上,创建proxy.html文件,并将服务器1作为主服务器:

proxy.html:
<!DOCTYPE HTML>
<script src="js/xdomain.min.js"></script>
<script>
  xdomain.masters({
    "https://server1" : '*'
  });
</script>

第三步:

现在,您可以作为server1的端点对proxy.html进行AJAX调用。这是绕过CORS要求。该库在内部使用iframe解决方案,该解决方案可用于凭据和所有可能的方法:GET,POST等。

查询ajax代码:

$.ajax({
        url: 'https://crossdomain_server/proxy.html',
        type: "POST",
        data: JSON.stringify(_data),
        dataType: "json",
        contentType: "application/json; charset=utf-8"
    })
    .done(_success)
    .fail(_failed)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.