Comment faire fonctionner une demande de partage de ressources d'origine croisée (CORS)

217

J'ai une machine sur mon réseau local (machineA) qui a deux serveurs Web. Le premier est celui intégré dans XBMC (sur le port 8080) et affiche notre bibliothèque. Le deuxième serveur est un script python CherryPy (port 8081) que j'utilise pour déclencher une conversion de fichier à la demande. La conversion du fichier est déclenchée par une requête AJAX POST à ​​partir de la page servie depuis le serveur XBMC.

  • Aller à http: // machineA: 8080 qui affiche la bibliothèque
  • La bibliothèque est affichée
  • L'utilisateur clique sur le lien «convertir» qui émet la commande suivante -

jQuery Ajax Request

$.post('http://machineA:8081', {file_url: 'asfd'}, function(d){console.log(d)})
  • Le navigateur émet une requête HTTP OPTIONS avec les en-têtes suivants;

En-tête de demande - 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
  • Le serveur répond par ce qui suit:

En-tête de réponse - OPTIONS (STATUT = 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
  • La conversation s'arrête alors. Le navigateur devrait, en théorie, émettre une requête POST car le serveur a répondu avec les en-têtes CORS corrects (?) (Access-Control-Allow-Origin: *)

Pour le dépannage, j'ai également émis la même commande $ .post depuis http://jquery.com . C'est là que je suis perplexe, de jquery.com, la demande de publication fonctionne, une demande d'OPTIONS est envoyée par la suite par un POST. Les en-têtes de cette transaction sont ci-dessous;

En-tête de demande - 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

En-tête de réponse - OPTIONS (STATUT = 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

En-tête de demande - 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

En-tête de réponse - POST (STATUS = 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

Je ne peux pas comprendre pourquoi la même demande fonctionnerait à partir d'un site, mais pas de l'autre. J'espère que quelqu'un pourrait indiquer ce qui me manque. Merci de votre aide!

James
la source
CORS est-il nécessaire si les deux serveurs Web sont sur la même machine?
jdigital
8
À ma connaissance, il s'agit d'une demande CORS en raison des différents ports. De plus, la demande OPTIONS indique que le navigateur la traite comme une demande CORS
James

Réponses:

158

Je suis finalement tombé sur ce lien " Une requête CORS POST fonctionne à partir de javascript ordinaire, mais pourquoi pas avec jQuery? " Qui note que jQuery 1.5.1 ajoute le

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

en-tête à toutes les demandes CORS. jQuery 1.5.2 ne fait pas cela. En outre, selon la même question, définir un en-tête de réponse du serveur de

Access-Control-Allow-Headers: *

ne permet pas à la réponse de continuer. Vous devez vous assurer que l'en-tête de réponse inclut spécifiquement les en-têtes requis. c'est à dire:

Access-Control-Allow-Headers: x-requested-with 
James
la source
70

DEMANDE:

 $.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");
            }
        });

RÉPONSE:

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

return response
Hassan Zaheer
la source
3
si le serveur n'accepte pas l'origine croisée, le domaine croisé = true n'a pas à résoudre le problème. en utilisant dataType: "jsonp" et en définissant le rappel comme jsonpCallback: "réponse" serait la meilleure idée de le faire. Voir aussi: api.jquery.com/jquery.ajax
BonifatiusK
14

J'ai résolu mon propre problème lors de l'utilisation de l'API google distance matrix en définissant mon en-tête de demande avec Jquery ajax. jetez un œil ci-dessous.

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

      });

Notez ce que j'ai ajouté dans les paramètres
**

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

**
J'espère que cela aide.

Miracool
la source
c'est la seule solution qui a fonctionné pour nous sans rien changer du côté serveur ... thanx miracool
Timsta
2
@Timsta, je suis heureux que ma suggestion ait fonctionné pour vous. Reconnaissant d'empiler le débordement également. Passez une bonne journée.
Miracool
13

Cela m'a pris un peu de temps pour trouver la solution.

Si votre serveur répond correctement et que la demande pose problème, vous devez ajouter withCredentials: trueà la xhrFieldsdans la demande:

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

Remarque: jQuery> = 1.5.1 est requis

