¿AJAX en Chrome envía OPCIONES en lugar de GET / POST / PUT / DELETE?

107

Estoy trabajando en una aplicación web interna en el trabajo. En IE10, las solicitudes funcionan bien, pero en Chrome todas las solicitudes AJAX (que hay muchas) se envían usando OPTIONS en lugar de cualquier método definido que le dé. Técnicamente, mis solicitudes son de "dominio cruzado". El sitio se sirve en localhost: 6120 y el servicio al que estoy haciendo solicitudes AJAX está en 57124. Este error de jquery cerrado define el problema, pero no una solución real.

¿Qué puedo hacer para utilizar el método http adecuado en las solicitudes ajax?

Editar:

Esto está en la carga de documentos de cada página:

jQuery.support.cors = true;

Y cada AJAX se construye de manera similar:

var url = 'http://localhost:57124/My/Rest/Call';
$.ajax({
    url: url,
    dataType: "json",
    data: json,
    async: true,
    cache: false,
    timeout: 30000,
    headers: { "x-li-format": "json", "X-UserName": userName },
    success: function (data) {
        // my success stuff
    },
    error: function (request, status, error) {
        // my error stuff
    },
    type: "POST"
});
Corey Ogburn
fuente
2
El último comentario en ese informe de error lo explica bastante bien ...
Kevin B
1
Volteó mi mente porque todo lo que estoy haciendo es tan simple (y mi código es similar al del error jquery). Aparte de eso, no es excusa para no incluirlo. BRB, tomando un código de muestra.
Corey Ogburn
3
Tenga en cuenta que IE no considera los números de puerto al determinar si una solicitud es de origen cruzado.
Ray Nicholus
@KevinB: Nuestro servicio REST aprovecha las diferentes solicitudes para hacer diferentes cosas según el método http. Cambiar todo a GET no es una solución válida. Además, de acuerdo con la respuesta de Dark Falcon, de todos modos no ayudará porque tengo X-UserName y otros encabezados personalizados en las solicitudes.
Corey Ogburn
eso no cambia el hecho de que si desea realizar una solicitud de origen cruzado, debe seguir todas las reglas que se aplican a las solicitudes de origen cruzado para que funcione correctamente. Las solicitudes de origen cruzado generalmente implican una solicitud de OPCIONES. Manéjelo correctamente y el problema desaparecerá. La única otra forma de resolver esto (sin cambiar la api) es tener un script en el mismo servidor que la página principal que interactúa con la api utilizando código del lado del servidor.
Kevin B

Respuestas:

136

Chrome está realizando una verificación previa de la solicitud para buscar encabezados CORS . Si la solicitud es aceptable, enviará la solicitud real. Si está haciendo este dominio cruzado, simplemente tendrá que lidiar con él o de lo contrario encontrar una manera de hacer que la solicitud no sea de dominio cruzado. Esta es la razón por la que el error de jQuery se cerró como no se solucionará. Esto es por diseño.

A diferencia de las solicitudes simples (discutidas anteriormente), las solicitudes "preflight" envían primero una solicitud HTTP mediante el método OPTIONS al recurso en el otro dominio, para determinar si la solicitud real es segura para enviar. Las solicitudes entre sitios se revisan previamente de esta manera, ya que pueden tener implicaciones en los datos del usuario. En particular, una solicitud tiene una verificación previa si:

  • Utiliza métodos distintos a GET, HEAD o POST. Además, si se usa POST para enviar datos de solicitud con un tipo de contenido que no sea application / x-www-form-urlencoded, multipart / form-data o text / plain, por ejemplo, si la solicitud POST envía una carga útil XML al servidor usando application / xml o text / xml, la solicitud tiene una verificación previa.
  • Establece encabezados personalizados en la solicitud (por ejemplo, la solicitud utiliza un encabezado como X-PINGOTHER)
Halcón oscuro
fuente
20
Encabezados personalizados. Eso es probablemente lo que desencadenó las llamadas OPTIONS antes del vuelo.
Corey Ogburn
18

Basado en el hecho de que la solicitud no se envía en el puerto predeterminado 80/443, esta llamada Ajax se considera automáticamente una solicitud de recurso de origen cruzado (CORS) , lo que en otras palabras significa que la solicitud emite automáticamente una solicitud OPTIONS que verifica Encabezados CORS en el lado del servidor / servlet.

Esto sucede incluso si configura

crossOrigin: false;

o incluso si lo omite.

La razón es simplemente esa localhost != localhost:57124. Intente enviarlo solo localhostsin el puerto; fallará, porque el destino solicitado no será accesible, sin embargo , tenga en cuenta que si los nombres de dominio son iguales, la solicitud se envía sin la solicitud OPTIONS antes de POST.

Abandonar
fuente
3

Estoy de acuerdo con Kevin B, el informe de errores lo dice todo. Parece que está intentando realizar llamadas ajax entre dominios. Si no está familiarizado con la misma política de origen, puede comenzar aquí: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Same_origin_policy_for_JavaScript .

Si esto no pretende ser una llamada ajax entre dominios, intente hacer que su URL de destino sea relativa y vea si el problema desaparece. Si está realmente desesperado, mire el JSONP, pero tenga cuidado, el caos acecha. Realmente no hay mucho más que podamos hacer para ayudarlo.

