Erro xml jQuery 'Nenhum cabeçalho' Access-Control-Allow-Origin 'está presente no recurso solicitado.'

89

Estou trabalhando neste projeto pessoal meu apenas por diversão, onde desejo ler um arquivo xml que está localizado em http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml e analisar o xml e use-o para converter valores entre as moedas.

Até agora, eu vim com o código abaixo que é bastante básico para ler o xml, mas recebo o seguinte erro.

XMLHttpRequest não pode carregar ****. Nenhum cabeçalho 'Access-Control-Allow-Origin' está presente no recurso solicitado. Origin ' http://run.jsbin.com ', portanto, não tem acesso permitido.

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

Não vejo nada de errado com meu código, então espero que alguém possa apontar o que estou fazendo de errado com meu código e como posso corrigi-lo.

Bazinga777
fonte
2
Eu sugiro que você leia sobre a Política da Mesma Origem e CORS
jmoerdyk
o erro indica exatamente o que está errado, palavra por palavra. Seu código está bom, o problema é com o servidor que você está acessando.
Kevin B
e também ver CORS em MDN
Amir Ali Akbari

Respostas:

163

Você não poderá fazer uma chamada ajax para a http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xmlpartir de um arquivo implantado em http://run.jsbin.comdevido à política de mesma origem .


Como a página de origem (também conhecida como origem ) e o URL de destino estão em domínios ( run.jsbin.come www.ecb.europa.eu) diferentes, seu código está, na verdade, tentando fazer uma solicitação de vários domínios (CORS) , não uma solicitação comum GET.

Em poucas palavras, a política de mesma origem diz que os navegadores devem permitir apenas chamadas ajax para serviços no mesmo domínio da página HTML.


Exemplo:

Uma página em http://www.example.com/myPage.htmlsó pode solicitar diretamente serviços em http://www.example.com, como http://www.example.com/api/myService. Se o serviço estiver hospedado em outro domínio (digamos http://www.ok.com/api/myService), o navegador não fará a chamada diretamente (como seria de esperar). Em vez disso, ele tentará fazer uma solicitação CORS.

Resumindo, para realizar uma solicitação (CORS) * em diferentes domínios, seu navegador:

  • Incluirá um Origincabeçalho na solicitação original (com o domínio da página como valor) e o executará normalmente; e depois
  • Somente se a resposta do servidor àquela solicitação contiver os cabeçalhos adequados ( Access-Control-Allow-Originé um deles ) permitindo a solicitação CORS, o navegador completará a chamada (quase ** exatamente da maneira que faria se a página HTML estivesse no mesmo domínio).
    • Se os cabeçalhos esperados não vierem, o navegador simplesmente desiste (como aconteceu com você).


* A descrição acima descreve as etapas em uma solicitação simples , como uma regular GETsem cabeçalhos sofisticados. Se a solicitação não for simples (como um POSTcom application/jsoncomo tipo de conteúdo), o navegador a aguardará por um momento e, antes de atendê-la, enviará primeiro uma OPTIONSsolicitação ao URL de destino. Como acima, ele só continuará se a resposta a essa OPTIONSsolicitação contiver os cabeçalhos CORS. Essa OPTIONSchamada é conhecida como solicitação de comprovação .
** Estou dizendo quase porque existem outras diferenças entre chamadas regulares e chamadas CORS. Um aspecto importante é que alguns cabeçalhos, mesmo se presentes na resposta, não serão selecionados pelo navegador se não estiverem incluídos noAccess-Control-Expose-Headers cabeçalho.


Como corrigi-lo?

Foi apenas um erro de digitação? Às vezes, o código JavaScript apresenta apenas um erro de digitação no domínio de destino. Você verificou? Se a página estiver aberta, www.example.comela fará apenas chamadas regulares para www.example.com! Outros URLs, como api.example.comou mesmo example.comou www.example.com:8080são considerados domínios diferentes pelo navegador! Sim, se a porta for diferente, é um domínio diferente!

Adicione os cabeçalhos. A maneira mais simples de habilitar o CORS é adicionar os cabeçalhos necessários (as Access-Control-Allow-Origin) às respostas do servidor. (Cada servidor / idioma tem uma maneira de fazer isso - verifique algumas soluções aqui .)

Último recurso: se você não tiver acesso do lado do servidor ao serviço, também pode espelhá-lo (por meio de ferramentas como proxies reversos ) e incluir todos os cabeçalhos necessários.

acdcjunior
fonte
2
Obrigado, isso é muita informação. Agora posso fazer a pesquisa necessária para prosseguir.
Bazinga777
1
Olá acdcjunior, como faço para espelhar o serviço da web ao qual desejo obter acesso?
Franva,
2
@Franva Você terá que configurar um servidor HTTP (por exemplo, Tomcat, Apache com PHP, IIS com ASP) e colocar uma página lá que, para cada solicitação, abre um soquete para o serviço real (o serviço que você está espelhando), solicita os dados reais e os fornece como resposta. Claro, você fará isso por meio de código (Java, PHP, ASP, etc.).
acdcjunior
@acdcjunior Por favor, corrija-me se meu entendimento estiver correto. Se eu inserir algum URL no navegador diretamente, ele redirecionará para o novo URL do domínio automaticamente sem Access-Control-Allow-Origin . Por exemplo, ao usar WIF, o usuário será redirecionado para a página de login de terceiros ao fazer o login pela primeira vez.
machinarium
@machinarium Não tenho certeza se entendi o que você quis dizer, mas tentarei responder (diga-me se entendi algo errado): Se você inserir o URL na barra de endereço do navegador, a presença ou ausência de Access-Control-Allow-Originnesse URL os cabeçalhos não importam - o navegador abrirá a URL normalmente. A política de mesma origem (e o requisito para o Access-Control-Allow-Origincabeçalho) se aplica apenas a chamadas Ajax.
acdcjunior
29

Existe uma forma hack-tastic de fazer isso se você tiver o php habilitado em seu servidor. Mude esta linha:

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

para esta linha:

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

e então no script php (se você tiver permissão para usar a função file_get_contents ()):

<?php

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

?>

Php não parece se importar se esse url é de uma origem diferente. Como eu disse, essa é uma resposta maluca e tenho certeza de que há algo errado com ela, mas funciona para mim.

Editar: Se você deseja armazenar o resultado em cache, aqui está o arquivo php que você usaria:

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

?>

O código de cache é obtido a partir daqui .

jyapayne
fonte
3
Uma solução ainda melhor seria armazenar em cache o arquivo XML no lado do servidor e realizar a file_get_contentschamada apenas se o arquivo XML mais recente estiver suficientemente datado. Além disso, não se esqueça do cabeçalho Content-Type :-)
sffc
Tropecei nesta resposta. Pergunta: Como o arquivo PHP sabe pegar os dados GET e enviá-los para a referida URL? Isso funcionaria com dados POSTADOS também?
mpdc
Não o submete. Ele obtém o arquivo e o salva localmente, em seguida, expele-o como se fosse o arquivo php que você está solicitando localmente. Ele irá recuperar e salvar outra cópia se o arquivo em cache local for mais antigo do que a idade máxima fornecida.
Pensamento em