Comment envoyer une demande POST interdomaine via JavaScript?


568

Comment envoyer une demande POST interdomaine via JavaScript?

Notes - cela ne devrait pas rafraîchir la page, et j'ai besoin de récupérer et d'analyser la réponse par la suite.


J'aimerais en savoir un peu plus sur le cas d'utilisation qui vous permet d'essayer de le faire. Pourriez-vous s'il vous plaît en dire quelque chose?
mkoeller

Fondamentalement, je travaille sur un script qui doit envoyer du texte d'un fichier HTML à un autre serveur pour traitement.
Ido Schacham

3
Pouvez-vous configurer un proxy qui le fait côté serveur et donne simplement le résultat à votre script? Ou doit-il être 100% JavaScript?
Sasha Chedygov

Réponses:


382

Mise à jour: Avant de continuer, tout le monde doit lire et comprendre le tutoriel html5rocks sur CORS . Il est facile à comprendre et très clair.

Si vous contrôlez le serveur en cours de POST, tirez simplement parti de la «norme de partage des ressources d'origine croisée» en définissant des en-têtes de réponse sur le serveur. Cette réponse est discutée dans d'autres réponses de ce fil, mais pas très clairement à mon avis.

En bref, voici comment vous pouvez effectuer le POST interdomaine de from.com/1.html à to.com/postHere.php (en utilisant PHP comme exemple). Remarque: vous devez uniquement définir Access-Control-Allow-Originpour les OPTIONSdemandes NON - cet exemple définit toujours tous les en-têtes pour un extrait de code plus petit.

  1. Dans postHere.php, configurez les éléments suivants:

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

    Cela permet à votre script de créer des POST, GET et OPTIONS entre domaines. Cela deviendra clair à mesure que vous continuerez à lire ...

  2. Configurez votre POST interdomaine à partir de JS (exemple 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.');
        }
    });

Lorsque vous effectuez le POST à ​​l'étape 2, votre navigateur envoie une méthode "OPTIONS" au serveur. Ceci est un "sniff" par le navigateur pour voir si le serveur est cool avec vous en y postant. Le serveur répond avec un "Access-Control-Allow-Origin" indiquant au navigateur qu'il est d'accord pour POST | GET | ORIGIN si la demande provient de " http://from.com " ou " https://from.com ". Puisque le serveur est d'accord avec lui, le navigateur fera une 2ème demande (cette fois un POST). Il est recommandé que votre client définisse le type de contenu qu'il envoie - vous devrez donc également l'autoriser.

MDN a une grande rédaction sur le contrôle d'accès HTTP , qui explique en détail le fonctionnement de l'ensemble du flux. Selon leurs documents, cela devrait "fonctionner dans les navigateurs qui prennent en charge XMLHttpRequest intersite". C'est un peu trompeur cependant, car je pense que seuls les navigateurs modernes autorisent le POST interdomaine. Je n'ai vérifié que cela fonctionne avec safari, chrome, FF 3.6.

