¿Por qué recibo una solicitud de OPCIONES en lugar de una solicitud GET?

288
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js" type="text/javascript"></script>
<script>
$.get("http://example.com/", function(data) {
     alert(data);
});
</script>

realiza una solicitud de OPCIONES a esa URL, y luego la devolución de llamada nunca se llama con nada.

Cuando no es dominio cruzado, funciona bien.

¿No debería jQuery simplemente hacer la llamada con un <script>nodo y luego hacer la devolución de llamada cuando está cargada? Entiendo que no podré obtener el resultado (ya que es dominio cruzado), pero está bien; Solo quiero que la llamada pase. ¿Es esto un error o estoy haciendo algo mal?

Paul Tarjan
fuente
2
Podría ser cos de dominio cruzado. Por ejemplo, si está en su archivo Archivo: // PATH_TO_WEBSITE en lugar de usar localhost / WEBSITE_LINK
James111

Respuestas:

262

De acuerdo con MDN ,

Solicitudes verificadas previamente

A diferencia de las solicitudes simples (discutidas anteriormente), las solicitudes "con verificación previa" primero envían un encabezado de solicitud de OPCIONES HTTP al recurso en el otro dominio, para determinar si la solicitud real es segura de enviar. Las solicitudes de sitios cruzados se verifican de esta manera, ya que pueden tener implicaciones para los datos del usuario. En particular, una solicitud se verifica previamente si:

  • Utiliza métodos distintos a GET o POST. Además, si POST se utiliza 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 XML al servidor usando application / xml o text / xml, la solicitud se verifica previamente.
  • Establece encabezados personalizados en la solicitud (por ejemplo, la solicitud utiliza un encabezado como X-PINGOTHER)
arturgrigor
fuente
43
esto solucionó nuestro problema, el cambio de "aplicación / json" a "texto / sin formato" detuvo la horrible solicitud de opciones
Keeno
10
lo que no entiendo es por qué el navegador está solicitando con el método OPTIONS solo para verificar que la solicitud real sea segura de enviar. pero en que sentido Me refiero a que el servidor también puede poner restricciones con ciertos encabezados de respuesta, ¿por qué esto es necesario?
hardik
11
@hardik Recuerde que al agregar CORS, está potencialmente aceptando solicitudes de cualquier persona, en la que podrían manipular datos en su servidor a través de solicitudes (POST, PUT, DELETE, etc.). En estas situaciones, como cuando se usan encabezados personalizados, el navegador solo está verificando primero con el servidor que el servidor está dispuesto a aceptar la solicitud antes de enviarla, ya que enviar solicitudes no solicitadas al servidor podría ser realmente peligroso para sus datos, y también el punto en el navegador que envía cargas útiles potencialmente grandes si el servidor no quiere aceptarlas, de ahí la verificación de OPCIONES previas al vuelo.
davidnknight
66
@davidnknight si enviar sus datos al servidor puede ser peligroso, lo que significa que el servidor podría verse comprometido, entonces, por supuesto, el servidor malintencionado respondería a su solicitud de OPCIONES con "¡Claro, envíelo todo!". ¿Cómo es esa seguridad? (pregunta honesta)
Matt
3
"Las solicitudes de verificación previa no son una cuestión de seguridad. Más bien, son una cosa que no cambia las reglas". - Vea la respuesta a ¿Cuál es la motivación detrás de la presentación de solicitudes de
verificación previa?
9

Si estás intentando publicar

Asegúrese de JSON.stringifysus datos de formulario y envíe como text/plain.

<form id="my-form" onSubmit="return postMyFormData();">
    <input type="text" name="name" placeholder="Your Name" required>
    <input type="email" name="email" placeholder="Your Email" required>
    <input type="submit" value="Submit My Form">
</form>

function postMyFormData() {

    var formData = $('#my-form').serializeArray();
    formData = formData.reduce(function(obj, item) {
        obj[item.name] = item.value;
        return obj;
    }, {});
    formData = JSON.stringify(formData);

    $.ajax({
        type: "POST",
        url: "https://website.com/path",
        data: formData,
        success: function() { ... },
        dataType: "text",
        contentType : "text/plain"
    });
}
Derek Soike
fuente
2

