jQuery $ .ajax (), $ .post enviando "OPCIONES" como REQUEST_METHOD en Firefox

330

Tener problemas con lo que pensé que era un complemento jQuery relativamente simple ...

El complemento debe obtener datos de un script php a través de ajax para agregar opciones a <select>. La solicitud ajax es bastante genérica:

$.ajax({
  url: o.url,
  type: 'post',
  contentType: "application/x-www-form-urlencoded",
  data: '{"method":"getStates", "program":"EXPLORE"}',
  success: function (data, status) {
    console.log("Success!!");
    console.log(data);
    console.log(status);
  },
  error: function (xhr, desc, err) {
    console.log(xhr);
    console.log("Desc: " + desc + "\nErr:" + err);
  }
});

Esto parece funcionar bien en Safari. En Firefox 3.5, el REQUEST_TYPEen el servidor siempre es 'OPCIONES', y los datos $ _POST no aparecen. Apache registra la solicitud como tipo 'OPCIONES':

::1 - - [08/Jul/2009:11:43:27 -0500] "OPTIONS sitecodes.php HTTP/1.1" 200 46

¿Por qué esta llamada ajax funcionaría en Safari, pero no en Firefox, y cómo lo soluciono para Firefox?

Encabezados de respuesta
Fecha: miércoles, 08 de julio de 2009 21:22:17 GMT
Servidor: Apache / 2.0.59 (Unix) PHP / 5.2.6 DAV / 2
Desarrollado por X: PHP / 5.2.6
Contenido-Longitud 46
Tiempo de espera de Keep-Alive = 15, max = 100
Conexión Keep-Alive
Tipo de contenido text / html

Solicitar encabezados
Formulario de pedido de host: 8888
User-Agent Mozilla / 5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv: 1.9.1) Gecko / 20090624 Firefox / 3.5
Acepte text / html, application / xhtml + xml, application / xml; q = 0.9, * / *; q = 0.8
Accept-Language en-us, en; q = 0.5
Aceptar-codificar gzip, desinflar
Aceptar-Charset ISO-8859-1, utf-8; q = 0.7, *; q = 0.7
Keep-Alive 300
Conexión para mantener vivo
Origen http://ux.inetu.act.org
Método de solicitud de control de acceso POST
Acceso-Control-Solicitud-Encabezados x-solicitado-con

Aquí hay una imagen de la salida de Firebug:

Fitzgeraldsteele
fuente
¿Puede publicar la respuesta de Firebug y solicitar encabezados. No recibo ningún error cuando ejecuto un código similar en Firefox.
MitMaro el
Se agregó información de encabezado y una imagen de Firebug.
fitzgeraldsteele
Acabo de tener este mismo problema al implementar un servidor web incorporado. Gracias por preguntar :)
Robert Gould
Si está buscando soluciones Java JAX-RS, consulte aquí: Access-Control-Allow-Origin
Tobias Sarnow
¿El comportamiento de firefox parece haber cambiado ahora? No recibo ninguna solicitud de opción.
Buge

Respuestas:

169

La razón del error es la misma política de origen. Solo le permite hacer solicitudes XMLHTTP a su propio dominio. Vea si puede usar una devolución de llamada JSONP en su lugar:

$.getJSON( 'http://<url>/api.php?callback=?', function ( data ) { alert ( data ); } );
Jonas Skovmand
fuente
26
¿Por qué Firefox es el único navegador que hace esto? Quiero una publicación, no un get.
Maslow
11
Crossite-POST: ¿Alguien sabe una solución para hacer un POST con application / json como Content-Type?
schoetbi
13
Entonces, ¿cuál es exactamente la solución?
Nik So
3
Buscar una solución para esto también y usar getJSON en lugar de una llamada ajax no lo hace por mí, ya que es mucho más limitado.
Timo Wallenius
1
@schoetbi para eso necesitará usar CORS, que está bien soportado en los navegadores más nuevos ... soporte limitado en IE8-9, y necesita soporte del lado del servidor.
Rastreador1
57

Usé el siguiente código en el lado de Django para interpretar la solicitud de OPCIONES y configurar los encabezados de control de acceso requeridos. Después de esto, mis solicitudes de dominio cruzado de Firefox comenzaron a funcionar. Como se dijo antes, el navegador primero envía la solicitud de OPCIONES y luego inmediatamente después de la POST / GET