Gardez à l'esprit les points suivants si vous procédez ainsi:

  1. Votre serveur devra traiter 2 requêtes par opération
  2. Vous devrez penser aux implications de sécurité. Soyez prudent avant de faire quelque chose comme «Access-Control-Allow-Origin: *»
  3. Cela ne fonctionnera pas sur les navigateurs mobiles. D'après mon expérience, ils n'autorisent pas du tout le domaine POST. J'ai testé Android, iPad, iPhone
  4. Il y a un assez gros bogue dans FF <3.6 où si le serveur retourne un code de réponse non 400 ET il y a un corps de réponse (erreurs de validation par exemple), FF 3.6 n'obtiendra pas le corps de réponse. C'est une énorme douleur dans le cul, car vous ne pouvez pas utiliser de bonnes pratiques REST. Voir le bogue ici (son déposé sous jQuery, mais je suppose que c'est un bogue FF - semble être corrigé dans FF4).
  5. Renvoyez toujours les en-têtes ci-dessus, pas seulement sur les demandes OPTION. FF en a besoin dans la réponse du POST.

Peut-il retourner du HTML par exemple? Je dois retourner du HTML et quelque chose ne fonctionne pas ...
denis_n

Oui, vous devriez pouvoir. Je ne l'ai jamais essayé. Votre serveur retourne 200? Votre serveur renvoie-t-il également les en-têtes des requêtes OPTIONS et POST? J'ai mis à jour ma réponse avec plus de détails à ce sujet. Assurez-vous que votre serveur répond également avec l'en-tête de type de contenu correct (comme text / html). Ma recommandation est d'utiliser Google Chrome, page de clic droit> inspecter l'élément. Cliquez sur l'onglet réseau et regardez le POST et la réponse. Devrait vous donner des informations sur ce qui ne va pas.
rynop

J'ai essayé cela, mais j'obtiens toujours 400 Bad Requestsur OPTIONSdemande. et dans firefoxla deuxième demande de POSTn'est jamais faite. :(
Zain Shaikh

Existe-t-il un moyen d'appeler votre ordinateur local dans votre déclaration de cas ci-dessus? Ou devez-vous simplement utiliser le * dans ce cas pour les origines autorisées.
Todd Vance

1
la dernière modification a eu lieu il y a 4 ans - cela fonctionnera-t-il maintenant sur les navigateurs mobiles?
frankpinto

121

Si vous contrôlez le serveur distant, vous devez probablement utiliser CORS, comme décrit dans cette réponse ; il est pris en charge dans IE8 et versions ultérieures, ainsi que dans toutes les versions récentes de FF, GC et Safari. (Mais dans IE8 et 9, CORS ne vous permettra pas d'envoyer des cookies dans la demande.)

Donc, si vous ne contrôlez pas le serveur distant, ou si vous devez prendre en charge IE7, ou si vous avez besoin de cookies et que vous devez prendre en charge IE8 / 9, vous voudrez probablement utiliser une technique iframe.

  1. Créez un iframe avec un nom unique. (Les iframes utilisent un espace de noms global pour l'ensemble du navigateur, alors choisissez un nom qu'aucun autre site Web n'utilisera.)
  2. Construisez un formulaire avec des entrées cachées, ciblant l'iframe.
  3. Soumettez le formulaire.

Voici un exemple de code; Je l'ai testé sur 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();
}

Il faut se méfier! Vous ne pourrez pas lire directement la réponse du POST, car l'iframe existe sur un domaine séparé. Les cadres ne sont pas autorisés à communiquer entre eux à partir de différents domaines; c'est la même politique d'origine .

Si vous contrôlez le serveur distant mais que vous ne pouvez pas utiliser CORS (par exemple parce que vous êtes sur IE8 / IE9 et que vous devez utiliser des cookies), il existe des moyens de contourner la même politique d'origine, par exemple en utilisant window.postMessageet / ou l'une des nombreuses bibliothèques vous permettant d'envoyer des messages inter-domaines entre images dans des navigateurs plus anciens:

Si vous ne contrôlez pas le serveur distant, vous ne pouvez pas lire la réponse du POST, point. Sinon, cela entraînerait des problèmes de sécurité.


2
Vous devrez définir form.target sur quelque chose, sinon le navigateur s'éloignera de votre site vers l'URL d'action du formulaire. De plus, la chaîne doit être unique; s'il existe d'autres cadres ou fenêtres portant le même nom, le formulaire peut être publié dans cette fenêtre au lieu de votre iframe. Mais à quel point doit-il être unique? Probablement pas très. Les chances de clobber sont assez faibles. haussement d'épaules
Dan Fabulich

1
@Nawaz Comme je l'ai dit dans ma réponse, vous devrez effectuer une communication inter-domaines entre cadres pour obtenir le résultat dans votre page Web. Il nécessite que vous contrôliez le serveur Web distant afin de pouvoir modifier sa réponse pour permettre la communication avec votre page Web. (D'une part, le serveur devra répondre avec HTML; si le serveur répond avec XML brut, il ne peut pas faire de communication entre cadres.)
Dan Fabulich

1
+1 - c'est la meilleure solution que j'ai trouvée si vous n'avez pas accès au serveur
James Long

1
@VojtechB Non, ce serait un trou de sécurité.
Dan Fabulich

1
@Andrus Vous pouvez lire le résultat du POST, mais seulement si vous contrôlez le serveur! Voir dans cette réponse, il est dit "faire X sur l'expéditeur [client], faire Y sur le récepteur [serveur]". Si vous ne contrôlez pas le récepteur / serveur, vous ne pouvez pas faire Y, et donc vous ne pouvez pas lire le résultat du POST.
Dan Fabulich

48
  1. Créez un iFrame,
  2. mettre un formulaire avec des entrées cachées,
  3. définissez l'action du formulaire sur l'URL,
  4. Ajouter un iframe au document
  5. soumettre le formulaire

Pseudocode

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

Vous voulez probablement styliser l'iframe, pour qu'il soit caché et absolument positionné. Je ne sais pas si la publication intersite sera autorisée par le navigateur, mais si c'est le cas, voici comment procéder.


4
En fait, cela est légèrement inexact, car ifr.appendChild (frm); ne fonctionnera pas. l'iframe est une référence à un objet window, et la méthode appendChild n'existe pas pour lui. Vous devrez d'abord saisir le nœud du document dans l'iframe. Cela nécessite une détection des fonctionnalités pour fonctionner sur tous les navigateurs.
Rakesh Pai


19
Problème! La réponse reçue dans l'iframe se trouve dans un domaine différent, de sorte que la fenêtre principale n'y a pas accès, l'iframe n'a pas non plus accès à la fenêtre principale. Donc, cette solution ne semble bonne que pour faire le POST, mais vous ne pouvez pas analyser la réponse par la suite :(
Ido Schacham

2
Essayez de définir une onload dans la balise body de la réponse à une fonction JavaScript qui appelle une fonction dans le parent avec la chaîne de réponse.
Lou Franco

Cette réponse n'a pas fonctionné pour moi; J'ai posté ma propre variation ci-dessous.
Dan Fabulich

24

Rester simple:

  1. POST interdomaine:
    utilisationcrossDomain: true,

  2. ne doit pas rafraîchir la page:
    Non, il ne sera pas rafraîchir la page que lesuccessouerrorasync rappel sera appelée lorsque l'envoi de la réponse serveur.


Exemple de script:

$.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: truecurieusement n'a absolument rien à voir avec de vraies demandes inter-domaines. Si la demande est inter-domaines, jquery définit cela sur true de manière automatique.
Kevin B

16

Si vous avez accès à tous les serveurs impliqués, mettez ce qui suit dans l'en-tête de la réponse pour la page demandée dans l'autre domaine:

PHP:

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

Par exemple, dans le code xmlrpc.php de Drupal, vous feriez ceci:

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

Cela crée probablement un problème de sécurité et vous devez vous assurer de prendre les mesures appropriées pour vérifier la demande.



6
  1. Créez deux iframes cachés (ajoutez "display: none;" au style css). Faites en sorte que votre deuxième iframe pointe vers quelque chose sur votre propre domaine.

  2. Créez un formulaire caché, définissez sa méthode sur "post" avec target = votre premier iframe, et définissez éventuellement enctype sur "multipart / form-data" (je pense que vous voulez faire du POST parce que vous voulez envoyer des données en plusieurs parties comme des images ?)

  3. Lorsque vous êtes prêt, envoyez le formulaire () au POST.

  4. Si vous pouvez obtenir l'autre domaine pour renvoyer le javascript qui fera la communication inter-domaines avec les iframes ( http://softwareas.com/cross-domain-communication-with-iframes ), alors vous avez de la chance, et vous pouvez capturer la réponse ainsi que.

Bien sûr, si vous souhaitez utiliser votre serveur comme proxy, vous pouvez éviter tout cela. Envoyez simplement le formulaire à votre propre serveur, qui procurera la demande à l'autre serveur (en supposant que l'autre serveur n'est pas configuré pour remarquer des différences IP), obtenez la réponse et retournez ce que vous voulez.


6

Encore une chose importante à noter !!! Dans l' exemple ci-dessus, il est décrit comment utiliser

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

JQuery 1.6 et inférieur a un bogue avec XHR interdomaine. Selon Firebug, aucune demande, sauf OPTIONS, n'a été envoyée. Pas d'article. Du tout.

J'ai passé 5 heures à tester / régler mon code. Ajout d'un grand nombre d'en-têtes sur le serveur distant (script). Sans aucun effet. Mais plus tard, j'ai mis à jour la bibliothèque JQuery vers 1.6.4, et tout fonctionne comme un charme.


Whoopps, pas dans Opera 10.61. Ma décision finale a été d'utiliser un proxy PHP sur mon domaine.
BasTaller

Comment avez-vous utilisé le proxy PHP? Pouvez-vous me guider là-dessus?
Zoran777

voir les réponses ci-dessous, par exemple par Ivan Durst
BasTaller

5

Si vous souhaitez le faire dans un environnement ASP.net MVC avec JQuery AJAX, procédez comme suit: (il s'agit d'un résumé de la solution proposée dans ce fil)

Supposons que "caller.com" (peut être n'importe quel site Web) doit être publié sur "server.com" (une application ASP.net MVC)

  1. Sur le Web.config de l'application "server.com", ajoutez la section suivante:

      <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. Sur le "server.com", nous aurons l'action suivante sur le contrôleur (appelé "Home") sur lequel nous posterons:

    [HttpPost]
    public JsonResult Save()
    {
        //Handle the post data...
    
        return Json(
            new
            {
                IsSuccess = true
            });
    }
  3. Ensuite, à partir de "caller.com", publiez les données d'un formulaire (avec l'ID html "formId") vers "server.com" comme suit:

    $.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

Il existe un autre moyen (en utilisant la fonctionnalité html5). Vous pouvez utiliser un iframe proxy hébergé sur cet autre domaine, vous envoyez un message à l'aide de postMessage à cet iframe, puis cet iframe peut effectuer une requête POST (sur le même domaine) et postMessage avec reposnse dans la fenêtre parent.

parent sur 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 sur 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)
}

