Usando PUT / POST / DELETE con JSONP y jQuery


Estoy trabajando para crear una API RESTful que admita solicitudes entre dominios, compatibilidad con JSON/JSONP y el método HTTP principal (PUT/GET/POST/DELETE). Ahora, si bien será fácil acceder a esta API a través del código del lado del servidor, sería bueno exponerlo a javascript. Por lo que puedo decir, cuando se hace un JSONP peticiones con jQuery, solo es compatible con el método GET. ¿Hay alguna forma de hacer una solicitud JSONP usando POST / PUT / DELETE?

Idealmente me gustaría una forma de hacer esto desde dentro de jQuery (a través de un plugin si el núcleo no es compatible con esto), pero voy a tomar una solución de javascript también. Cualquier enlace al código de trabajo o cómo codificarlo sería útil, gracias.

Author: ryanzec, 2011-03-18

4 answers

En realidad - hay una manera de apoyar las solicitudes POST. Y no hay necesidad en un servidor PROXI, solo una pequeña página HTML de utilidad que se describe a continuación.

Así es como obtienes Efectivamente una llamada POST-cross-domain, incluyendo archivos adjuntos y multi-parte y todo:)

Aquí primero están los pasos en entendiendo la idea, después de eso - encuentre una muestra de implementación.

Cómo se implementa JSONP de jQuery y por qué no soporta POST ¿peticiones?

Mientras que el JSONP tradicional se implementa creando un elemento de script y anexándolo al DOM - lo que resulta en forzar al navegador a disparar una solicitud HTTP para recuperar el origen de la etiqueta, y luego ejecutarla como JavaScript, la solicitud HTTP que el navegador dispara es simple GET.

¿Qué no está limitado a OBTENER solicitudes?

UN FORMULARIO. Envíe el FORMULARIO mientras especifica action el servidor de dominios cruzados. Se puede crear una etiqueta de FORMULARIO usando completamente un script, rellenado con todos los campos usando script, establecer todos los atributos necesarios, inyectado en el DOM, y luego enviado-todo usando script.

Pero, ¿cómo podemos enviar un FORMULARIO sin actualizar la página?

Especificamos el target el formulario a un IFRAME en la misma página. Un IFRAME también se puede crear, establecer, nombrar e inyectar en el DOM usando script.

Pero, ¿cómo podemos ocultar este trabajo al usuario? Vamos a contener tanto la FORMA y IFRAME en un DIV oculto usando style="display:none"

(y aquí está la parte más complicada de la técnica, sé paciente)

Pero IFRAME de otro dominio no puede llamar a una devolución de llamada en su documento de nivel superior. Cómo superar eso?

De hecho , si una respuesta de FORM submit es una página de otro dominio, cualquier comunicación de script entre la página de nivel superior y la página en el IFRAME resulta en "acceso denegado". Por lo tanto, el servidor no puede devolver la llamada usando un script. ¿Qué puede el servidor puede hacer? redireccionar . El servidor puede redirigir a cualquier página, incluidas las páginas del mismo dominio que el documento de nivel superior, páginas que pueden invocar la devolución de llamada para nosotros.

¿Cómo puede redirigir un servidor?

De dos maneras:

  1. Usando script del lado del cliente como <Script>location.href = 'some-url'</script>
  2. Usando HTTP-Header. Véase: http://www.webconfs.com/how-to-redirect-a-webpage.php

¿Así que termino con otra página? Cómo ayuda ¿yo?

Esta es una página de utilidad simple que se utilizará desde todas las llamadas entre dominios. En realidad, esta página es de hecho una especie de proxi, pero no es un servidor, sino una página HTML simple y estática, que cualquiera con bloc de notas y un navegador puede usar.

Todo lo que tiene que hacer esta página es invocar la devolución de llamada en el documento de nivel superior, con los datos de respuesta del servidor. El scripting del lado del cliente tiene acceso a todas las partes de la URL, y el servidor puede poner su respuesta allí codificada como parte de ella, así como el nombre de la devolución de llamada que tiene que ser invocada. Significa - esta página puede ser una página estática y HTML, y no tiene que ser una página dinámica del lado del servidor:)

Esta página de utilidad tomará la información de la URL en la que se ejecuta, específicamente en mi implementación a continuación, los parámetros de cadena de consulta (o puede escribir su propia implementación usando anchor - ID - es decir, la parte de una url directamente al signo"#"). Y dado que esta página es estática, incluso se puede permitir que sea caché :)

¿No agregará para cada solicitud de POST un DIV, un SCRIPT y un IFRAME eventualmente fugas de memoria?

Si lo dejas en la página - lo hará. Si usted limpia después de usted-no lo hará. Todo lo que tenemos que hacer es dar un ID al DIV que podemos usar para celan-up el DIV y el FORMULARIO e IFRAME dentro de él cada vez que la respuesta llegue del servidor, o se agote el tiempo.

¿Qué obtenemos?