def send_data(request):
    if request.method == "OPTIONS": 
        response = HttpResponse()
        response['Access-Control-Allow-Origin'] = '*'
        response['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
        response['Access-Control-Max-Age'] = 1000
        # note that '*' is not valid for Access-Control-Allow-Headers
        response['Access-Control-Allow-Headers'] = 'origin, x-csrftoken, content-type, accept'
        return response
    if request.method == "POST":
        # ... 

Editar: parece ser que, al menos en algunos casos, también debe agregar los mismos encabezados de Control de acceso a la respuesta real. Esto puede ser un poco confuso, ya que la solicitud parece tener éxito, pero Firefox no pasa el contenido de la respuesta al Javascript.

Juha Palomäki
fuente
Su edición sobre la respuesta POST / GET real es un poco aterradora; si alguien puede confirmar eso, ¡háganoslo saber aquí!
Arjan
No sé si es un error o una característica, pero parece ser que alguien más también lo ha notado. Ver por ejemplo kodemaniak.de/?p=62 y buscar "cuerpo de respuesta vacío"
Juha Palomäki
2
Hay una diferencia entre las solicitudes simples y las que necesitan verificación previa. Su "solución" solo funcionará con solicitudes de verificación previa, por lo que no es una solución real. Siempre que obtenga un "Origen:" - encabezado en los encabezados de solicitud, debe responder con eso permitido.
Odinho - Velmont
1
Creo que el encabezado Access-Control-Allow-Headersdebe contener el valor x-csrf-token, no x-csrftoken.
JellicleCat
16

Este artículo del centro de desarrolladores de mozilla describe varios escenarios de solicitud entre dominios. El artículo parece indicar que una solicitud POST con el tipo de contenido de 'application / x-www-form-urlencoded' debe enviarse como una 'solicitud simple' (sin solicitud de OPCIONES 'preflight'). Sin embargo, descubrí que Firefox envió la solicitud de OPCIONES, aunque mi POST se envió con ese tipo de contenido.

Pude hacer que esto funcionara creando un controlador de solicitud de opciones en el servidor, que estableció el encabezado de respuesta 'Access-Control-Allow-Origin' en '*'. Puede ser más restrictivo configurándolo en algo específico, como ' http://someurl.com '. Además, he leído que, supuestamente, puede especificar una lista separada por comas de orígenes múltiples, pero no pude hacer que esto funcione.

Una vez que Firefox recibe la respuesta a la solicitud OPTIONS con un valor aceptable de 'Acceso-Control-Permitir-Origen', envía la solicitud POST.

Mike C
fuente
15

He solucionado este problema utilizando una solución completamente basada en Apache. En mi vhost / htaccess pongo el siguiente bloque:

# enable cross domain access control
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS"

# force apache to return 200 without executing my scripts
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule .* / [R=200,L]

Es posible que no necesite la última parte, dependiendo de lo que suceda cuando Apache ejecute su script de destino. El crédito va a la gente amigable de ServerFault para la última parte.

Mark McDonald
fuente
Su respuesta me ayudó, pero si necesita algo de lógica detrás de CORS, no se resuelve por completo.
Ratata Tata
10

Este PHP en la parte superior del script de respuesta parece funcionar. (Con Firefox 3.6.11. Todavía no he hecho muchas pruebas).

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Max-Age: 1000');
if(array_key_exists('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', $_SERVER)) {
    header('Access-Control-Allow-Headers: '
           . $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
} else {
    header('Access-Control-Allow-Headers: *');
}

if("OPTIONS" == $_SERVER['REQUEST_METHOD']) {
    exit(0);
}
Chad Clark
fuente
Esto podría ser una cuestión de gustos, pero siempre el envío de esas cabeceras de respuesta (también para GET, POST, ...) es un poco demasiado para mi gusto. (Y, me pregunto si siempre enviar esos cumple con las especificaciones?)
Arjan
3
envolverlo en if ($ _ SERVER ['HTTP_ORIGIN']). Si ese encabezado está allí, es una solicitud CORS, si no, bueno, no es necesario enviar nada.
odinho - Velmont
7

Tuve el mismo problema con el envío de solicitudes a Google Maps, y la solución es bastante simple con jQuery 1.5: para uso de tipo de datos dataType: "jsonp"

eslavo
fuente
12
Incompatible con el método POST.
Pavel Vlasov
1
Funciona con un método GET pero es una solución muy limitada. Por ejemplo, al hacerlo, no puede enviar una respuesta con un encabezado específico que incluya un token.
svassr
6

El culpable es la solicitud de verificación previa utilizando el método OPTIONS

Para los métodos de solicitud HTTP que pueden causar efectos secundarios en los datos del usuario (en particular, para métodos HTTP que no sean GET, o para el uso POST con ciertos tipos MIME), la especificación exige que los navegadores "realicen una verificación previa" de la solicitud, solicitando métodos compatibles del servidor con un método de solicitud de OPCIONES HTTP, y luego, tras la "aprobación" del servidor, enviando la solicitud real con el método de solicitud HTTP real.

Las especificaciones web se refieren a: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

Resolví el problema agregando las siguientes líneas en Nginx conf.

    location / {
               if ($request_method = OPTIONS ) {
                   add_header Access-Control-Allow-Origin  "*";
                   add_header Access-Control-Allow-Methods "POST, GET, PUT, UPDATE, DELETE, OPTIONS";
                   add_header Access-Control-Allow-Headers "Authorization";
                   add_header Access-Control-Allow-Credentials  "true";
                   add_header Content-Length 0;
                   add_header Content-Type text/plain;
                   return 200;
               }
    location ~ ^/(xxxx)$ {
                if ($request_method = OPTIONS) {
                    rewrite ^(.*)$ / last;
                }
    }
thinkhy
fuente
1
Esta respuesta es muy útil gracias. El hecho de que el navegador envíe una solicitud de verificación previa con un método OPTIONS no es obvio.
Normangorman
4

Estaba buscando en la fuente 1.3.2, cuando utilizo JSONP, la solicitud se realiza creando un elemento SCRIPT dinámicamente, que supera la política del mismo dominio de los navegadores. Naturalmente, no puede realizar una solicitud POST utilizando un elemento SCRIPT, el navegador obtendrá el resultado utilizando GET.

Como está solicitando una llamada JSONP, el elemento SCRIPT no se genera, porque solo lo hace cuando la llamada Tipo de AJAX se establece en GET.

http://dev.jquery.com/ticket/4690


fuente
4

Tuvimos un problema como este con ASP.Net. Nuestro IIS devolvía un error interno del servidor cuando intentaba ejecutar un jQuery $.postpara obtener contenido html debido a que PageHandlerFactory estaba restringido para responder soloGET,HEAD,POST,DEBUG verbos. Para que pueda cambiar esa restricción agregue el verbo "OPCIONES" a la lista o seleccione "Todos los verbos"

Puede modificar eso en su Administrador de IIS, seleccionando su sitio web, luego seleccionando Asignaciones de controladores, haga doble clic en su PageHandlerFactory para los archivos * .apx según lo necesite (Usamos el grupo de aplicaciones integrado con framework 4.0). Haga clic en Solicitar restricciones, luego vaya a Verbos Tabn y aplique su modificación.

Ahora nuestra $.postsolicitud está funcionando como se esperaba :)

fboiton
fuente
2

Compruebe si la actionURL de su formulario incluye la wwwparte del dominio, mientras que la página original que ha abierto se ve sin ella www.

Típicamente hecho para URL canónicas.

Luché durante horas antes de tropezar con este artículo y encontré la indirecta de Cross Domain.

Bijay Rungta
fuente
2

Parece que si o.url = 'index.php'y este archivo existe está bien y devuelve un mensaje de éxito en la consola. Devuelve un error si uso url:http://www.google.com

Si realiza una solicitud posterior, ¿por qué no utiliza directamente el método $ .post ?

$.post("test.php", { func: "getNameAndTime" },
    function(data){
        alert(data.name); // John
        console.log(data.time); //  2pm
    }, "json");

Es mucho más simple.

Elzo Valugi
fuente
Conseguido lo mismo con esto ... pensé que debería utilizar .ajax $ () para que pudiera obtener al menos alguna información de depuración en la condición de error ..
fitzgeraldsteele
1

La solución a esto es:

  1. use dataType: json
  2. añadir &callback=?a tu url

esto funcionó para llamar a la API de Facebook y con Firefox. Firebug se está utilizando en GETlugar de OPTIONScon las condiciones anteriores (ambas).

Antonio Gulli
fuente
1

Otra posibilidad para eludir el problema es usar un script proxy. Ese método se describe por ejemplo aquí

Niehztog
fuente
0

¿Puedes probar esto sin

contentType:application/x-www-form-urlencoded

Mathias F
fuente
El mismo resultado, me temo.
fitzgeraldsteele
0

Intenta agregar la opción:

dataType: "json"

ScottE
fuente
2
eso funcionó, ¿por qué json se considera "seguro" para solicitudes entre dominios?
Nik So
0
 function test_success(page,name,id,divname,str)
{ 
 var dropdownIndex = document.getElementById(name).selectedIndex;
 var dropdownValue = document.getElementById(name)[dropdownIndex].value;
 var params='&'+id+'='+dropdownValue+'&'+str;
 //makerequest_sp(url, params, divid1);

 $.ajax({
    url: page,
    type: "post",
    data: params,
    // callback handler that will be called on success
    success: function(response, textStatus, jqXHR){
        // log a message to the console
        document.getElementById(divname).innerHTML = response;

        var retname = 'n_district';
        var dropdownIndex = document.getElementById(retname).selectedIndex;
        var dropdownValue = document.getElementById(retname)[dropdownIndex].value;
        if(dropdownValue >0)
        {
            //alert(dropdownValue);
            document.getElementById('inputname').value = dropdownValue;
        }
        else
        {
            document.getElementById('inputname').value = "00";
        }
        return;
        url2=page2; 
        var params2 = parrams2+'&';
        makerequest_sp(url2, params2, divid2);

     }
});         
}
Naser Gulzade
fuente
La pregunta ya fue respondida hace 6 meses. ¿Cómo lo resuelve esto?
Barmar
0

Tuve un problema similar al intentar usar la API de Facebook.

El único tipo de contenido que no envió la solicitud Preflight parecía ser solo texto / sin formato ... no el resto de los parámetros mencionados en Mozilla aquí

  • ¿Por qué es este el único navegador que hace esto?
  • ¿Por qué Facebook no conoce y acepta la solicitud de verificación previa?

FYI: El documento de Moz mencionado anteriormente sugiere que los encabezados de X-Lori deberían desencadenar una solicitud Preflight ... no lo hace.

Dibujó
fuente
0

Necesita hacer un trabajo en el lado del servidor. Veo que está utilizando PHP en el lado del servidor, pero la solución para la aplicación web .NET está aquí: no se puede establecer el tipo de contenido en 'application / json' en jQuery.ajax

Haga lo mismo en el script PHP y funcionará. Simplemente: en la primera solicitud, el navegador pregunta al servidor si puede enviar dichos datos con ese tipo y la segunda solicitud es la correcta / permitida.

Fanda
fuente
0

Intenta agregar lo siguiente:

dataType: "json",
ContentType: "application/json",
data: JSON.stringify({"method":"getStates", "program":"EXPLORE"}),  
Mary Jain
fuente
0

Utilicé una URL de proxy para resolver un problema similar cuando quiero publicar datos en mi servidor apache alojado en otro servidor. (Puede que esta no sea la respuesta perfecta, pero resuelve mi problema).

Siga esta URL: Utilizando Mode-Rewrite para proxy , agrego esta línea a mi httpd.conf:

 RewriteRule ^solr/(.*)$ http://ip:8983/solr$1 [P]

Por lo tanto, puedo publicar datos en / solr en lugar de publicar datos en http: // ip: 8983 / solr / *. Luego publicará datos en el mismo origen.

Bernice
fuente
0

Ya tengo este código manejando bien mi situación de cors en php:

header( 'Access-Control-Allow-Origin: '.CMSConfig::ALLOW_DOMAIN );
header( 'Access-Control-Allow-Headers: '.CMSConfig::ALLOW_DOMAIN );
header( 'Access-Control-Allow-Credentials: true' );

Y funcionaba bien local y remotamente, pero no para cargas cuando es remoto.

Algo sucede con apache / php O mi código, no me molesté en buscarlo, cuando solicitas OPCIONES, devuelve mi encabezado con reglas de cors pero con un resultado 302. Por lo tanto, mi navegador no reconoce como una situación aceptable.

Lo que hice, basado en la respuesta de @Mark McDonald, fue poner este código después de mi encabezado:

if( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' )
{
    header("HTTP/1.1 202 Accepted");
    exit;
}

Ahora, cuando lo solicite OPTIONS, solo enviará el encabezado y el resultado 202.

Ratata Tata
fuente
-1

Por favor tenga en cuenta:

JSONP solo admite el método de solicitud GET.

* Enviar solicitud por firefox : *

$.ajax({
   type: 'POST',//<<===
   contentType: 'application/json',
   url: url,
   dataType: "json"//<<=============
    ...
});

Solicitud anterior enviada por OPTIONS (while ==> type: 'POST' ) !!!!

$.ajax({
    type: 'POST',//<<===
    contentType: 'application/json',
    url: url,
    dataType: "jsonp"//<<==============
    ...
});

Pero por encima de la solicitud enviada por GET (while ==> type: 'POST' ) !!!!

Cuando esté en "comunicación entre dominios", preste atención y tenga cuidado.

M.Namjo
fuente