Dekel
la source
version jquery ok? avez-vous réglé le withCredentials: true? vous êtes sûr d'avoir les en-têtes appropriés?
Dekel
Oui, 1withCredential: true , jquery version: 3.2.1`. En fait, il fonctionne via le facteur, mais il ne passe pas par le navigateur Chrome
Mox Shah
1
Je suis presque sûr que le facteur ne devrait pas avoir de problèmes CORS car ce n'est pas un navigateur et son comportement est différent. Vous êtes sûr d'avoir les en-têtes corrects et pertinents envoyés du serveur au client? Encore une fois - notez que ce changement n'est pas suffisant. Vous devez vous assurer que le serveur répond avec les en- têtes corrects .
Dekel
Pourriez-vous me dire quels sont les en-têtes de réponse requis sur le serveur?
Mox Shah
@MoxShah, c'est une question complètement différente et il y a beaucoup de ressources là-bas :)
Dekel
9

Eh bien, j'ai eu du mal avec ce problème pendant quelques semaines.

Le moyen le plus simple, le plus conforme et le moins hacky de le faire est probablement d'utiliser une API JavaScript du fournisseur qui ne fait pas d'appels par navigateur et peut gérer les demandes Cross Origin.

Par exemple, Facebook JavaScript API et Google JS API.

Dans le cas où votre fournisseur d'API n'est pas à jour et ne prend pas en charge l'en-tête Cross Origin Resource Origin '*' dans sa réponse et n'a pas d'API JS (Oui, je parle de vous Yahoo), vous êtes frappé avec l'une des trois options:

  1. Utiliser jsonp dans vos demandes, ce qui ajoute une fonction de rappel à votre URL où vous pouvez gérer votre réponse. Attention, cela changera l'URL de la demande, votre serveur API doit donc être équipé pour gérer le? Callback = à la fin de l'URL.

  2. Envoyez la demande à votre serveur API que vous contrôlez et qui se trouve dans le même domaine que le client ou dont le partage de ressources Cross Origin est activé à partir duquel vous pouvez proxy la demande au serveur API tiers.

  3. Probablement le plus utile dans les cas où vous faites des demandes OAuth et devez gérer l'interaction utilisateur Haha! window.open('url',"newwindowname",'_blank', 'toolbar=0,location=0,menubar=0')

Pritam Roy
la source
4

L'utiliser en combinaison avec Laravel a résolu mon problème. Ajoutez simplement cet en-tête à votre demande jquery Access-Control-Request-Headers: x-requested-withet assurez-vous que votre réponse côté serveur a cet en-tête défini Access-Control-Allow-Headers: *.

MK
la source
6
Il n'y a aucune raison d'ajouter manuellement des en-têtes CORS à la demande. Le navigateur ajoutera toujours les en-têtes CORS prop à la demande pour vous.
Ray Nicholus
1
Le vrai défi consiste à faire répondre le serveur par un Access-Control-Allow-HeadersJQ correct et à fournir un correct Access-Control-Request-Headers(plus tout ce que vous ajoutez via le code) qui ne peuvent pas être des caractères génériques. il suffit d'un seul "mauvais" en-tête pour faire exploser le pré-vol, par exemple en utilisant If-None-Matchpour un GET conditionnel, si le serveur ne l'a pas répertorié.
escape-llc
1

Pour une raison quelconque, une question sur les demandes GET a été fusionnée avec celle-ci, je vais donc y répondre ici.

Cette fonction simple obtiendra de manière asynchrone une réponse d'état HTTP à partir d'une page compatible CORS. Si vous l'exécutez, vous verrez que seule une page avec les en-têtes appropriés renvoie un état 200 si elle est accessible via XMLHttpRequest - que GET ou POST soit utilisé. Rien ne peut être fait du côté client pour contourner cela, sauf éventuellement en utilisant JSONP si vous avez juste besoin d'un objet json.

Les éléments suivants peuvent être facilement modifiés pour obtenir les données contenues dans l'objet 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>

Victor Stoddard
la source
1

J'ai eu exactement le même problème où jquery ajax ne m'a donné que des problèmes de cors sur les demandes de publication où les demandes de travail ont bien fonctionné - j'ai fatigué tout ce qui précède sans aucun résultat. J'avais les en-têtes corrects sur mon serveur, etc. Changer pour utiliser XMLHTTPRequest au lieu de jquery a résolu mon problème immédiatement. Peu importe la version de jquery que j'ai utilisée, elle ne l'a pas corrigé. La récupération fonctionne également sans problème si vous n'avez pas besoin de compatibilité de navigateur en amont.

        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)

Espérons que cela aide toute autre personne aux mêmes problèmes.

ozzieisaacs
la source
1

Voici un résumé de ce qui a fonctionné pour moi:

Définissez une nouvelle fonction (encapsulée $.ajaxpour simplifier):

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

Usage:

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

Fonctionne également avec .done, .fail, etc:

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

Côté serveur (dans ce cas où example.com est hébergé), définissez ces en-têtes (ajout d'un exemple de code en 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);

C'est le seul moyen que je connaisse pour vraiment POSTER l'inter-domaine à partir de JS.

JSONP convertit le POST en GET qui peut afficher des informations sensibles dans les journaux du serveur.

lepe
la source
0

Si, pour certaines raisons, en essayant d'ajouter des en-têtes ou de définir une stratégie de contrôle, vous n'obtenez toujours rien, vous pouvez envisager d'utiliser apache ProxyPass…

Par exemple, dans celui <VirtualHost>qui utilise SSL, ajoutez les deux directives suivantes:

SSLProxyEngine On
ProxyPass /oauth https://remote.tld/oauth

Assurez-vous que les modules Apache suivants sont chargés (chargez-les à l'aide de a2enmod):

  • Procuration
  • proxy_connect
  • proxy_http

Évidemment, vous devrez changer l'url de vos requêtes AJAX afin d'utiliser le proxy apache…

llange
la source
-3

C'est un peu tard pour la fête, mais je me bats avec ça depuis quelques jours. C'est possible et aucune des réponses que j'ai trouvées ici n'a fonctionné. C'est d'une simplicité trompeuse. Voici l'appel .ajax:

    <!DOCTYPE HTML>
    <html>
    <head>
    <body>
     <title>Javascript Test</title>
     <script src="http://code.jquery.com/jquery-latest.min.js"></script>
     <script type="text/javascript">
     $(document).domain = 'XXX.com';
     $(document).ready(function () {
     $.ajax({
        xhrFields: {cors: false},
        type: "GET",
        url: "http://XXXX.com/test.php?email='[email protected]'",
        success: function (data) {
           alert(data);
        },
        error: function (x, y, z) {
           alert(x.responseText + " :EEE: " + x.status);
        }
    });
    });
    </script> 
    </body>
    </html>

Voici le php côté serveur:

    <html>
    <head>
     <title>PHP Test</title>
     </head>
    <body>
      <?php
      header('Origin: xxx.com');
      header('Access-Control-Allow-Origin:*');
      $servername = "sqlxxx";
      $username = "xxxx";
      $password = "sss";
      $conn = new mysqli($servername, $username, $password);
      if ($conn->connect_error) {
        die( "Connection failed: " . $conn->connect_error);
      }
      $sql = "SELECT email, status, userdata  FROM msi.usersLive";
      $result = $conn->query($sql);
      if ($result->num_rows > 0) {
      while($row = $result->fetch_assoc()) {
        echo $row["email"] . ":" . $row["status"] . ":" . $row["userdata"] .  "<br>";
      }
    } else {
      echo "{ }";
    }
    $conn->close();
    ?>
    </body>

Steve Johnson
la source
1
Pour ce que ça vaut, l'en-tête Origin est un en-tête de demande, pas un en-tête de réponse. Votre script php ne devrait pas le définir.
Nouilles
Hmmph. Cela a suscité mes espoirs, puis les a anéantis quand j'ai vu que vous créez un AJAX "GET" dans votre code, lorsque l'OP a clairement dit qu'il essayait D'ÉVITER d'utiliser "GET" et voulait utiliser "POST".
Steve Sauder
Les en-têtes CORS fonctionnent de la même manière quel que soit le verbe impliqué. Nous sommes en mesure d'invoquer tous les verbes via $.ajax()un serveur correctement configuré. La partie la plus difficile est d'obtenir le Access-Control-Request-Headersbon, mais même cela n'est pas trop difficile. Comme indiqué par les affiches précédentes, il ne doit pas s'agir d'un caractère générique, mais d'une liste blanche d'en-têtes.
escape-llc le