Cross Domain Ajax Request with XML response for IE,Firefox,Chrome, Safari – jQuery

ajax

In a previous post I discussed how to accomplish cross domain JSON requests and some caveats to be aware of. That example involved a scenario in which you had control of the web service environment and were able to allow the cross domain requests on the server side. But what do you do when this isn't an option, and furthermore,  the response is not JSON but XML?

There are two challenges here:

  1. Bypassing the cross domain AJAX request restriction
  2. Consuming and parsing the XML response

Making a cross domain request - the hard way

I say the hard way, but if you have no way of modifying the source server it may be your only option. The trick here is to use the Yahoo API library (Specifically YQL) to act as a proxy between your AJAX call and the server. This is a free, publicly available service that accepts an SQL style query for scraping web responses. If you're struggling with this cross domain issue, you've no doubt tried making a request to the server you're trying to reach via your web browser directly instead of through an AJAX request and found that it works. It's frustrating that you can see your data in this way but not through the request that you need.

The YQL service will basically perform the same type of request that you would make directly through your web browser, then parse the results into an object and return the result to you regardless of the origin domain. This adds considerable latency due the fact that you're now making several more web requests than you'd like and also doing some processing work, but it is what it is and can be a life saver.

Here is the function to make this indirect cross domain request via YQL:

// Accepts a url and a callback function to run.
function requestCrossDomain(site, callback) {

    // If no url was passed, exit.
    if (!site) {
        alert('No site was passed.');
        return false;
    }

    // Take the provided url, and add it to a YQL query. Make sure you encode it!
    var yql = 'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent('select * from xml where url="' + site + '"') + '&format=xml&callback=?';

    // Request that YSQL string, and run a callback function.
    // Pass a defined function to prevent cache-busting.
    $.getJSON(yql, cbFunc);

    function cbFunc(data) {
        // If we have something to work with...
        if (data.results[0]) {
            if (typeof callback === 'function') {
                callback(data);
            }
        }
        // Else, Maybe we requested a site that doesn't exist, and nothing returned.
        else throw new Error('Nothing returned from getJSON.');
    }
}

Note that there are some parameters you can set in the query depending on the type of data you are working with. You can read up on YQL here.

Making a cross domain request - the easy way

If you are able to modify the web service you're working with, you can avoid the YQL work around by adding a response header to the web service response indicating that cross domain requests are OK. The header you want to add to the response is:

Access-Control-Allow-Origin: *

This will allow any website to perform AJAX requests on this service. You can restrict this to specific domains by replacing the * with the URL you want to allow.

In a .NET WCF service, it's easiest to add this to the global.asax of your service like so:

protected void Application_BeginRequest(object sender, EventArgs e)
{
    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin","*");
}

Consuming and Parsing the XML Response

USING YQL

Your data is going to come back as a string and you're going to want to get that string into an XML Document so that you can parse it easily using the DOM parser. Similar to the previous article, you have to perform the task differently for IE than you do for other browsers.

function readFeed(limit) {
    requestCrossDomain('http://www.yourservice.com/xml', function (result) {
        var num = 1;

        var browserName = navigator.appName;
        var doc;
        if (browserName == 'Microsoft Internet Explorer') {
            doc = new ActiveXObject('Microsoft.XMLDOM');
            doc.async = 'false'
            doc.loadXML(result.results);
        } else {
            doc = (new DOMParser()).parseFromString(result.results, 'text/xml');
        }
        var xml = doc;

        $(xml).find('news').each(function () {

            if (num <= limit) {
                var date = $(this).attr('date');
                var headlines = $(this).find('headlines').text();
                var link = $(this).find('link').text();

                var content = "<p>" + headlines + "</p><a class='more' href='" + link + "'>continue reading</a>";
                $("#news" + num).html(content);
                num++;
            }
        });
    });
}

This code will call our requestCrossDomain() function and create the callback function to process the response. In this example, we are accepting a limit variable for processing news articles. As you can see, we start out by building an XML doc object in one of two ways depending on the browser. Then with this line:

