Ошибка jQuery xml «На запрошенном ресурсе отсутствует заголовок« Access-Control-Allow-Origin ».

89

Я работаю над своим личным проектом просто для удовольствия, где я хочу прочитать файл xml, расположенный по адресу http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml, и проанализировать xml и используйте его для преобразования значений между валютами.

До сих пор я придумал приведенный ниже код, который является довольно простым для чтения xml, но я получаю следующую ошибку.

XMLHttpRequest не может загрузить ****. На запрошенном ресурсе нет заголовка Access-Control-Allow-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');
            }
         });
    }
);

Я не вижу ничего плохого в моем коде, поэтому я надеюсь, что кто-то может указать, что я делаю не так с моим кодом и как я могу это исправить.

Bazinga777
источник
2
Я предлагаю вам прочитать об одной и той же политике происхождения и CORS
jmoerdyk
ошибка точно указывает, что не так, слово в слово. Ваш код в порядке, проблема с сервером, к которому вы обращаетесь.
Kevin B
а также смотрите CORS на MDN
Амир Али Акбари

Ответы:

163

Вы не сможете выполнить ajax-вызов http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xmlиз файла, развернутого в, http://run.jsbin.comиз -за политики того же происхождения .


Поскольку исходная страница (также известная как origin ) и целевой URL-адрес находятся в разных доменах ( run.jsbin.comи www.ecb.europa.eu), ваш код на самом деле пытается сделать междоменный (CORS) запрос, а не обычный GET.

Короче говоря, политика одного и того же происхождения гласит, что браузеры должны разрешать вызовы ajax только к службам в том же домене HTML-страницы.


Пример:

Страница по адресу http://www.example.com/myPage.htmlможет напрямую запрашивать только те услуги, которые находятся по адресу http://www.example.com, например http://www.example.com/api/myService. Если служба размещена в другом домене (скажем http://www.ok.com/api/myService), браузер не будет выполнять вызов напрямую (как и следовало ожидать). Вместо этого он попытается сделать запрос CORS.

Короче говоря, для выполнения запроса (CORS) * в разных доменах ваш браузер:

  • Включит Originзаголовок в исходный запрос (с доменом страницы в качестве значения) и выполнит его как обычно; а потом
  • Только если сервер ответ на этот запрос содержит адекватные заголовки ( Access-Control-Allow-Originэто один из них ) позволяя CORS запрос, просматривает завершит вызов (почти ** именно так , как это было бы , если страница HTML была в том же домене).
    • Если ожидаемые заголовки не приходят, браузер просто отказывается (как и вы).


* Выше показаны шаги в простом запросе, например в обычном запросе GETбез дополнительных заголовков. Если запрос не является простым (например, POSTс application/jsonтипом содержимого as), браузер задержит его на мгновение и, прежде чем выполнить его, сначала отправит OPTIONSзапрос на целевой URL. Как и выше, он будет продолжаться только в том случае, если ответ на этот OPTIONSзапрос содержит заголовки CORS. Этот OPTIONSвызов называется предполетным запросом.
** Я говорю почти потому, что есть другие различия между обычными вызовами и вызовами CORS. Важным является то, что некоторые заголовки, даже если они присутствуют в ответе, не будут приняты браузером, если они не включены вAccess-Control-Expose-Headers заголовок.


Как это исправить?

Это просто опечатка? Иногда в коде JavaScript есть просто опечатка в целевом домене. Вы проверили? Если страница находится на www.example.comней, будут совершаться только регулярные звонки по адресу www.example.com! Другие URL-адреса, такие как api.example.comили даже example.comили www.example.com:8080считаются браузером другими доменами! Да, если порт другой, то это другой домен!

Добавьте заголовки. Самый простой способ включить CORS - это добавить необходимые заголовки (as Access-Control-Allow-Origin) к ответам сервера. (У каждого сервера / языка есть способ сделать это - проверьте некоторые решения здесь .)

В крайнем случае : если у вас нет доступа к сервису на стороне сервера, вы также можете отразить его (с помощью таких инструментов, как обратные прокси ) и включить туда все необходимые заголовки.

acdcjunior
источник
2
Спасибо, это много информации. Теперь я могу провести необходимое исследование, чтобы продолжить.
Bazinga777 06
1
Привет, acdcjunior, как мне отразить веб-службу, к которой я хочу получить доступ?
Franva
2
@Franva. Вам необходимо настроить HTTP-сервер (например, Tomcat, Apache с PHP, IIS с ASP) и разместить там страницу, которая для каждого запроса открывает сокет для фактической службы (службы, которую вы зеркалируете), запрашивает фактические данные и затем дает их в качестве ответа. Конечно, вы сделаете это с помощью кода (Java, PHP, ASP и т. Д.).
acdcjunior
@acdcjunior Пожалуйста, поправьте меня, если я правильно понимаю. Если я введу какой-либо URL-адрес в браузере напрямую, он автоматически перенаправит на новый URL-адрес домена без Access-Control-Allow-Origin . Например, при использовании WIF пользователь будет перенаправлен на стороннюю страницу входа при первом входе в систему.
Machinarium
@machinarium Я не уверен, понимаю ли я, что вы имели в виду, но я постараюсь ответить (скажите, если я что-то не так понял): если вы вводите URL-адрес в адресной строке браузера, наличие или отсутствие Access-Control-Allow-Originв этом URL-адресе заголовки вообще не имеют значения - браузер откроет URL как обычно. Политика одинакового происхождения (и требование для Access-Control-Allow-Originзаголовка) применяется только к вызовам Ajax.
acdcjunior
29

Есть своего рода хитрый способ сделать это, если на вашем сервере включен php. Измените эту строку:

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

к этой строке:

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

а затем в php-скрипте (если у вас есть разрешение на использование функции file_get_contents ()):

<?php

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

?>

Php, похоже, не возражает, если этот URL-адрес из другого происхождения. Как я уже сказал, это хакерский ответ, и я уверен, что с ним что-то не так, но он работает для меня.

Изменить: если вы хотите кэшировать результат в php, вот файл php, который вы должны использовать:

<?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;

?>

Кеширующий код берем отсюда .

Jyapayne
источник
3
Еще лучшим решением было бы кэшировать XML-файл на стороне сервера и выполнять file_get_contentsвызов только в том случае, если самый последний XML-файл достаточно устарел. Также не забывайте заголовок Content-Type :-)
sffc
Наткнулся на такой ответ. Вопрос: Каким образом файл PHP узнает, что нужно принимать данные GET и отправлять их по указанному URL-адресу? Будет ли это работать и с данными POSTed?
mpdc
Он не отправляет это. Он получает файл и сохраняет его локально, а затем извергает его, как если бы этот файл был файлом php, который вы запрашиваете локально. Он получит и сохранит еще одну его копию, если локально кэшированный файл старше указанного максимального возраста.
Мысль