Cómo hacer que funcione una solicitud posterior de intercambio de recursos de origen cruzado (CORS)

216

Tengo una máquina en mi LAN local (machineA) que tiene dos servidores web. El primero es el incorporado en XBMC (en el puerto 8080) y muestra nuestra biblioteca. El segundo servidor es un script CherryPy python (puerto 8081) que estoy usando para activar una conversión de archivos a pedido. La conversión de archivos se desencadena por una solicitud AJAX POST desde la página servida desde el servidor XBMC.

  • Ir a http: // machineA: 8080 que muestra la biblioteca
  • Se muestra la biblioteca
  • El usuario hace clic en el enlace 'convertir' que emite el siguiente comando:

Solicitud jQuery Ajax

$.post('http://machineA:8081', {file_url: 'asfd'}, function(d){console.log(d)})
  • El navegador emite una solicitud de OPCIONES HTTP con los siguientes encabezados;

Encabezado de solicitud - OPCIONES

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
  • El servidor responde con lo siguiente;

Encabezado de respuesta - OPCIONES (ESTADO = 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 conversación luego se detiene. El navegador, en teoría, debería emitir una solicitud POST ya que el servidor respondió con los encabezados CORS correctos (?) (Access-Control-Allow-Origin: *)

Para solucionar problemas, también he emitido el mismo comando $ .post de http://jquery.com . Aquí es donde estoy perplejo, desde jquery.com, la solicitud de publicación funciona, una solicitud de OPCIONES se envía a continuación por una POST. Los encabezados de esta transacción están a continuación;

Encabezado de solicitud - OPCIONES

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

Encabezado de respuesta - OPCIONES (ESTADO = 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

Encabezado de solicitud - 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

Encabezado de respuesta - POST (ESTADO = 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

No puedo entender por qué la misma solicitud funcionaría en un sitio, pero no en el otro. Espero que alguien pueda señalar lo que me estoy perdiendo. ¡Gracias por tu ayuda!

James
fuente
¿Se necesita CORS si ambos servidores web están en la misma máquina?
jdigital
8
Que yo sepa, es una solicitud CORS debido a los diferentes puertos. Además, la solicitud OPTIONS indica que el navegador la está tratando como una solicitud CORS
James

Respuestas:

157

Finalmente me topé con este enlace " Una solicitud CORS POST funciona desde JavaScript simple, pero ¿por qué no con jQuery? ", Que señala que jQuery 1.5.1 agrega el

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

encabezado a todas las solicitudes CORS. jQuery 1.5.2 no hace esto. Además, de acuerdo con la misma pregunta, establecer un encabezado de respuesta del servidor de

Access-Control-Allow-Headers: *

no permite que la respuesta continúe. Debe asegurarse de que el encabezado de respuesta incluya específicamente los encabezados necesarios. es decir:

Access-Control-Allow-Headers: x-requested-with 
James
fuente
70

SOLICITUD:

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

RESPUESTA:

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

return response
Hassan Zaheer
fuente
3
si el servidor no acepta origen cruzado, crossdomain = true no tiene que resolver el problema. usando dataType: "jsonp" y configurando la devolución de llamada como jsonpCallback: "respuesta" sería la mejor idea para hacer esto. Ver también: api.jquery.com/jquery.ajax
BonifatiusK
14

Resolví mi propio problema al usar la API de matriz de distancia de Google configurando mi encabezado de solicitud con Jquery ajax. Echa un vistazo a continuación.

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

      });

Tenga en cuenta lo que agregué en la configuración
**

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

**
Espero que esto ayude.

Miracool
fuente
esta es la única solución que nos funcionó sin cambiar nada en el lado del servidor ... gracias miracool
Timsta
1
@Timsta, estoy feliz de que mi sugerencia haya funcionado para ti. Agradecido de apilar el desbordamiento también. Que tengas un gran día.
Miracool
13

Me tomó algo de tiempo encontrar la solución.

En caso de que su servidor responda correctamente y la solicitud sea el problema, debe agregar withCredentials: truea xhrFieldsla solicitud:

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

Nota: se requiere jQuery> = 1.5.1

Dekel
fuente
versión jquery ok? estableciste el withCredentials: true? ¿Estás seguro de que tienes los encabezados relevantes?
Dekel
Sí, 1con credencial: verdadera , versión de jquery: 3.2.1`. De hecho, está trabajando a través del cartero, pero no está pasando por el navegador Chrome
Mox Shah
1
Estoy casi seguro de que el cartero no debería tener problemas CORS porque no es un navegador y su comportamiento es diferente. ¿Seguro que tiene los encabezados correctos y relevantes enviados desde el servidor al cliente? Nuevamente, tenga en cuenta que este cambio no es suficiente. Debe asegurarse de que el servidor responda con los encabezados correctos .
Dekel
¿Podría decirme cuáles son los encabezados de respuesta requeridos en el servidor?
Mox Shah
@MoxShah esta es una pregunta completamente diferente y hay muchos recursos allí :)
Dekel
9

Bueno, luché con este problema durante un par de semanas.

La forma más fácil, más compatible y no hacky de hacer esto es probablemente usar una API JavaScript del proveedor que no realice llamadas basadas en el navegador y pueda manejar solicitudes Cross Origin.

Por ejemplo, Facebook JavaScript API y Google JS API.

En caso de que su proveedor de API no esté actualizado y no admita el encabezado Cross Origin Resource Origin '*' en su respuesta y no tenga una API JS (Sí, estoy hablando de usted, Yahoo), se le llama la atención con una de las tres opciones:

  1. Usando jsonp en sus solicitudes que agrega una función de devolución de llamada a su URL donde puede manejar su respuesta. Tenga en cuenta que esto cambiará la URL de solicitud, por lo que su servidor API debe estar equipado para manejar el? Callback = al final de la URL.

  2. Envíe la solicitud a su servidor API, que usted controla y está en el mismo dominio que el cliente o tiene habilitado el uso compartido de recursos de Cross Origin desde donde puede enviar la solicitud al servidor API de terceros.

  3. Probablemente lo más útil en los casos en que realiza solicitudes de OAuth y necesita manejar la interacción del usuario ¡Jaja! window.open('url',"newwindowname",'_blank', 'toolbar=0,location=0,menubar=0')

Pritam Roy
fuente
4

Usar esto en combinación con Laravel resolvió mi problema. Simplemente agregue este encabezado a su solicitud de jquery Access-Control-Request-Headers: x-requested-withy asegúrese de que la respuesta del lado del servidor tenga este encabezado configurado Access-Control-Allow-Headers: *.

MK
fuente
66
No hay ninguna razón para agregar encabezados CORS a la solicitud manualmente. El navegador siempre agregará los encabezados CORS prop a la solicitud por usted.
Ray Nicholus el
1
El verdadero desafío es lograr que el servidor responda con un correcto Access-Control-Allow-Headersy JQ proporcione el correcto Access-Control-Request-Headers(además de cualquiera que agregue a través del código), ninguno de los cuales puede ser comodín. solo se necesita un encabezado "incorrecto" para hacer explotar el pre-vuelo, por ejemplo, If-None-Matchpara un GET condicional, si el servidor no tiene ese listado.
escape-llc
1

Por alguna razón, una pregunta sobre las solicitudes GET se fusionó con esta, por lo que responderé aquí.

Esta función simple obtendrá de forma asíncrona una respuesta de estado HTTP desde una página habilitada para CORS. Si lo ejecuta, verá que solo una página con los encabezados adecuados devuelve un estado 200 si se accede a través de XMLHttpRequest, ya sea que se use GET o POST. No se puede hacer nada en el lado del cliente para evitar esto, excepto posiblemente usar JSONP si solo necesita un objeto json.

Lo siguiente se puede modificar fácilmente para obtener los datos contenidos en el objeto 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
fuente
1

Tuve exactamente el mismo problema en el que jquery ajax solo me dio problemas de cors en las solicitudes posteriores donde las solicitudes funcionaron bien: cansé todo lo anterior sin resultados. Tenía los encabezados correctos en mi servidor, etc. Cambiarme para usar XMLHTTPRequest en lugar de jquery solucionó mi problema de inmediato. No importa qué versión de jquery usé, no lo solucionó. Fetch también funciona sin problemas si no necesita compatibilidad con el navegador hacia atrás.

        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)

Esperemos que esto ayude a cualquier otra persona con los mismos problemas.

ozzieisaacs
fuente
1

Este es un resumen de lo que funcionó para mí:

Defina una nueva función (ajustada $.ajaxpara simplificar):

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

Uso:

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

También funciona con .done, .fail, etc:

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

Del lado del servidor (en este caso donde se aloja example.com), configure estos encabezados (se agregó un código de muestra 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);

Esta es la única forma en que sé REALMENTE PUBLICAR dominios cruzados desde JS.

JSONP convierte la POST en GET que puede mostrar información confidencial en los registros del servidor.

lepe
fuente
0

Si por alguna razón al intentar agregar encabezados o establecer una política de control todavía no está llegando a ningún lado, puede considerar usar apache ProxyPass ...

Por ejemplo, en uno <VirtualHost>que usa SSL, agregue las dos directivas siguientes:

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

Asegúrese de que los siguientes módulos de Apache estén cargados (cárguelos usando a2enmod):

  • apoderado
  • proxy_connect
  • proxy_http

Obviamente, tendrá que cambiar su url de solicitudes AJAX para usar el proxy apache ...

embestida
fuente
-3

Esto es un poco tarde para la fiesta, pero he estado luchando con esto durante un par de días. Es posible y ninguna de las respuestas que encontré aquí ha funcionado. Es engañosamente simple. Aquí está la llamada .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>

Aquí está el php en el lado del servidor:

    <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
fuente
1
Para lo que vale, el encabezado Origin es un encabezado de solicitud, no un encabezado de respuesta. Su script php no debería estar configurándolo.
Fideos
Hmmph Esto me hizo ilusiones y luego las desvanecí cuando vi que estabas haciendo un "GET" de AJAX en tu código, cuando el OP dijo claramente que estaba tratando de EVITAR usar "GET" y quería usar "POST".
Steve Sauder
Los encabezados CORS funcionan igual independientemente del verbo involucrado. Podemos invocar todos los verbos a través de $.ajax()un servidor configurado correctamente. La parte más difícil es obtener lo Access-Control-Request-Headerscorrecto, pero incluso eso no es demasiado difícil. Como se señaló en carteles anteriores, esto no debe ser un comodín sino una lista blanca de encabezados.
escape-llc