jgitter
fuente
1
La estructura de nuestro sistema es algo que no puedo cambiar. El uso de un puerto diferente es un requisito de nuestra arquitectura. Tengo la misma política de origen, pero pensé que el CORS que implementamos era suficiente. Aparentemente no.
Corey Ogburn
2
Si su servidor devuelve respuestas JSON, puede consultar el método JSONP, solo utilícelo de manera responsable.
jgitter
1
Realmente no me importa discutir con usted, pero JSONP usa etiquetas de script para extraer datos de otro dominio y luego envía el resultado a una función de devolución de llamada. Es mucho más difícil si el resultado no es json.
jgitter
1
No, no es mucho más difícil. De hecho, la respuesta no debería ser JSON válido en ningún caso. En su lugar, el servidor debe devolver algo como esto: callbackfunc(somedata). Como puede ver, este no es un JSON válido. Y somedatapuede ser una cadena, un número o lo que quieras que sea.
Ray Nicholus
1
Estoy usando Postman y allí los métodos de solicitud se envían correctamente (por ejemplo, 'PUT', 'DELETE', etc.). Pero cuando trato de hacerlo desde mi código, siempre los envío con el método de solicitud OPTIONS. No tengo ni idea de cómo Postman puede hacerlo.
ErwinGO
1

Si es posible, pase los parámetros a través de GET / POST normal con un nombre diferente y deje que el código del lado del servidor lo maneje.

Tuve un problema similar con mi propio proxy para omitir CORS y obtuve el mismo error de POST-> OPCIÓN en Chrome. Era el Authorizationencabezado en mi caso ( "x-li-format"y "X-UserName"aquí en su caso). Terminé pasándolo en un formato ficticio (por ejemplo, AuthorizatinJacken GET) y cambié el código de mi proxy para convertirlo en un encabezado al hacer la llamada al destino . Aquí está en PHP:

if (isset($_GET['AuthorizationJack'])) {
    $request_headers[] = "Authorization: Basic ".$_GET['AuthorizationJack'];
}
Ayuda en
fuente
1

En mi caso, llamo a una API alojada por AWS (API Gateway). El error ocurrió cuando intenté llamar a la API desde un dominio que no era el propio dominio de la API. Como soy el propietario de la API, habilité CORS para el entorno de prueba, como se describe en la documentación de Amazon .

En producción este error no ocurrirá, ya que la solicitud y la api estarán en el mismo dominio.

¡Espero que ayude!

gbonesso
fuente
0

Como respondió @Dark Falcon, simplemente lo resolví .

En mi caso, estoy usando el servidor node.js y creando una sesión si no existe. Dado que el método OPTIONS no tiene los detalles de la sesión, terminó creando una nueva sesión para cada solicitud del método POST.

Entonces, en mi rutina de aplicación para crear-sesión-si-no-existe, acabo de agregar una verificación para ver si el método es OPTIONS, y si es así, simplemente omita la parte de creación de sesión:

    app.use(function(req, res, next) {
        if (req.method !== "OPTIONS") {
            if (req.session && req.session.id) {
                 // Session exists
                 next();
            }else{
                 // Create session
                 next();
          }
        } else {
           // If request method is OPTIONS, just skip this part and move to the next method.
           next(); 
        }
    }
Mahesh
fuente
0

Las solicitudes "preflight" envían primero una solicitud HTTP mediante el método OPTIONS al recurso en el otro dominio, para determinar si la solicitud real es segura para enviar. Solicitudes entre sitios

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

Noorullah
fuente
1
¿Podría agregar un poco más de información? Tu respuesta parece un comentario. :)
Badacadabra
0

Considere usar axios

axios.get( url,
{ headers: {"Content-Type": "application/json"} } ).then( res => {

  if(res.data.error) {

  } else { 
    doAnything( res.data )
  }

}).catch(function (error) {
   doAnythingError(error)
});

Tuve este problema al usar fetch y axios funcionó perfectamente.

Evhz
fuente
5
Axios también usa las primeras OPCIONES
Skylin R
0

Me encontré con un problema muy similar. Pasé casi medio día para comprender por qué todo funciona correctamente en Firefox y falla en Chrome. En mi caso, fue debido a campos duplicados (o tal vez mal escritos) en el encabezado de mi solicitud.

Andrew Tatomyr
fuente
0

Use fetch en lugar de XHR, entonces la solicitud no se prealizará incluso si tiene dominios cruzados.

Fei Sun
fuente
-1
 $.ajax({
            url: '###',
            contentType: 'text/plain; charset=utf-8',
            async: false,
            xhrFields: {
                withCredentials: true,
                crossDomain: true,
                Authorization: "Bearer ...."
            },

            method: 'POST',

            data: JSON.stringify( request ),
            success: function (data) {
                console.log(data);
            }
        });

el contentType: 'text / plain; charset = utf-8 ', o simplemente contentType:' text / plain ', ¡funciona para mí! ¡¡Saludos!!

David Lopes
fuente
¿Qué tiene esto que ver con la pregunta?
Corey Ogburn
Hola, creo que esto soluciona el problema del título, con este tipo de contenido pasas el método OPCIONES. Saludos
David Lopes
ContentType no tiene nada que ver con el método.
Corey Ogburn
Sé lo que estás diciendo, pero pruébalo. dependiendo del navegador, su tipo de contenido puede influir en su solicitud y cambiar su método.
David Lopes