$(xml).find('news').each(function () {

We search through the XML document using jQuery to find each root element that we're interested in, in this case, the <news> element. For <news> element we find, store it's children elements <date>, <headlines>, and <link> into variables for use in our HTML content. What you do here will depend on your data and goals but the same principals apply.

NOT USING YQL

If you do not need to use YQL because you have added the header

Access-Control-Allow-Origin: *

Then you can make a standard AJAX call like so:

function readFeed(limit) {
    $.get('http://www.yourservice.com/xml', function (result) {
        var num = 1;

        var browserName = navigator.appName;
        var doc;
        if (browserName == 'Microsoft Internet Explorer') {
            doc = new ActiveXObject('Microsoft.XMLDOM');
            doc.async = 'false'
            doc.loadXML(result.results);
        } else {
            doc = (new DOMParser()).parseFromString(result.results, 'text/xml');
        }
        var xml = doc;

        $(xml).find('news').each(function () {

            if (num <= limit) {
                var date = $(this).attr('date');
                var headlines = $(this).find('headlines').text();
                var link = $(this).find('link').text();

                var content = "<p>" + headlines + "</p><a class='more' href='" + link + "'>continue reading</a>";
                $("#news" + num).html(content);
                num++;
            }
        });
    });
}

21 Comments

  1. Author's Headshot
    John July 29, 2011
    Reply

    Thank for the info, very good read!

  2. Author's Headshot
    KntL October 1, 2011
    Reply

    a nice lifesaver article. thank you!

  3. Author's Headshot
    JDB February 24, 2012
    Reply

    dude, this was a lifesaver. Thanks!

  4. Author's Headshot
    Doug April 13, 2012
    Reply

    I copied and pasted your code, and added the header to the response, and it doesn't work. I get a XML parse error. I verified that the xml is valid (i can hit the url directly in browser and it works and is valid xml) If i step through it with firebug, i get to the method function cbFunc(data) and it goes to that else else throw new Error('Nothing returned from getJSON.');

    • Author's Headshot
      Matt Mombrea April 13, 2012

      If you get an XML Parse error there is probably something being returned in the XML that the Parser doesn't like. Take a look at cleaning up any invalid characters etc. in the XML response.

    • Author's Headshot
      Doug April 13, 2012

      I did look at the xml and it has no problems. I even took the xml and pasted it in a xml file locally in my project and hit that instead of the url and it works

    • Author's Headshot
      Doug April 13, 2012

      Sorry, i misspoke on my first comment, the parse xml error was before i implemented this sites code. With the code from this site, no error that i can see, but the cbFunc method has 0 results. Which according to the site means the site doesn't exist, but it does and i can hit it and get back xml in browser.

    • Author's Headshot
      Matt Mombrea April 13, 2012

      Doug, I think I see the issue. It looks like in my example for the section "The Easy Way" I'm still calling the function that executed the YQL call. That is a mistake. The Easy Way should not use YQL, just a standard AJAX request. Try changing the first line in the readFeed function from:

      requestCrossDomain('http://www.yourservice.com/xml', function (result) {

      to

      $.get('http://www.yourservice.com/xml', function (result) {

  5. Author's Headshot
    Doug April 13, 2012
    Reply

    sorry, when i said i copied and pasted i did change the site to my site.

  6. Author's Headshot
    Rohit April 17, 2012
    Reply

    Awesome Stuff Man !! YQL worked for me!

  7. Author's Headshot
    Haseeb June 9, 2012
    Reply

    Error: could not load the page.

    Here is my code:
    //feel free to add querystring vars to this
    var site = "http://www.edreams.es/edreams/espanol/hotels/bookingEngine/partners/HotelSearch.jhtml?mktportal=tradedoubler&city=Madrid&checkInDate=10062012&checkOutDate=12062012&rooms=1&adults=2&childs=1&utm_campaign=tradedoubler-15407";

    var yql = 'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent('select * from xml where url="' + site + '"') + '&format=xml&callback=?';

    //make the call to YQL
    $.getJSON(yql,
    function (data) {
    if (data.results[0]) {
    //this data.results[0] is the return object you work with,
    //if you actually want to do something with the returned json
    alert(data.results[0]);
    } else {
    var errormsg = 'Error: could not load the page.';
    //output to firebug's console
    //use alert() for other browsers/setups
    console.log(errormsg);
    }
    }
    );

    • Author's Headshot
      Matt Mombrea June 9, 2012

      Haseeb,

      It doesn't look like your site url is returning anything, just an error message.

      Regardless, I was able to get a response by using the code above. Here is the JS Fiddle of the script:
      http://jsfiddle.net/NdkPU/

  8. Author's Headshot
    Cross Site Request with Ajax | … Tech Addicted ! June 29, 2012
    Reply

    [...] Here is a good link about Cross Site Domain request via Ajax. Share this:ShareTwitterLinkedInLike this:LikeBe the first to like this. [...]

  9. Author's Headshot
    George November 13, 2012
    Reply

    Huge help! Thank you very much!

  10. Author's Headshot
    Cross domain Ajax calls and Same Origin policy in web browser | Java Tech IG December 11, 2012
    Reply
  11. Author's Headshot
    Hardik Patel January 26, 2013
    Reply

    Thanks a lot.
    Firefox ajax query is working for cross domain

  12. Author's Headshot
    venkat July 20, 2013
    Reply

    Hi,

    Thanks for you Code.

    My Code:

    // Accepts a url and a callback function to run.
    function requestCrossDomain(site, callback) {

    // If no url was passed, exit.
    alert(site);
    if (!site) {
    alert('No site was passed.');
    return false;
    }

    // Take the provided url, and add it to a YQL query. Make sure you encode it!
    var yql = 'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent('select * from xml where url="' + site + '"') + '&format=xml&callback=?';

    // Request that YSQL string, and run a callback function.
    // Pass a defined function to prevent cache-busting.
    $.getJSON(yql, cbFunc);

    function cbFunc(data) {
    // If we have something to work with...
    if (data.results[0]) {
    if (typeof callback === 'function') {
    callback(data);
    }
    }
    // Else, Maybe we requested a site that doesn't exist, and nothing returned.
    else {//throw new Error('Nothing returned from getJSON.');
    alert('Failure');
    }
    }
    }

    It is not work for me .

    Where i am wrong this code.

    Thanks,
    Venkat

  13. Author's Headshot
    Brice Durand July 29, 2013
    Reply

    Thanks Matt!

    I've got another issue, my request is not on port 80 and I get an error from Yahoo :

    Invalid URL http://my-url:8086/bla : Invalid HTTP destination port

    (I replaced the URL by a fake one obviously, but the real url returns a valid XML)

    Did they block other ports? Any way to get around this?

  14. Author's Headshot
    swa September 5, 2013
    Reply

    Please look at this code

    $.ajax({
    url:"http://polymerupdate.com/web/news-desk.asmx/PCWReports",
    dataType: 'jsonp', // Notice! JSONP <-- P (lowercase)
    success:function(responce){
    // do stuff with json (in this case an array)
    alert(XML);
    },
    error:function(jqXHR, textStatus, errorThrown,responce){
    alert(textStatus)
    }
    });

  15. Author's Headshot
    bohus September 12, 2014
    Reply

    Hi, i try easy way without yql but then i dont get any result. Where must i put Access-Control-Allow-Origin: * ? Im using php

Leave a Reply

Your email address will not be published. Required fields are marked *

Meet the Author

mmombrea-headshot
CTO / Partner

Matthew Mombrea

Matt is our Chief Technology Officer and one of the founders of our agency. He started Cypress North in 2010 with Greg Finn, and now leads our Buffalo office. As the head of our development team, Matt oversees all of our technical strategy and software and systems design efforts.

With more than 19 years of software engineering experience, Matt has the knowledge and expertise to help our clients find solutions that will solve their problems and help them reach their goals. He is dedicated to doing things the right way and finding the right custom solution for each client, all while accounting for long-term maintainability and technical debt.

Matt is a Buffalo native and graduated from St. Bonaventure University, where he studied computer science.

When he’s not at work, Matt enjoys spending time with his kids and his dog. He also likes to golf, snowboard, and roast coffee.