¿Cómo envío una solicitud POST entre dominios a través de JavaScript?


568

¿Cómo envío una solicitud POST entre dominios a través de JavaScript?

Notas: no debería actualizar la página, y luego necesito obtener y analizar la respuesta.


Me gustaría saber un poco sobre el caso de uso que le permite intentar hacer esto. ¿Podrías decir algo al respecto?
mkoeller

Básicamente, estoy trabajando en un script que necesita enviar algo de texto desde un archivo HTML a otro servidor para su procesamiento.
Ido Schacham

3
¿Puedes configurar un proxy que haga esto en el lado del servidor y solo le dé a tu script el resultado? ¿O necesita ser 100% JavaScript?
Sasha Chedygov

Respuestas:


382

Actualización: antes de continuar, todos deberían leer y comprender el tutorial html5rocks en CORS. Es fácil de entender y muy claro.

Si controla el servidor que se está PUBLICANDO, simplemente aproveche el "Estándar de intercambio de recursos de origen cruzado" configurando encabezados de respuesta en el servidor. Esta respuesta se discute en otras respuestas en este hilo, pero no muy claramente en mi opinión.

En resumen, así es como se realiza la POST entre dominios desde from.com/1.html a to.com/postHere.php (usando PHP como ejemplo). Nota: solo necesita configurar las solicitudes Access-Control-Allow-OriginNO OPTIONS- este ejemplo siempre establece todos los encabezados para un fragmento de código más pequeño.

  1. En postHere.php configura lo siguiente:

    switch ($_SERVER['HTTP_ORIGIN']) {
        case 'http://from.com': case 'https://from.com':
        header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);
        header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS');
        header('Access-Control-Max-Age: 1000');
        header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
        break;
    }

    Esto permite que su script realice POST, GET y OPCIONES entre dominios. Esto quedará claro a medida que continúe leyendo ...

  2. Configure su POST de dominio cruzado desde JS (ejemplo de jQuery):

    $.ajax({
        type: 'POST',
        url: 'https://to.com/postHere.php',
        crossDomain: true,
        data: '{"some":"json"}',
        dataType: 'json',
        success: function(responseData, textStatus, jqXHR) {
            var value = responseData.someKey;
        },
        error: function (responseData, textStatus, errorThrown) {
            alert('POST failed.');
        }
    });

Cuando realiza la POST en el paso 2, su navegador enviará un método de "OPCIONES" al servidor. Este es un "olfateo" por parte del navegador para ver si el servidor es bueno con usted PUBLICANDO. El servidor responde con un "Acceso-Control-Permitir-Origen" diciéndole al navegador que está BIEN POSTAR | OBTENER | ORIGEN si la solicitud se originó en " http://from.com " o " https://from.com ". Dado que el servidor está bien con él, el navegador realizará una segunda solicitud (esta vez una POST). Es una buena práctica que su cliente establezca el tipo de contenido que está enviando, por lo que también deberá permitirlo.

MDN tiene una excelente reseña sobre el control de acceso HTTP , que detalla cómo funciona todo el flujo. Según sus documentos, debería "funcionar en navegadores que admitan XMLHttpRequest entre sitios". Esto es un poco engañoso, sin embargo, como ya PIENSO sólo los navegadores modernos permiten la POST dominios. Solo he verificado que esto funciona con safari, chrome, FF 3.6.

Tenga en cuenta lo siguiente si hace esto:

  1. Su servidor tendrá que manejar 2 solicitudes por operación
  2. Tendrá que pensar en las implicaciones de seguridad. Tenga cuidado antes de hacer algo como 'Access-Control-Allow-Origin: *'
  3. Esto no funcionará en navegadores móviles. En mi experiencia, no permiten POST de dominio cruzado en absoluto. He probado Android, iPad, iPhone
  4. Hay un error bastante grande en FF <3.6 donde si el servidor devuelve un código de respuesta no 400 Y hay un cuerpo de respuesta (errores de validación, por ejemplo), FF 3.6 no obtendrá el cuerpo de respuesta. Este es un gran dolor en el culo, ya que no puedes usar buenas prácticas REST. Ver error aquí (está archivado en jQuery, pero supongo que es un error FF, parece estar solucionado en FF4).
  5. Siempre devuelva los encabezados anteriores, no solo en las solicitudes de OPCIÓN. FF lo necesita en la respuesta de la POST.

¿Puede devolver html por ejemplo? Necesito devolver html y algo no funciona ...
denis_n