No creo que jQuery simplemente haga una solicitud JSONP cuando recibe una URL como esa. Sin embargo, hará una solicitud JSONP cuando le diga qué argumento usar para una devolución de llamada:

$.get("http://metaward.com/import/http://metaward.com/u/ptarjan?jsoncallback=?", function(data) {
     alert(data);
});

Depende completamente del script receptor hacer uso de ese argumento (que no tiene que llamarse "jsoncallback"), por lo que en este caso la función nunca será llamada. Pero, dado que declaró que solo desea que se ejecute el script en metaward.com, eso sería suficiente.

VoteyDisciple
fuente
¿se seguiría notificando MI devolución de llamada que el elemento del script se ha cargado completamente? Solo quiero asegurarme de que la actualización se realizó antes de consultar la API.
Paul Tarjan
Lo hará si el script receptor admite JSONP y está dispuesto a llamar a la función que identifique. Si la secuencia de comandos no hace nada más que generar un bloque de datos JSON sin otro comportamiento, no podrá saber cuándo finalizó la carga. Si es esencial saber cuándo termina de cargarse, puede considerar implementar un script en su propio servidor que actúe como proxy.
VoteyDisciple
1

De hecho, las solicitudes AJAX (XMLHttp) entre dominios no están permitidas por razones de seguridad (piense en buscar una página web "restringida" desde el lado del cliente y enviarla de vuelta al servidor; esto sería un problema de seguridad).

La única solución son las devoluciones de llamada. Esto es: crear un nuevo objeto de script y apuntar el src al JavaScript del lado final, que es una devolución de llamada con valores JSON (myFunction ({data}), myFunction es una función que hace algo con los datos (por ejemplo, almacenarlo en una variable).

Adrián Navarro
fuente
1
bien, pero puedo cargarlo en un <script src = ""> o <img src = ""> y el navegador lo golpeará felizmente. Solo quiero saber cuándo está completamente cargado para poder consultar el resultado de la importación.
Paul Tarjan
1

Simplemente cambie "application / json" a "text / plain" y no olvide el JSON.stringify (solicitud):

var request = {Company: sapws.dbName, UserName: username, Password: userpass};
    console.log(request);
    $.ajax({
        type: "POST",
        url: this.wsUrl + "/Login",
        contentType: "text/plain",
        data: JSON.stringify(request),

        crossDomain: true,
    });
David Lopes
fuente
1

Yo tuve el mismo problema. Mi solución fue agregar encabezados a mi script PHP que están presentes solo en el entorno de desarrollo.

Esto permite solicitudes de dominio cruzado:

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

Esto le indica a la solicitud de verificación previa que está bien que el cliente envíe los encabezados que desee:

header("Access-Control-Allow-Headers: *");

De esta forma no hay necesidad de modificar la solicitud.

Si tiene datos confidenciales en su base de datos de desarrollo que podrían filtrarse, podría pensarlo dos veces.

Fivebit
fuente
1

En mi caso, el problema no estaba relacionado con CORS ya que estaba emitiendo un jQuery POST al mismo servidor web. Los datos eran JSON pero había omitido el parámetro dataType: 'json'.

No tenía (ni agregué) un parámetro contentType como se muestra en la respuesta de David Lopes arriba.

GarDavis
fuente
0

Parece que Firefox y Opera (también probado en Mac) no les gusta el dominio cruzado de esto (pero Safari está bien con eso).

Puede que tenga que llamar a un código del lado del servidor local para curvar la página remota.

holaandre
fuente
0

Pude solucionarlo con la ayuda de los siguientes encabezados

Access-Control-Allow-Origin
Access-Control-Allow-Headers
Access-Control-Allow-Credentials
Access-Control-Allow-Methods

Si está en Nodejs, aquí está el código que puede copiar / pegar.

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin','*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  res.header('Access-Control-Allow-Credentials', true);
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH');
  next();
});
obai
fuente