Il y a une question connexe dans stackoverflow.com/questions/38940932/… . Est-il possible de créer un plugin ou une fonction générique en fonction de votre échantillon?
Andrus

@Andrus peut-être quelque chose comme ça gist.github.com/jcubic/26f806800abae0db9a0dfccd88cf6f3c
jcubic

Ce code nécessite de modifier la page du récepteur. Comment lire la réponse si les pages du destinataire ne peuvent pas être modifiées?
Andrus

@Andrus vous ne pouvez pas avoir besoin d'avoir accès à iframe recever.com pour y envoyer des demandes ajax. Sans iframe, il n'y aura aucune demande.
jcubic

3

Haut niveau .... Vous devez avoir une configuration cname sur votre serveur afin que other-serve.your-server.com pointe vers other-server.com.

Votre page crée dynamiquement un iframe invisible, qui agit comme votre transport vers other-server.com. Vous devez ensuite communiquer via JS depuis votre page vers other-server.com et avoir des rappels qui renvoient les données sur votre page.

Possible mais nécessite une coordination de your-server.com et other-server.com


Je n'ai même pas pensé à utiliser un CNAME pour rediriger. Bon appel! Je n'ai pas encore essayé, mais je suppose que le CNAME incitera le navigateur à penser qu'il interagit avec le même site? Je vais l'utiliser pour publier sur Amazon S3, donc j'espère que cela fonctionne.
SpencerElliott

