Como envio uma solicitação POST entre domínios via JavaScript?

568

Como envio uma solicitação POST entre domínios via JavaScript?

Notas - não deve atualizar a página e preciso pegar e analisar a resposta posteriormente.

Ido Schacham
fonte
Gostaria de saber um pouco sobre o caso de uso que permite tentar fazer isso. Poderia, por favor, contar algo sobre isso?
Mkeller
Basicamente, estou trabalhando em um script que precisa enviar algum texto de um arquivo HTML para outro servidor para processamento.
Ido Schacham
3
Você pode configurar um proxy que faça isso no lado do servidor e apenas forneça o resultado ao seu script? Ou precisa ser 100% JavaScript?
Sasha Chedygov

Respostas:

382

Atualização: Antes de continuar, todos devem ler e entender o tutorial html5rocks no CORS. É fácil de entender e muito claro.

Se você controlar o servidor que está sendo POSTADO, simplesmente aproveite o "Padrão de compartilhamento de recursos de origem cruzada" definindo cabeçalhos de resposta no servidor. Esta resposta é discutida em outras respostas neste tópico, mas não muito claramente na minha opinião.

Em resumo, aqui está como você realiza o POST entre domínios de.com/1.html a.com/postHere.php (usando o PHP como exemplo). Nota: você só precisa definir Access-Control-Allow-Originpara OPTIONSsolicitações NÃO - este exemplo sempre define todos os cabeçalhos para um trecho de código menor.

  1. Na configuração postHere.php, faça o seguinte:

    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;
    }

    Isso permite que seu script faça POST, GET e OPTIONS entre domínios. Isso ficará claro à medida que você continuar lendo ...

  2. Configure seu POST entre domínios a partir do JS (exemplo do 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.');
        }
    });

Quando você faz o POST na etapa 2, seu navegador envia um método "OPTIONS" para o servidor. Este é um "sniff" do navegador para ver se o servidor é legal quando você o publica. O servidor responde com um "Acesso-controle-permitir-origem" informando ao navegador que está OK para POST | GET | ORIGIN se a solicitação tiver origem em " http://from.com " ou " https://from.com ". Como o servidor está de acordo, o navegador fará uma segunda solicitação (desta vez um POST). É uma boa prática que seu cliente defina o tipo de conteúdo que está enviando - portanto, você precisará permitir isso também.

O MDN tem uma excelente descrição sobre o controle de acesso HTTP , que detalha como todo o fluxo funciona. De acordo com seus documentos, ele deve "funcionar em navegadores que suportam XMLHttpRequest entre sites". Este é um pouco enganador, no entanto, como I PENSAR únicos navegadores modernos permitem POST domínios. Eu apenas verifiquei que isso funciona com safari, chrome, FF 3.6.

Lembre-se do seguinte se você fizer isso:

  1. Seu servidor terá que lidar com 2 solicitações por operação
  2. Você terá que pensar nas implicações de segurança. Tenha cuidado antes de fazer algo como 'Access-Control-Allow-Origin: *'
  3. Isso não funciona em navegadores móveis. Na minha experiência, eles não permitem POST entre domínios. Eu testei android, iPad, iPhone
  4. Existe um bug bastante grande no FF <3.6, em que se o servidor retornar um código de resposta que não seja 400 E houver um corpo de resposta (erros de validação, por exemplo), o FF 3.6 não obterá o corpo de resposta. Isso é uma grande dor de cabeça, já que você não pode usar boas práticas de REST. Veja o bug aqui (está arquivado no jQuery, mas acho que é um bug do FF - parece estar corrigido no FF4).
  5. Sempre retorne os cabeçalhos acima, não apenas nas solicitações OPTION. O FF precisa disso na resposta do POST.
