Błąd jQuery xml „Brak nagłówka„ Access-Control-Allow-Origin ”w żądanym zasobie.”

89

Pracuję nad tym moim osobistym projektem dla zabawy, w którym chcę przeczytać plik xml, który znajduje się pod adresem http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml i przeanalizować xml i używaj go do przeliczania wartości między walutami.

Do tej pory wymyśliłem poniższy kod, który jest dość prosty, aby odczytać XML, ale pojawia się następujący błąd.

XMLHttpRequest nie może załadować ****. Żądany zasób nie zawiera nagłówka „Access-Control-Allow-Origin”. Dlatego Origin „ http://run.jsbin.com ” nie ma dostępu.

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

Nie widzę nic złego w moim kodzie, więc mam nadzieję, że ktoś może wskazać, co robię źle z moim kodem i jak mogę to naprawić.

Bazinga777
źródło
2
Sugeruję, abyś przeczytał te same zasady pochodzenia i CORS
jmoerdyk
błąd określa dokładnie, co jest nie tak, słowo w słowo. Twój kod jest w porządku, problem dotyczy serwera, do którego uzyskujesz dostęp.
Kevin B
a także zobacz CORS na MDN
Amir Ali Akbari

Odpowiedzi:

163

Nie będziesz w stanie wykonać wywołania Ajax do http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xmlz pliku wdrożonego w z http://run.jsbin.compowodu zasad tego samego pochodzenia .


Ponieważ strona źródłowa (inaczej strona pochodzenia ) i docelowy adres URL znajdują się w różnych domenach ( run.jsbin.comi www.ecb.europa.eu), Twój kod w rzeczywistości próbuje wykonać żądanie międzydomenowe (CORS) , a nie zwykłe GET.

W kilku słowach zasada tego samego pochodzenia mówi, że przeglądarki powinny zezwalać na wywołania Ajax tylko do usług w tej samej domenie strony HTML.


Przykład:

Strona pod adresem http://www.example.com/myPage.htmlmoże bezpośrednio żądać tylko usług znajdujących się pod adresem http://www.example.com, na przykład http://www.example.com/api/myService. Jeśli usługa jest hostowana w innej domenie (powiedzmy http://www.ok.com/api/myService), przeglądarka nie wykona połączenia bezpośrednio (jak można się spodziewać). Zamiast tego spróbuje wysłać żądanie CORS.

Krótko mówiąc, aby wykonać żądanie (CORS) * w różnych domenach, Twoja przeglądarka:

  • Zawiera Originnagłówek w pierwotnym żądaniu (z domeną strony jako wartość) i wykonuje to jak zwykle; i wtedy
  • Tylko jeśli odpowiedź serwera na to żądanie zawiera odpowiednie nagłówki ( Access-Control-Allow-Originjest jednym z nich ) zezwalające na żądanie CORS, przeglądarka zakończy wywołanie (prawie ** dokładnie tak, jak gdyby strona HTML znajdowała się w tej samej domenie).
    • Jeśli oczekiwane nagłówki nie pojawią się, przeglądarka po prostu się poddaje (tak jak zrobiła to u Ciebie).


* Powyższe przedstawia kroki w prostym żądaniu, takim jak zwykły GETbez fantazyjnych nagłówków. Jeśli żądanie nie jest proste (np. POSTZ application/jsontypem zawartości jako), przeglądarka zatrzyma je przez chwilę, a przed jego wypełnieniem najpierw wyśle OPTIONSżądanie do docelowego adresu URL. Podobnie jak powyżej, będzie kontynuowane tylko wtedy, gdy odpowiedź na to OPTIONSżądanie zawiera nagłówki CORS. To OPTIONSwywołanie jest znane jako żądanie wstępne .
** Mówię prawie, ponieważ istnieją inne różnice między zwykłymi wywołaniami a wywołaniami CORS. Ważne jest to, że niektóre nagłówki, nawet jeśli są obecne w odpowiedzi, nie zostaną odebrane przez przeglądarkę, jeśli nie zostaną uwzględnione wAccess-Control-Expose-Headers nagłówku.


Jak to naprawić?

