89

I am working on this personal project of mine just for fun where I want to read an xml file which is located at http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml and parse the xml and use it to convert values between the currencies.

So far I have come up with the code below which is pretty basic in order to read the XML but I get the following error.

XMLHttpRequest cannot load ****. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://run.jsbin.com' is therefore not allowed access.

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

I don't see anything wrong with my code so I am hoping someone could point out what I am doing wrong with my code and how I could fix it.

Syscall
  • 16,959
  • 9
  • 22
  • 41
Bazinga777
  • 4,726
  • 13
  • 44
  • 76
  • 2
    I suggest you read up on the [Same Origin Policy](http://en.wikipedia.org/wiki/Same-origin_policy) and [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing) – jmoerdyk Nov 06 '13 at 20:13
  • the error states exactly what is wrong, word for word. Your code is fine, the problem is with the server you are accessing. – Kevin B Nov 06 '13 at 20:15
  • and also see [CORS on MDN](https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS) – Amir Ali Akbari Dec 02 '13 at 04:05

2 Answers2

163

You won't be able to make an ajax call to http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml from a file deployed at http://run.jsbin.com due to the same-origin policy.


As the source (aka origin) page and the target URL are at different domains (run.jsbin.com and www.ecb.europa.eu), your code is actually attempting to make a Cross-domain (CORS) request, not an ordinary GET.

In a few words, the same-origin policy says that browsers should only allow ajax calls to services at the same domain of the HTML page.


Example:

A page at http://www.example.com/myPage.html can only directly request services that are at http://www.example.com, like http://www.example.com/api/myService. If the service is hosted at another domain (say http://www.ok.com/api/myService), the browser won't make the call directly (as you'd expect). Instead, it will try to make a CORS request.

To put it shortly, to perform a (CORS) request* across different domains, your browser:

  • Will include an Origin header in the original request (with the page's domain as value) and perform it as usual; and then
  • Only if the server response to that request contains the adequate headers (Access-Control-Allow-Origin is one of them) allowing the CORS request, the browse will complete the call (almost** exactly the way it would if the HTML page was at the same domain).
    • If the expected headers don't come, the browser simply gives up (like it did to you).


* The above depicts the steps in a simple request, such as a regular GET with no fancy headers. If the request is not simple (like a POST with application/json as content type), the browser will hold it a moment, and, before fulfilling it, will first send an OPTIONS request to the target URL. Like above, it only will continue if the response to this OPTIONS request contains the CORS headers. This OPTIONS call is known as preflight request.
** I'm saying almost because there are other differences between regular calls and CORS calls. An important one is that some headers, even if present in the response, will not be picked up by the browser if they aren't included in the Access-Control-Expose-Headers header.


How to fix it?

Was it just a typo? Sometimes the JavaScript code has just a typo in the target domain. Have you checked? If the page is at www.example.com it will only make regular calls to www.example.com! Other URLs, such as api.example.com or even example.com or www.example.com:8080 are considered different domains by the browser! Yes, if the port is different, then it is a different domain!

Add the headers. The simplest way to enable CORS is by adding the necessary headers (as Access-Control-Allow-Origin) to the server's responses. (Each server/language has a way to do that - check some solutions here.)

Last resort: If you don't have server-side access to the service, you can also mirror it (through tools such as reverse proxies), and include all the necessary headers there.

Community
  • 1
  • 1
acdcjunior
  • 114,460
  • 30
  • 289
  • 276
  • 2
    Thanks, that is a lot of information. Now I can perform the necessary research to proceed. – Bazinga777 Nov 06 '13 at 20:25
  • 1
    Hi acdcjunior, how do I mirror the web service which I want to get access to? – Franva Apr 11 '14 at 11:32
  • 2
    @Franva You'll have to setup a HTTP server (e.g. Tomcat, Apache with PHP, IIS with ASP) and place a page there that, for every request, it opens a socket to the actual service (the service you are mirroring), requests the actual data and then gives it as response. Of course, you'll do that through code (Java, PHP, ASP, etc.). – acdcjunior Apr 11 '14 at 16:04
  • @acdcjunior Please correct me if my understanding is right. If I enter some URL in browser directly, it will redirect to the new domain url automatically without *Access-Control-Allow-Origin*. For example, when using WIF, the user will be redirected to third party login page when login for the first time. – machinarium Jul 17 '14 at 13:18
  • @machinarium I'm not sure if I understand what you meant, but I'll try to answer (tell me if I got anything wrong): If you enter the URL in the browser's address bar, the presence or absence of `Access-Control-Allow-Origin` in that URL's headers won't matter at all - the browser will open the URL as usual. The same-origin policy (and the requirement for the `Access-Control-Allow-Origin` header) only applies to Ajax calls. – acdcjunior Jul 17 '14 at 14:41
  • @acdcjunior Thanks for your reply. That's what I want to make sure. – machinarium Jul 18 '14 at 01:38
  • The code in the question is making a simple GET request. It won't trigger a pre-flight OPTIONS request. – Quentin Sep 02 '15 at 15:36
  • @Quentin Yes, you are right. Plain `GET`, no special headers, jQuery shouldn't trigger a preflight request there. I improved the answer a bit, making that point a little more clear. Thanks for that. – acdcjunior Sep 02 '15 at 22:30
29

There's a kind of hack-tastic way to do it if you have php enabled on your server. Change this line:

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

to this line:

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

and then in the php script (if you have permission to use the file_get_contents() function):

<?php

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

?>

Php doesn't seem to mind if that url is from a different origin. Like I said, this is a hacky answer, and I'm sure there's something wrong with it, but it works for me.

Edit: If you want to cache the result in php, here's the php file you would use:

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

?>

Caching code take from here.

Community
  • 1
  • 1
jyapayne
  • 510
  • 5
  • 11
  • 3
    An even better solution would be to cache the XML file on the server side and only perform the `file_get_contents` call if the most recent XML file is sufficiently dated. Also, don't forget your Content-Type header :-) – sffc Dec 28 '13 at 07:37
  • Stumbled upon this answer. Question: How does the PHP file know to take the GET data and submit it to said URL? Would this work with POSTed data as well? – mpdc Oct 29 '14 at 13:56
  • It doesn't submit it. It gets the file and saves it locally, then spews it out as if that file were the php file you're requesting locally. It will retrieve and save another copy of it if the locally-cached file is older than the max age given. – Thought Dec 18 '15 at 15:56