rynop
fonte
Ele pode retornar html, por exemplo? Eu preciso voltar html e algo não está funcionando ...
denis_n
Sim, você deveria ser capaz. Nunca tentei tho. Seu servidor retornando 200? Seu servidor também está retornando os cabeçalhos nas solicitações OPTIONs AND POST? Atualizei minha resposta com mais detalhes sobre isso. Verifique se o servidor também está respondendo com o cabeçalho do tipo de conteúdo correto (como text / html). Minha recomendação é usar o google chrome, clique com o botão direito do mouse na página> inspecionar elemento. Clique na guia rede e observe o POST e a resposta. Deve fornecer informações sobre o que está errado.
Rynop #
Eu tentei isso, mas ainda assim, 400 Bad Requesta OPTIONSpedido. e no firefoxsegundo pedido de POSTnunca é feito. :(
Zain Shaikh
Existe uma maneira de chamar sua máquina local na declaração de caso acima? Ou você apenas precisa usar o * neste caso para as origens permitidas.
Todd Vance
1
isso foi editado pela última vez há 4 anos - funcionará agora em navegadores móveis?
frankpinto
121

Se você controla o servidor remoto, provavelmente deve usar o CORS, conforme descrito nesta resposta ; é suportado no IE8 e versões posteriores, e em todas as versões recentes do FF, GC e Safari. (Mas no IE8 e 9, o CORS não permitirá que você envie cookies na solicitação.)

Portanto, se você não controla o servidor remoto, ou se precisa dar suporte ao IE7, ou se precisa de cookies e precisa dar suporte ao IE8 / 9, provavelmente desejará usar uma técnica de iframe.

  1. Crie um iframe com um nome exclusivo. (os iframes usam um espaço de nome global para todo o navegador, escolha um nome que nenhum outro site usará.)
  2. Construa um formulário com entradas ocultas, visando o iframe.
  3. Envie o formulário.

Aqui está o código de exemplo; Eu testei no 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();
}

Cuidado! Você não poderá ler diretamente a resposta do POST, pois o iframe existe em um domínio separado. Os quadros não podem se comunicar entre si de domínios diferentes; esta é a política de mesma origem .

Se você controla o servidor remoto, mas não pode usar o CORS (por exemplo, porque você está no IE8 / IE9 e precisa usar cookies), existem maneiras de contornar a política de mesma origem, por exemplo, usando window.postMessagee / ou uma de várias bibliotecas que permite enviar mensagens entre quadros entre domínios em navegadores antigos:

Se você não controlar o servidor remoto, não poderá ler a resposta do POST, ponto final. Caso contrário, causaria problemas de segurança.

Dan Fabulich
fonte
2
Você precisará definir form.target como algo, caso contrário, o navegador sairá do site para o URL de ação do formulário. Além disso, a string precisa ser única; se houver outros quadros ou janelas usando o mesmo nome, o formulário poderá ser postado nessa janela em vez do seu iframe. Mas quão único tem que ser? Provavelmente não muito. As chances de derrotar são bem pequenas. encolher de ombros
Dan Fabulich
1
@Nawaz Como eu disse na minha resposta, você terá que fazer uma comunicação entre domínios para obter o resultado em sua página da web. Requer que você controle o servidor da Web remoto para poder modificar sua resposta para permitir a comunicação com sua página da Web. (Por um lado, o servidor precisará responder com HTML; se o servidor responder com XML bruto, não poderá realizar comunicação entre quadros.)
Dan Fabulich
1
+1 - esta é a melhor solução que encontrei se você não tiver acesso ao servidor
James Long
1
@VojtechB Não, isso seria uma falha de segurança.
Dan Fabulich
1
@ Andrus Você pode ler o resultado do POST, mas somente se você controlar o servidor! Veja nessa resposta: "faça X no remetente [cliente], faça Y no destinatário [servidor]". Se você não controla o receptor / servidor, não pode fazer Y e não pode ler o resultado do POST.
Dan Fabulich
48
  1. Crie um iFrame,
  2. coloque um formulário nele com entradas ocultas,
  3. defina a ação do formulário para o URL,
  4. Adicionar iframe ao documento
  5. envie o formulário

Pseudo-código

 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();

Você provavelmente deseja estilizar o iframe, ficar oculto e absolutamente posicionado. Não há certeza de que a postagem entre sites será permitida pelo navegador, mas, nesse caso, é assim que se faz.

Lou Franco
fonte
4
Na verdade, isso é um pouco impreciso, pois ifr.appendChild (frm); não funciona. o iframe é uma referência a um objeto de janela e o método appendChild não existe para ele. Você precisará pegar o nó do documento no iframe primeiro. Isso requer a detecção de recursos para funcionar nos navegadores.
Rakesh Pai
Obrigado. Encontrado esses links úteis sobre o assunto: bindzus.wordpress.com/2007/12/24/... developer.apple.com/internet/webcontent/iframe.html
Ido Schacham
19
Problema! A resposta recebida no iframe está em um domínio diferente; portanto, a janela principal não tem acesso a ele, nem o iframe tem acesso à janela principal. Portanto, essa solução parece boa para o POST, mas você não pode analisar a resposta posteriormente :(
Ido Schacham 30/11/08
2
Tente definir uma carga na tag body da resposta para uma função JavaScript que chama uma função no pai com a string de resposta.
Lou Franco
Essa resposta não funcionou para mim; Eu postei minha própria variação abaixo.
Dan Fabulich
24

Mantenha simples:

  1. POST entre domínios:
    usecrossDomain: true,

  2. não deve atualizar a página:
    Não, não atualizará a página, pois oretorno de chamada assíncronosuccessouerrorassíncrono será chamado quando o servidor enviar a resposta.


Script de exemplo:

$.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
        }
    });