Efectivamente una llamada POST-cross-domain, incluyendo archivos adjuntos y multi-parte y todos:)

¿cuáles son los límites?

  • La respuesta del servidor se limita a lo que cabe en una redirección.
  • El servidor siempre debe devolver una REDIRECCIÓN a un POST requests. Que incluyen errores 404 y 500. Alternativamente, cree un tiempo de espera en el cliente justo antes de disparar la solicitud, para que tenga la oportunidad de detectar las solicitudes que no han regresado.
  • no todo el mundo puede entender todo esto y todas las etapas involucrados. es una especie de trabajo a nivel de infraestructura, pero una vez que lo haces funcionar, es genial:)

¿Puedo usarlo para llamadas PUT y DELETE?

La etiqueta de FORMULARIO no PONE y ELIMINA. Pero eso es mejor que nada:)

Ok, entendí el concepto. ¿Cómo se hace técnicamente?

Lo que hago es:{[14]]}

Creo el DIV, lo estilo como invisible, y lo añado al DOM. También le doy una identificación que puedo limpiar desde el DOM después de la respuesta del servidor ha llegado (de la misma manera jQuery limpia es tasgs SCRIPT JSONP - pero el DIV).

Luego compongo una cadena que contiene tanto IFRAME como FORM - con todos los atributos, propiedades y campos de entrada, y la inyecto en el DIV invisible. es importante inyectar esta cadena en el DIV solo DESPUÉS de que el div esté en el DOM. Si no, no funcionará en todos los navegadores.

Después de eso, obtengo una referencia al FORMULARIO y lo envío. Sólo recuerda una línea antes de eso-para establecer una devolución de llamada Timeout en caso de que el servidor no responda, o responda de manera incorrecta.

La función callback contiene el código de limpieza. También es llamado por timer en caso de un tiempo de espera de respuesta (y limpia su tiempo de espera cuando llega una respuesta del servidor).

¡Muéstrame el código!

El fragmento de código de abajo es totalmente "neutral" en javascript "puro", y declara cualquier utilidad que necesite. Solo por simplificación de explicar la idea-todo se ejecuta en el ámbito global, sin embargo, debe ser un poco más sofisticado...

Organícelo en funciones como pueda y parametrice lo que necesita , pero asegúrese de que todas las partes que necesitan verse entre sí se ejecuten en el mismo ámbito:)

Para este ejemplo - supongamos que el cliente se ejecuta en http://samedomain.com y el servidor se ejecuta en http://crossdomain.com .

El código de script en el nivel superior documento

//declare the Async-call callback function on the global scope
function myAsyncJSONPCallback(data){
    //clean up 
    var e = document.getElementById(id);
    if (e) e.parentNode.removeChild(e);
    clearTimeout(timeout);

    if (data && data.error){
        //handle errors & TIMEOUTS
        //...
        return;
    }

    //use data
    //...
}

var serverUrl          = "http://crossdomain.com/server/page"
  , params = { param1  : "value of param 1"      //I assume this value to be passed
             , param2  : "value of param 2"      //here I just declare it...
             , callback: "myAsyncJSONPCallback" 
             }
  , clientUtilityUrl   = "http://samedomain.com/utils/postResponse.html"
  , id     = "some-unique-id"// unique Request ID. You can generate it your own way
  , div    = document.createElement("DIV")       //this is where the actual work start!
  , HTML   = [ "<IFRAME name='ifr_",id,"'></IFRAME>"  
             , "<form target='ifr_",id,"' method='POST' action='",serverUrl 
             , "' id='frm_",id,"' enctype='multipart/form-data'>"
             ]
  , each, pval, timeout;

//augment utility func to make the array a "StringBuffer" - see usage bellow
HTML.add = function(){
              for (var i =0; i < arguments.length; i++) 
                  this[this.length] = arguments[i];
           }

//add rurl to the params object - part of infrastructure work
params.rurl = clientUtilityUrl //ABSOLUTE URL to the utility page must be on
                               //the SAME DOMAIN as page that makes the request

