Error de jQuery xml 'No hay un encabezado' Access-Control-Allow-Origin 'presente en el recurso solicitado.'

89

Estoy trabajando en este proyecto personal mío solo por diversión, donde quiero leer un archivo xml que se encuentra en http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml y analizar el xml y utilícelo para convertir valores entre las monedas.

Hasta ahora he creado el siguiente código, que es bastante básico para leer el xml, pero aparece el siguiente error.

XMLHttpRequest no puede cargar ****. No hay ningún encabezado 'Access-Control-Allow-Origin' presente en el recurso solicitado. Por lo tanto, no se permite el acceso a Origin ' http://run.jsbin.com '.

$(document).ready( 
    function() {     
        $.ajax({          
            type:  'GET',
            url:   'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml',
            dataType: 'xml',              
            success: function(xml){
                alert('aaa');
            }
         });
    }
);

No veo nada malo en mi código, así que espero que alguien pueda señalar lo que estoy haciendo mal con mi código y cómo podría solucionarlo.

Bazinga777
fuente
2
Le sugiero que lea sobre la Política del Mismo Origen y CORS
jmoerdyk
el error indica exactamente lo que está mal, palabra por palabra. Su código está bien, el problema está en el servidor al que está accediendo.
Kevin B
y también vea CORS en MDN
Amir Ali Akbari

Respuestas:

163

No podrá realizar una llamada ajax http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xmldesde un archivo implementado en http://run.jsbin.comdebido a la política del mismo origen .


Como la página de origen (también conocida como origen ) y la URL de destino se encuentran en dominios diferentes ( run.jsbin.comy www.ecb.europa.eu), su código en realidad está intentando realizar una solicitud entre dominios (CORS) , no una solicitud ordinaria GET.

En pocas palabras, la política del mismo origen dice que los navegadores solo deben permitir llamadas ajax a servicios en el mismo dominio de la página HTML.


Ejemplo:

Una página en http://www.example.com/myPage.htmlsolo puede solicitar directamente servicios que están en http://www.example.com, como http://www.example.com/api/myService. Si el servicio está alojado en otro dominio (digamos http://www.ok.com/api/myService), el navegador no realizará la llamada directamente (como era de esperar). En su lugar, intentará realizar una solicitud CORS.

En pocas palabras, para realizar una solicitud (CORS) * en diferentes dominios, su navegador:

  • Incluirá un Originencabezado en la solicitud original (con el dominio de la página como valor) y lo realizará como de costumbre; y entonces
  • Solo si la respuesta del servidor a esa solicitud contiene los encabezados adecuados ( Access-Control-Allow-Origines uno de ellos ) que permiten la solicitud CORS, el navegador completará la llamada (casi ** exactamente como lo haría si la página HTML estuviera en el mismo dominio).
    • Si los encabezados esperados no vienen, el navegador simplemente se rinde (como lo hizo contigo).


* Lo anterior describe los pasos en una solicitud simple , como una regular GETsin encabezados elegantes. Si la solicitud no es simple (como POSTcon un application/jsontipo de contenido), el navegador la detendrá un momento y, antes de completarla, enviará primero una OPTIONSsolicitud a la URL de destino. Como anteriormente, solo continuará si la respuesta a esta OPTIONSsolicitud contiene los encabezados CORS. Esta OPTIONSllamada se conoce como solicitud de verificación previa.
** Digo casi porque hay otras diferencias entre las llamadas regulares y las llamadas CORS. Uno importante es que algunos encabezados, incluso si están presentes en la respuesta, no serán recogidos por el navegador si no están incluidos en elAccess-Control-Expose-Headers encabezado.


¿Como arreglarlo?

¿Fue solo un error tipográfico? A veces, el código JavaScript tiene un error tipográfico en el dominio de destino. ¿Te fijaste? Si la página está en www.example.com, solo hará llamadas regulares a www.example.com! Otras URL, como api.example.como incluso example.como www.example.com:8080son consideradas dominios diferentes por el navegador. Sí, si el puerto es diferente, entonces es un dominio diferente.