Alain Gauthier
fonte
8
crossDomain: trueestranhamente não tem absolutamente nada a ver com solicitações reais entre domínios. Se a solicitação for de domínio cruzado, o jquery definirá isso como verdadeiro automaticamente.
Kevin B
16

Se você tiver acesso a todos os servidores envolvidos, coloque o seguinte no cabeçalho da resposta da página solicitada no outro domínio:

PHP:

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

Por exemplo, no código xmlrpc.php do Drupal, você faria o seguinte:

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;
}

Isso provavelmente cria um problema de segurança e você deve tomar as medidas adequadas para verificar a solicitação.

Robb Lovell
fonte
6
  1. Crie dois iframes ocultos (adicione "display: none;" ao estilo css). Faça o seu segundo iframe apontar para algo em seu próprio domínio.

  2. Crie um formulário oculto, defina seu método como "postar" com target = seu primeiro iframe e, opcionalmente, defina enctype como "multipart / form-data" (acho que você deseja fazer o POST porque deseja enviar dados multipartes como imagens ?)

  3. Quando estiver pronto, faça o formulário submit () o POST.

  4. Se você conseguir que o outro domínio retorne o javascript que fará a comunicação entre domínios com iframes ( http://softwareas.com/cross-domain-communication-with-iframes ), você estará com sorte e poderá capturar a resposta também.

Obviamente, se você quiser usar seu servidor como proxy, poderá evitar tudo isso. Simplesmente envie o formulário ao seu próprio servidor, que fará proxy da solicitação para o outro servidor (supondo que o outro servidor não esteja configurado para observar discrepâncias de IP), obtenha a resposta e retorne o que quiser.

Magarshak
fonte
6

Mais uma coisa importante a ser observada !!! No exemplo acima, está descrito como usar

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

O JQuery 1.6 e inferior tem um erro com o XHR entre domínios. De acordo com o Firebug, nenhum pedido, exceto OPÇÕES, foi enviado. Sem POST. Em absoluto.

Passei 5 horas testando / ajustando meu código. Adicionando muitos cabeçalhos no servidor remoto (script). Sem nenhum efeito. Porém, mais tarde, atualizei a JQuery lib para a 1.6.4 e tudo funciona como um encanto.

BasTaller
fonte
Whoopps, não no Opera 10.61. Minha decisão final para fazer isso foi usar o proxy PHP no meu domínio.
precisa saber é o seguinte
Como você usou o proxy PHP? Você pode me guiar nisso?
Zoran777
veja as respostas abaixo, por exemplo, por Ivan Durst
BasTaller
5

Se você quiser fazer isso no ambiente ASP.net MVC com JQuery AJAX, siga estas etapas: (este é um resumo da solução oferecida neste segmento)

Suponha que "caller.com" (pode ser qualquer site) precise postar em "server.com" (um aplicativo ASP.net MVC)

  1. No Web.config do aplicativo "server.com", adicione a seguinte seção:

      <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. No "server.com", executaremos a seguinte ação no controlador (chamado "Início") no qual publicaremos:

    [HttpPost]
    public JsonResult Save()
    {
        //Handle the post data...
    
        return Json(
            new
            {
                IsSuccess = true
            });
    }
  3. Em seguida, no "caller.com", publique dados de um formulário (com o ID html "formId") em "server.com" da seguinte maneira:

    $.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
            }
        });
Sujeewa
fonte
4

Há mais uma maneira (usando o recurso html5). Você pode usar o iframe proxy hospedado nesse outro domínio, enviar uma mensagem usando postMessage para esse iframe, para que o iframe possa fazer a solicitação POST (no mesmo domínio) e o postMessage de volta com reposnse para a janela pai.

pai em 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");

iframe em reciver.com

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)
}
jcubic
fonte
Há perguntas relacionadas em stackoverflow.com/questions/38940932/… . É possível criar algum plugin ou função genérica com base em sua amostra?
Andrus
@Andrus talvez algo como este gist.github.com/jcubic/26f806800abae0db9a0dfccd88cf6f3c
jcubic
Este código requer a modificação da página do receptor. Como ler a resposta se as páginas do receptor não podem ser modificadas?
Andrus
@ Andrus, você não pode precisar acessar iframe recever.com para enviar solicitações de ajax para lá. Sem iframe, não haverá solicitações.
jcubic
3