//add all params to composed string of FORM and IFRAME inside the FORM tag  
for(each in params){
    pval = params[each].toString().replace(/\"/g,"&quot;");//assure: that " mark will not break
    HTML.add("<input name='",each,"' value='",pval,"'/>"); //        the composed string      
}
//close FORM tag in composed string and put all parts together
HTML.add("</form>");
HTML = HTML.join("");   //Now the composed HTML string ready :)

//prepare the DIV
div.id = id; // this ID is used to clean-up once the response has come, or timeout is detected
div.style.display = "none"; //assure the DIV will not influence UI

//TRICKY: append the DIV to the DOM and *ONLY THEN* inject the HTML in it
//        for some reason it works in all browsers only this way. Injecting the DIV as part 
//        of a composed string did not always work for me
document.body.appendChild(div);
div.innerHTML = HTML;

//TRICKY: note that myAsyncJSONPCallback must see the 'timeout' variable
timeout = setTimeout("myAsyncJSONPCallback({error:'TIMEOUT'})",4000);
document.getElementById("frm_"+id+).submit();

El servidor en el cross-domain Se espera que la respuesta del servidor sea una REDIRECCIÓN, ya sea por encabezado HTTP o escribiendo una etiqueta de SCRIPT. (la redirección es mejor, la etiqueta de SCRIPT es más fácil de depurar con puntos de interrupción JS). Aquí está el ejemplo del encabezado, asumiendo el valor rurl desde arriba

Location: http://samedomain.com/HTML/page?callback=myAsyncJSONPCallback&data=whatever_the_server_has_to_return

Tenga en cuenta que

  • el valor del argumento data puede ser un Objeto JavaScript-Literal o una expresión JSON, sin embargo, mejor que sea codificado en URL.
  • la longitud de la respuesta del servidor está limitada a la longitud de una URL que un navegador puede procesar.

También - en mi sistema el servidor tiene un valor por defecto para el rurl por lo que este parámetro es opcional. Pero puede hacerlo solo si su aplicación cliente y aplicación servidor están acopladas.

Api para emitir encabezado de redirección:

Http://www.webconfs.com/how-to-redirect-a-webpage.php

Alternativamente, puede tener el el servidor escribe como respuesta lo siguiente:

<script>
   location.href="http://samedomain.com/HTML/page?callback=myAsyncJSONPCallback&data=whatever_the_server_has_to_return"
</script>

Pero los encabezados HTTP se considerarían más limpios;)

La página de utilidad en el mismo dominio que el documento de nivel superior

Utilizo la misma página de utilidad que rurl para todas mis solicitudes post: todo lo que hace es tomar el nombre de la devolución de llamada y los parámetros de la Cadena de consulta usando código del lado del cliente, y llamarlo en el documento padre. Puede hacerlo SOLO cuando esta página se ejecuta en el mismo dominio como la página que disparó la solicitud! Importante: A diferencia de las cookies, ¡los subdominios no cuentan!! Tiene exactamente el mismo dominio.

También es más eficiente si esta página de utilidad no contiene referencias a otros recursos, incluidas las bibliotecas JS. Así que esta página es JavaScript simple. Pero puedes implementarlo como quieras.

Aquí está la página del respondedor que uso, la URL de quién se encuentra en el rurl de la solicitud POST (en el ejemplo: http://samedomain.com/utils/postResponse.html )

<html><head>
<script type="text/javascript">
//parse and organize all QS parameters in a more comfortable way
var params = {};
if (location.search.length > 1) {
    var i, arr = location.search.substr(1).split("&");
    for (i = 0; i < arr.length; i++) {
        arr[i] = arr[i].split("=");
        params[arr[i][0]] = unescape(arr[i][1]);
    }
}

//support server answer as JavaScript Object-Literals or JSON:
//  evaluate the data expression
try { 
    eval("params.data = " + params.data); 
} catch (e) { 
    params.data = {error: "server response failed with evaluation error: " + e.message
                  ,data : params.data
                  }
}

//invoke the callback on the parent
try{
     window.parent[ params.callback ](params.data || "no-data-returned");
}catch(e){
     //if something went wrong - at least let's learn about it in the
     //      console (in addition to the timeout)
     throw "Problem in passing POST response to host page: \n\n" + e.message;
}
</script>
</head><body></body></html>

No es mucha automatización y biblioteca' ready-made 'como jQuery e involucra algo de trabajo 'manual', pero tiene el encanto:)

Si usted es un gran fan de las bibliotecas ya hechas-también puede comprobar en Dojo Toolkit que la última vez que comprobé (hace aproximadamente un año) - tenía su propia implementación para el mismo mecanismo. http://dojotoolkit.org /

Buena suerte amigo, espero que ayude...

 70
Author: Radagast the Brown,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-11 23:04:21

¿Hay alguna forma de hacer una solicitud JSONP usando POST/PUT/DELETE?

No, no lo hay.

 12
Author: Darin Dimitrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-17 21:31:05

No. Considere qué es JSONP: una inyección de una nueva etiqueta <script> en el documento. El navegador realiza una solicitud GET para extraer el script al que apunta el atributo src. No hay forma de especificar ningún otro verbo HTTP al hacer esto.

 8
Author: Wayne Burkett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-17 21:37:30
  • En lugar de golpearnos la cabeza con el método JSONP, eso en realidad no soporte POST método por defecto, podemos ir para CORS .Eso no proporcionará grandes cambios en la forma convencional de programación. Por simple llamada Jquery Ajax podemos ir con dominios cruzados.
  • En el método CORS, debe agregar encabezados en el archivo de scripting del lado del servidor, o en el propio servidor(en dominio remoto), para habilitar este acceso. Esto es muy confiable, ya que podemos prevenir / restringir la creación de dominios llamadas no deseadas.
  • Se puede encontrar en detalle en la página wikipedia.
 2
Author: Ganesh Babu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-23 10:25:13