Agrega los encabezados. La forma más sencilla de habilitar CORS es agregando los encabezados necesarios (como Access-Control-Allow-Origin) a las respuestas del servidor. (Cada servidor / idioma tiene una forma de hacerlo; consulte algunas soluciones aquí ).

Último recurso: si no tiene acceso del lado del servidor al servicio, también puede duplicarlo (a través de herramientas como proxies inversos ) e incluir todos los encabezados necesarios allí.

acdcjunior
fuente
2
Gracias, esa es mucha información. Ahora puedo realizar la investigación necesaria para continuar.
Bazinga777
1
Hola acdcjunior, ¿cómo puedo reflejar el servicio web al que quiero acceder?
Franva
2
@Franva Tendrá que configurar un servidor HTTP (por ejemplo, Tomcat, Apache con PHP, IIS con ASP) y colocar una página allí que, para cada solicitud, abra un conector al servicio real (el servicio que está duplicando), solicita los datos reales y luego los da como respuesta. Por supuesto, lo hará a través de código (Java, PHP, ASP, etc.).
acdcjunior
@acdcjunior Por favor, corrígeme si mi entendimiento es correcto. Si ingreso alguna URL en el navegador directamente, se redirigirá a la nueva URL del dominio automáticamente sin Access-Control-Allow-Origin . Por ejemplo, al utilizar WIF, el usuario será redirigido a la página de inicio de sesión de terceros cuando inicie sesión por primera vez.
machinarium
@machinarium No estoy seguro de entender lo que quisiste decir, pero intentaré responder (dime si me equivoqué): si ingresas la URL en la barra de direcciones del navegador, la presencia o ausencia de Access-Control-Allow-Originen esa URL los encabezados no importarán en absoluto: el navegador abrirá la URL como de costumbre. La política del mismo origen (y el requisito del Access-Control-Allow-Originencabezado) solo se aplica a las llamadas Ajax.
acdcjunior
29

Hay una forma hack-tástica de hacerlo si tiene php habilitado en su servidor. Cambie esta línea:

url:   'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml',

a esta línea:

url: '/path/to/phpscript.php',

y luego en el script php (si tiene permiso para usar la función file_get_contents ()):

<?php

header('Content-type: application/xml');
echo file_get_contents("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml");

?>

A Php no parece importarle si esa URL es de un origen diferente. Como dije, esta es una respuesta hacky, y estoy seguro de que hay algo mal en ella, pero funciona para mí.

Editar: si desea almacenar en caché el resultado en php, aquí está el archivo php que usaría:

<?php

$cacheName = 'somefile.xml.cache';
// generate the cache version if it doesn't exist or it's too old!
$ageInSeconds = 3600; // one hour
if(!file_exists($cacheName) || filemtime($cacheName) > time() + $ageInSeconds) {
  $contents = file_get_contents('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml');
  file_put_contents($cacheName, $contents);
}

$xml = simplexml_load_file($cacheName);

header('Content-type: application/xml');
echo $xml;

?>

El código de almacenamiento en caché toma de aquí .

jyapayne
fuente
3
Una solución aún mejor sería almacenar en caché el archivo XML en el lado del servidor y solo realizar la file_get_contentsllamada si el archivo XML más reciente tiene la fecha suficiente. Además, no olvide su encabezado de tipo de contenido :-)
sffc
Tropecé con esta respuesta. Pregunta: ¿Cómo sabe el archivo PHP tomar los datos GET y enviarlos a dicha URL? ¿Funcionaría esto también con datos POSTED?
mpdc
No lo envía. Obtiene el archivo y lo guarda localmente, luego lo arroja como si ese archivo fuera el archivo php que está solicitando localmente. Recuperará y guardará otra copia si el archivo almacenado en caché local es más antiguo que la edad máxima indicada.
Pensado el