Alto nível .... Você precisa ter uma configuração de cname no servidor para que other-serve.your-server.com aponte para other-server.com.

Sua página cria dinamicamente um iframe invisível, que atua como seu transporte para outro servidor.com. Você precisa se comunicar via JS da sua página para o outro servidor.com e ter retornos de chamada que retornam os dados à sua página.

Possível, mas requer coordenação entre o seu servidor e o servidor outro.


fonte
Nem sequer pensou em usar um CNAME para redirecionar. Boa decisão! Ainda tenho que tentar isso, mas suponho que o CNAME induza o navegador a pensar que está interagindo com o mesmo site? Vou usá-lo para postar no Amazon S3, então espero que funcione.
precisa saber é o seguinte
1
Não vejo como isso resolveria alguma coisa. atravessar para um subdomínio diferente tem os mesmos problemas que atravessar para um domínio diferente.
Octopus
2

Essa é uma pergunta antiga, mas alguma nova tecnologia pode ajudar alguém.

Se você tiver acesso administrativo ao outro servidor, poderá usar o projeto Forge de código aberto para realizar seu POST entre domínios. O Forge fornece um wrapper JavaScript XmlHttpRequest entre domínios que aproveita a API de soquete bruto do Flash. O POST pode até ser feito via TLS.

O motivo pelo qual você precisa de acesso administrativo ao servidor para o qual está postando é porque deve fornecer uma diretiva de domínio cruzado que permita o acesso do seu domínio.

http://github.com/digitalbazaar/forge

Dalongley
fonte
2

Sei que essa é uma pergunta antiga, mas queria compartilhar minha abordagem. Eu uso o cURL como proxy, muito fácil e consistente. Crie uma página php chamada submit.php e adicione o seguinte código:

<?

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));

Então, em seus js (jQuery aqui):

$.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.');
}
});
Ivan Durst
fonte
1

Deve ser possível com uma tabela personalizada YQL + JS XHR, consulte: http://developer.yahoo.com/yql/guide/index.html

Eu o uso para fazer raspagem html no lado do cliente (js), funciona bem (eu tenho um reprodutor de áudio completo, com pesquisa na internet / playlists / letras / últimas informações fm, todos os clientes js + YQL)

Guillaume86
fonte
1

CORS é para você. O CORS é "Compartilhamento de Recursos de Origem Cruzada", é uma maneira de enviar solicitações entre domínios. Agora, a API XMLHttpRequest2 e Fetch suportam o CORS e pode enviar solicitações POST e GET

O servidor precisa reivindicar o Access-Control-Allow-Origin e não pode ser definido como '*'.

E se você deseja que qualquer origem possa enviar uma solicitação para você, é necessário JSONP (também é necessário definir o Access-Control-Allow-Origin , mas pode ser '*')

Para muitas solicitações, se você não sabe escolher, acho que você precisa de um componente funcional completo para fazer isso. Deixe-me apresentar um componente simples https://github.com/Joker-Jelly/catta


Se você estiver usando um navegador moderno (> IE9, Chrome, FF, Edge etc.), recomendamos que você use um componente simples, mas bonito, https://github.com/Joker-Jelly/catta .Não tem dependência, Menos superior a 3 KB e suporta Fetch, AJAX e JSONP com a mesma sintaxe e opções de amostra mortais.

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

Ele também suporta todo o caminho para importar para o seu projeto, como o módulo ES6, CommonJS e até mesmo <script>em HTML.

Geléia
fonte
1

Se você tiver acesso ao servidor entre domínios e não quiser fazer alterações no código no servidor, poderá usar uma biblioteca chamada - 'xdomain'.

Como funciona:

Etapa 1: servidor 1: inclua a biblioteca xdomain e configure o domínio cruzado como escravo:

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

Etapa 2: no servidor entre domínios, crie um arquivo proxy.html e inclua o servidor 1 como mestre:

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

Etapa 3:

Agora, você pode fazer uma chamada AJAX para o proxy.html como ponto de extremidade do servidor1. Isso é ignorar a solicitação do CORS. A biblioteca usa internamente a solução iframe que funciona com credenciais e todos os métodos possíveis: GET, POST etc.

Consulta código 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)
Raj Malakpet
fonte