Sí, deberías poder hacerlo. Nunca lo intenté aunque. ¿Su servidor devuelve 200? ¿También su servidor devuelve los encabezados en las OPCIONES Y las solicitudes POST? He actualizado mi respuesta con más detalles sobre esto. Asegúrese de que su servidor también responda con el encabezado de tipo de contenido correcto (como text / html). Mi recomendación es usar google chrome, haga clic derecho en la página> inspeccionar elemento. Haga clic en la pestaña de red y mire la POST y la respuesta. Debería darte información sobre lo que va mal.
rynop

He intentado esto, pero aún así obtener 400 Bad Requestel OPTIONSpedido. y en firefoxla segunda solicitud de POSTnunca se hace. :(
Zain Shaikh

¿Hay alguna forma de llamar a su máquina local en la declaración de su caso anterior? ¿O simplemente tiene que usar * en este caso para los orígenes permitidos?
Todd Vance

1
Esto fue editado por última vez hace 4 años. ¿Funcionará en navegadores móviles ahora?
frankpinto

121

Si controla el servidor remoto, probablemente debería usar CORS, como se describe en esta respuesta ; es compatible con IE8 y versiones posteriores, y todas las versiones recientes de FF, GC y Safari. (Pero en IE8 y 9, CORS no le permitirá enviar cookies en la solicitud).

Por lo tanto, si no controla el servidor remoto, o si tiene que admitir IE7, o si necesita cookies y debe admitir IE8 / 9, es probable que desee utilizar una técnica de iframe.

  1. Crea un iframe con un nombre único. (los iframes usan un espacio de nombres global para todo el navegador, así que elija un nombre que ningún otro sitio web usará).
  2. Construya un formulario con entradas ocultas, apuntando al iframe.
  3. Envíe el formulario

Aquí hay un código de muestra; Lo probé en IE6, IE7, IE8, IE9, FF4, GC11, S5.

function crossDomainPost() {
  // Add the iframe with a unique name
  var iframe = document.createElement("iframe");
  var uniqueString = "CHANGE_THIS_TO_SOME_UNIQUE_STRING";
  document.body.appendChild(iframe);
  iframe.style.display = "none";
  iframe.contentWindow.name = uniqueString;

  // construct a form with hidden inputs, targeting the iframe
  var form = document.createElement("form");
  form.target = uniqueString;
  form.action = "http://INSERT_YOUR_URL_HERE";
  form.method = "POST";

  // repeat for each parameter
  var input = document.createElement("input");
  input.type = "hidden";
  input.name = "INSERT_YOUR_PARAMETER_NAME_HERE";
  input.value = "INSERT_YOUR_PARAMETER_VALUE_HERE";
  form.appendChild(input);

  document.body.appendChild(form);
  form.submit();
}

¡Tener cuidado! No podrá leer directamente la respuesta de la POST, ya que el iframe existe en un dominio separado. Los marcos no pueden comunicarse entre sí desde diferentes dominios; Esta es la política del mismo origen .

Si controla el servidor remoto pero no puede usar CORS (por ejemplo, porque está en IE8 / IE9 y necesita usar cookies), hay formas de evitar la política del mismo origen, por ejemplo, usando window.postMessagey / o una de una serie de bibliotecas que le permite enviar mensajes de marcos cruzados de dominios cruzados en navegadores antiguos:

Si no controla el servidor remoto, entonces no puede leer la respuesta de la POST, punto. De lo contrario, causaría problemas de seguridad.


2
Deberá establecer form.target en algo, o el navegador se desplazará fuera de su sitio a la URL de acción del formulario. Además, la cadena debe ser única; Si hay otros marcos o ventanas con el mismo nombre, el formulario podría publicar en esa ventana en lugar de su iframe. Pero, ¿qué tan único tiene que ser? Probablemente no muy. Las probabilidades de golpear son bastante pequeñas. encogimiento de hombros
Dan Fabulich

1
@Nawaz Como dije en mi respuesta, tendrá que hacer una comunicación entre marcos cruzados para obtener el resultado en su página web. Requiere que controle el servidor web remoto para que pueda modificar su respuesta para permitir la comunicación con su página web. (Por un lado, el servidor necesitará responder con HTML; si el servidor responde con XML sin procesar, no puede hacer comunicación entre marcos).
Dan Fabulich

1
+1: esta es la mejor solución que he encontrado si no tiene acceso al servidor
James Long

1
@VojtechB No, eso sería un agujero de seguridad.
Dan Fabulich

1
@Andrus ¡Puedes leer el resultado de la POST, pero solo si controlas el servidor! Ver en esa respuesta, dice "hacer X en el remitente [cliente], hacer Y en el receptor [servidor]". Si no controla el receptor / servidor, no puede hacer Y, por lo que no puede leer el resultado de la POST.
Dan Fabulich

48
  1. Crea un iFrame,
  2. poner un formulario con entradas ocultas,
  3. establece la acción del formulario en la URL,
  4. Agregar iframe al documento
  5. enviar el formulario

Pseudocódigo

 var ifr = document.createElement('iframe');
 var frm = document.createElement('form');
 frm.setAttribute("action", "yoururl");
 frm.setAttribute("method", "post");

 // create hidden inputs, add them
 // not shown, but similar (create, setAttribute, appendChild)

 ifr.appendChild(frm);
 document.body.appendChild(ifr);
 frm.submit();

Probablemente desee diseñar el iframe, para que esté oculto y en una posición absoluta. No estoy seguro de que el navegador permita la publicación en sitios cruzados, pero si es así, así es como se hace.


44
En realidad, esto es un poco inexacto, ya que ifr.appendChild (frm); no trabajará. el iframe es una referencia a un objeto de ventana, y el método appendChild no existe para él. Primero deberá tomar el nodo del documento en el iframe. Esto requiere la detección de funciones para funcionar en todos los navegadores.
Rakesh Pai


19
¡Problema! La respuesta recibida en el iframe se encuentra en un dominio diferente, por lo que la ventana principal no tiene acceso a ella, ni el iframe tiene acceso a la ventana principal. Entonces, esta solución solo parece buena para hacer la POST, pero no puede analizar la respuesta después :(
Ido Schacham

2
Intente establecer una carga en la etiqueta del cuerpo de la respuesta a una función de JavaScript que llama a una función en el padre con la cadena de respuesta.
Lou Franco

Esta respuesta no funcionó para mí; Publiqué mi propia variación a continuación.
Dan Fabulich,

24

Mantenlo simple:

  1. POST entre dominios:
    usocrossDomain: true,

  2. no debe actualizar la página:
    No, no se actualizará la página que elsuccessoerrorasincrónica de devolución de llamada se llamará cuando el servidor de envío de la respuesta.


Script de ejemplo:

$.ajax({
        type: "POST",
        url: "http://www.yoururl.com/",
        crossDomain: true,
        data: 'param1=value1&param2=value2',
        success: function (data) {
            // do something with server response data
        },
        error: function (err) {
            // handle your error logic here
        }
    });

8
crossDomain: trueCuriosamente no tiene absolutamente nada que ver con las solicitudes reales entre dominios. Si la solicitud es de dominio cruzado, jquery establece esto en verdadero de forma automática.
Kevin B

16

Si tiene acceso a todos los servidores involucrados, coloque lo siguiente en el encabezado de la respuesta para la página que se solicita en el otro dominio:

PHP:

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

Por ejemplo, en el código xmlrpc.php de Drupal harías esto:

function xmlrpc_server_output($xml) {
    $xml = '<?xml version="1.0"?>'."\n". $xml;
    header('Connection: close');
    header('Content-Length: '. strlen($xml));
    header('Access-Control-Allow-Origin: *');
    header('Content-Type: application/x-www-form-urlencoded');
    header('Date: '. date('r'));
    // $xml = str_replace("\n", " ", $xml); 

    echo $xml;
    exit;
}

Esto probablemente crea un problema de seguridad, y debe asegurarse de tomar las medidas adecuadas para verificar la solicitud.



6
  1. Cree dos iframes ocultos (agregue "display: none;" al estilo css). Haga que su segundo iframe apunte a algo en su propio dominio.

  2. Cree un formulario oculto, establezca su método para "publicar" con target = su primer iframe y, opcionalmente, establezca enctype en "multipart / form-data" (creo que quiere hacer POST porque desea enviar datos multiparte como imágenes ?)

  3. Cuando esté listo, haga que el formulario envíe () la POST.

  4. Si puede hacer que el otro dominio devuelva javascript que hará la comunicación entre dominios con marcos flotantes ( http://softwareas.com/cross-domain-communication-with-iframes ), entonces está de enhorabuena y puede capturar la respuesta también.

Por supuesto, si desea utilizar su servidor como proxy, puede evitar todo esto. Simplemente envíe el formulario a su propio servidor, que enviará la solicitud al otro servidor (suponiendo que el otro servidor no esté configurado para notar discrepancias de IP), obtenga la respuesta y devuelva lo que desee.


6

¡Una cosa más importante a tener en cuenta! En el ejemplo anterior se describe cómo usar

$.ajax({
    type     : 'POST',
    dataType : 'json', 
    url      : 'another-remote-server',
    ...
});

JQuery 1.6 y versiones anteriores tienen un error con XHR entre dominios. Según Firebug, no se enviaron solicitudes, excepto OPCIONES. Sin POST. En absoluto.

Pasé 5 horas probando / afinando mi código. Agregar muchos encabezados en el servidor remoto (script). Sin ningún efecto Pero más tarde, actualicé JQuery lib a 1.6.4, y todo funciona de maravilla.


Whoopps, no en Opera 10.61. Mi decisión final de hacer esto fue usar el proxy PHP en mi dominio.
BasTaller

¿Cómo usaste el proxy PHP? ¿Me puede guiar en eso?
Zoran777

vea las respuestas a continuación, por ejemplo, por Ivan Durst
BasTaller

5

Si desea hacer esto en un entorno ASP.net MVC con JQuery AJAX, siga estos pasos: (este es un resumen de la solución ofrecida en este hilo)

Suponga que "caller.com" (puede ser cualquier sitio web) necesita publicar en "server.com" (una aplicación ASP.net MVC)

  1. En la aplicación "server.com" Web.config agregue la siguiente sección:

      <httpProtocol>
          <customHeaders>
              <add name="Access-Control-Allow-Origin" value="*" />
              <add name="Access-Control-Allow-Headers" value="Content-Type" />
              <add name="Access-Control-Allow-Methods" value="POST, GET, OPTIONS" />
          </customHeaders>
      </httpProtocol>
  2. En "server.com", tendremos la siguiente acción en el controlador (llamado "Inicio") en el que publicaremos:

    [HttpPost]
    public JsonResult Save()
    {
        //Handle the post data...
    
        return Json(
            new
            {
                IsSuccess = true
            });
    }
  3. Luego, desde "caller.com", publique datos de un formulario (con la identificación html "formId") en "server.com" de la siguiente manera:

    $.ajax({
            type: "POST",
            url: "http://www.server.com/home/save",
            dataType: 'json',
            crossDomain: true,
            data: $(formId).serialize(),
            success: function (jsonResult) {
               //do what ever with the reply
            },
            error: function (jqXHR, textStatus) {
                //handle error
            }
        });

4

Hay una forma más (usando la función html5). Puede usar un iframe proxy alojado en ese otro dominio, enviar un mensaje usando postMessage a ese iframe, luego ese iframe puede hacer una solicitud POST (en el mismo dominio) y postMessage de vuelta con reposicionar a la ventana principal.

padre en sender.com

var win = $('iframe')[0].contentWindow

function get(event) {
    if (event.origin === "http://reciver.com") {
        // event.data is response from POST
    }
}

if (window.addEventListener){
    addEventListener("message", get, false)
} else {
    attachEvent("onmessage", get)
}
win.postMessage(JSON.stringify({url: "URL", data: {}}),"http://reciver.com");

iframe en reciver.com

function listener(event) {
    if (event.origin === "http://sender.com") {
        var data = JSON.parse(event.data);
        $.post(data.url, data.data, function(reponse) {
            window.parent.postMessage(reponse, "*");
        });
    }
}
// don't know if we can use jQuery here
if (window.addEventListener){
    addEventListener("message", listener, false)
} else {
    attachEvent("onmessage", listener)
}

Hay una pregunta relacionada en stackoverflow.com/questions/38940932/… . ¿Es posible crear algún complemento o función genérica basada en su muestra?
Andrus


Este código requiere modificar la página del receptor. ¿Cómo leer la respuesta si las páginas del receptor no pueden modificarse?
Andrus

@Andrus no puede necesitar tener acceso al iframe de recever.com para enviar solicitudes ajax allí Sin iframe no habrá solicitudes.
jcubic

3

Nivel alto ... Debe tener una configuración de cname en su servidor para que other-serve.your-server.com apunte a other-server.com.

Su página crea dinámicamente un iframe invisible, que actúa como su transporte a other-server.com. Luego debe comunicarse a través de JS desde su página a other-server.com y recibir devoluciones de llamada que devuelvan los datos a su página.

Posible pero requiere coordinación de your-server.com y other-server.com


Ni siquiera pensé en usar un CNAME para redirigir. ¡Buena llamada! Todavía tengo que intentar esto, pero supongo que el CNAME engañará al navegador para que piense que está interactuando con el mismo sitio. Lo usaré para publicar en Amazon S3, así que espero que esto funcione.
SpencerElliott

1
No veo cómo esto resolvería algo. cruzar a un subdominio diferente tiene los mismos problemas que cruzar a un dominio diferente.
Octopus


2

Esta es una vieja pregunta, pero alguna tecnología nueva podría ayudar a alguien.

Si tiene acceso administrativo al otro servidor, puede usar el proyecto Forge de código abierto para realizar su POST entre dominios. Forge proporciona un contenedor XmlHttpRequest de dominio cruzado que aprovecha la API de socket sin procesar de Flash. La POST puede incluso hacerse sobre TLS.

La razón por la que necesita acceso administrativo al servidor al que está PUBLICANDO es porque debe proporcionar una política entre dominios que permita el acceso desde su dominio.

http://github.com/digitalbazaar/forge


2

Sé que esta es una vieja pregunta, pero quería compartir mi enfoque. Yo uso cURL como proxy, muy fácil y consistente. Cree una página php llamada submit.php y agregue el siguiente código:

<?

function post($url, $data) {
$header = array("User-Agent: " . $_SERVER["HTTP_USER_AGENT"], "Content-Type: application/x-www-form-urlencoded");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}

$url = "your cross domain request here";
$data = $_SERVER["QUERY_STRING"];
echo(post($url, $data));

Luego, en tu js (jQuery aquí):

$.ajax({
type: 'POST',
url: 'submit.php',
crossDomain: true,
data: '{"some":"json"}',
dataType: 'json',
success: function(responseData, textStatus, jqXHR) {
    var value = responseData.someKey;
},
error: function (responseData, textStatus, errorThrown) {
    alert('POST failed.');
}
});

1

Debería ser posible con una tabla personalizada YQL + JS XHR, eche un vistazo a: http://developer.yahoo.com/yql/guide/index.html

Lo uso para hacer algunos raspados html del lado del cliente (js), funciona bien (tengo un reproductor de audio completo, con búsqueda en internet / listas de reproducción / letras / última información de fm, todos los clientes js + YQL)


1

CORS es para ti. CORS es "Cross Origin Resource Sharing", es una forma de enviar una solicitud de dominio cruzado. Ahora XMLHttpRequest2 y Fetch API son compatibles con CORS, y puede enviar solicitudes POST y GET

Pero tiene sus límites. El servidor debe reclamar específicamente Access-Control-Allow-Origin , y no se puede establecer en '*'.

Y si desea que cualquier origen pueda enviarle una solicitud, necesita JSONP (también necesita establecer Access-Control-Allow-Origin , pero puede ser '*')

Para muchas solicitudes, si no sabe cómo elegir, creo que necesita un componente funcional completo para hacerlo. Permítame presentarle un componente simple https://github.com/Joker-Jelly/catta


Si está utilizando un navegador moderno (> IE9, Chrome, FF, Edge, etc.), le recomiendo que use un componente simple pero atractivo https://github.com/Joker-Jelly/catta . No tiene dependencia, Menos de 3 KB, y es compatible con Fetch, AJAX y JSONP con la misma sintaxis y opciones de muestra letales.

catta('./data/simple.json').then(function (res) {
  console.log(res);
});

También admite todo el camino para importar a su proyecto, como el módulo ES6, CommonJS e incluso <script>en HTML.


1

Si tiene acceso al servidor de dominio cruzado y no desea realizar ningún cambio de código en el lado del servidor, puede usar una biblioteca llamada - 'xdomain'.

Cómo funciona:

Paso 1: servidor 1: incluya la biblioteca xdomain y configure el dominio cruzado como esclavo:

<script src="js/xdomain.min.js" slave="https://crossdomain_server/proxy.html"></script>

Paso 2: en el servidor de dominio cruzado, cree un archivo proxy.html e incluya el servidor 1 como maestro:

proxy.html:
<!DOCTYPE HTML>
<script src="js/xdomain.min.js"></script>
<script>
  xdomain.masters({
    "https://server1" : '*'
  });
</script>

Paso 3:

Ahora, puede hacer una llamada AJAX al proxy.html como punto final desde el servidor1. Esto es omitir la solicitud CORS. La biblioteca utiliza internamente una solución de iframe que funciona con credenciales y todos los métodos posibles: GET, POST, etc.

Consulta el código ajax:

$.ajax({
        url: 'https://crossdomain_server/proxy.html',
        type: "POST",
        data: JSON.stringify(_data),
        dataType: "json",
        contentType: "application/json; charset=utf-8"
    })
    .done(_success)
    .fail(_failed)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.