Czy to była tylko literówka? Czasami kod JavaScript zawiera tylko literówkę w domenie docelowej. Czy sprawdziłeś? Jeśli strona jest na www.example.com, będzie wykonywać tylko regularne połączenia www.example.com! Inne adresy URL, takie jak api.example.comlub nawet example.comlub www.example.com:8080są uważane za różne domeny przez przeglądarkę! Tak, jeśli port jest inny, to jest to inna domena!

Dodaj nagłówki. Najprostszym sposobem włączenia CORS jest dodanie niezbędnych nagłówków (as Access-Control-Allow-Origin) do odpowiedzi serwera. (Każdy serwer / język ma na to sposób - sprawdź tutaj kilka rozwiązań ).

Ostatnia deska ratunku: jeśli nie masz dostępu do usługi po stronie serwera, możesz ją również skopiować (za pomocą narzędzi, takich jak odwrotne proxy ) i dołączyć tam wszystkie niezbędne nagłówki.

acdcjunior
źródło
2
Dzięki, to dużo informacji. Teraz mogę przeprowadzić niezbędne badania, aby kontynuować.
Bazinga777
1
Cześć, acdcjunior, jak wykonać kopię lustrzaną usługi sieciowej, do której chcę uzyskać dostęp?
Franva
2
@Franva Będziesz musiał skonfigurować serwer HTTP (np. Tomcat, Apache z PHP, IIS z ASP) i umieścić tam stronę, która dla każdego żądania otwiera gniazdo do rzeczywistej usługi (usługa, którą dublujesz), żąda rzeczywistych danych, a następnie podaje je w odpowiedzi. Oczywiście zrobisz to za pomocą kodu (Java, PHP, ASP itp.).
acdcjunior
@acdcjunior Proszę mnie poprawić, jeśli rozumiem prawidłowo. Jeśli wprowadzę jakiś adres URL bezpośrednio w przeglądarce, nastąpi automatyczne przekierowanie do adresu URL nowej domeny bez Access-Control-Allow-Origin . Na przykład podczas korzystania z WIF użytkownik zostanie przekierowany na stronę logowania strony trzeciej przy pierwszym logowaniu.
machinarium
@machinarium Nie jestem pewien, czy rozumiem, co miałeś na myśli, ale spróbuję odpowiedzieć (powiedz mi, czy coś jest nie tak): Jeśli wpiszesz adres URL w pasku adresu przeglądarki, obecność lub brak Access-Control-Allow-Originw tym adresie URL nagłówki nie będą miały żadnego znaczenia - przeglądarka otworzy adres URL jak zwykle. Zasady tego samego pochodzenia (i wymaganie dotyczące Access-Control-Allow-Originnagłówka) mają zastosowanie tylko do wywołań Ajax.
acdcjunior
29

Jest na to pewien hackowy sposób, jeśli masz włączoną obsługę PHP na swoim serwerze. Zmień tę linię:

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

do tej linii:

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

a następnie w skrypcie php (jeśli masz uprawnienia do używania funkcji file_get_contents ()):

<?php

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

?>

Php nie wydaje się mieć nic przeciwko temu, że ten adres URL pochodzi z innego źródła. Tak jak powiedziałem, to zręczna odpowiedź i jestem pewien, że jest z nią coś nie tak, ale dla mnie działa.

Edycja: Jeśli chcesz buforować wynik w php, oto plik php, którego użyjesz:

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

?>

Kod buforowania bierze się stąd .

jyapayne
źródło
3
Jeszcze lepszym rozwiązaniem byłoby buforowanie pliku XML po stronie serwera i wykonywanie file_get_contentswywołania tylko wtedy, gdy najnowszy plik XML jest wystarczająco datowany. Nie zapomnij też o swoim nagłówku Content-Type :-)
sffc
Natknąłem się na tę odpowiedź. Pytanie: Skąd plik PHP wie, aby pobrać dane GET i przesłać je na wspomniany adres URL? Czy to zadziała również w przypadku opublikowanych danych?
mpdc
Nie składa tego. Pobiera plik i zapisuje go lokalnie, a następnie wypluwa go tak, jakby ten plik był plikiem php, o który prosisz lokalnie. Odzyska i zapisze kolejną kopię, jeśli lokalnie buforowany plik jest starszy niż podany maksymalny wiek.
Myśl