1
Je ne vois pas comment cela résoudrait quoi que ce soit. le passage à un sous-domaine différent a les mêmes problèmes que le passage à un domaine différent.
Octopus le


2

C'est une vieille question, mais une nouvelle technologie pourrait aider quelqu'un.

Si vous disposez d'un accès administratif à l'autre serveur, vous pouvez utiliser le projet opensource Forge pour effectuer votre POST interdomaine. Forge fournit un wrapper XmlHttpRequest JavaScript inter-domaines qui tire parti de l'API de socket brut de Flash. Le POST peut même être effectué sur TLS.

La raison pour laquelle vous avez besoin d'un accès administratif au serveur sur lequel vous POSTEZ est parce que vous devez fournir une stratégie inter-domaines qui autorise l'accès à partir de votre domaine.

http://github.com/digitalbazaar/forge


2

Je sais que c'est une vieille question, mais je voulais partager mon approche. J'utilise cURL comme proxy, très simple et cohérent. Créez une page php appelée submit.php et ajoutez le code suivant:

<?

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

Ensuite, dans votre js (jQuery ici):

$.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

Cela devrait être possible avec une table personnalisée YQL + JS XHR, jetez un œil à: http://developer.yahoo.com/yql/guide/index.html

Je l'utilise pour faire du grattage html côté client (js), fonctionne très bien (j'ai un lecteur audio complet, avec recherche sur internet / playlists / paroles / dernières informations fm, tous les clients js + YQL)


1

CORS est fait pour vous. CORS est "Cross Origin Resource Sharing", est un moyen d'envoyer une demande interdomaine. Maintenant, XMLHttpRequest2 et Fetch API prennent en charge CORS et peuvent envoyer à la fois des requêtes POST et GET

Mais il a ses limites. Le serveur doit revendiquer spécifiquement l' Access-Control-Allow-Origin , et il ne peut pas être défini sur '*'.

Et si vous voulez que n'importe quelle origine puisse vous envoyer une demande, vous avez besoin de JSONP (vous devez également définir Access-Control-Allow-Origin , mais peut être '*')

Pour beaucoup de demandes, si vous ne savez pas comment choisir, je pense que vous avez besoin d'un composant fonctionnel complet pour le faire. Permettez-moi de vous présenter un composant simple https://github.com/Joker-Jelly/catta


Si vous utilisez un navigateur moderne (> IE9, Chrome, FF, Edge, etc.), très vous recommande d'utiliser un composant simple mais esthétique https://github.com/Joker-Jelly/catta .Il n'a aucune dépendance, Moins de 3 Ko, et il prend en charge Fetch, AJAX et JSONP avec la même syntaxe et les options mortelles.

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

Il prend également en charge tout le chemin pour importer dans votre projet, comme le module ES6, CommonJS et même <script>en HTML.


1

Si vous avez accès au serveur interdomaine et que vous ne souhaitez pas modifier le code côté serveur, vous pouvez utiliser une bibliothèque appelée - 'xdomain'.

Comment ça fonctionne:

Étape 1: serveur 1: incluez la bibliothèque xdomain et configurez le domaine croisé en tant qu'esclave:

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

Étape 2: sur un serveur interdomaine, créez un fichier proxy.html et incluez le serveur 1 en tant que maître:

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

Étape 3:

Maintenant, vous pouvez effectuer un appel AJAX au proxy.html en tant que point de terminaison à partir de server1. Ceci contourne la demande CORS. La bibliothèque utilise en interne la solution iframe qui fonctionne avec les informations d'identification et toutes les méthodes possibles: GET, POST, etc.

Recherchez le code 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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.