A Simple Cross-Domain Ajax

In spite of the power of the XMLHttpRequest API, its usage is limited by the “same-origin” policy. What this means is that the hostname of the url you are sending the XMLHttpRequest cannot be different from the hostname of the web server.

Understandably, the reason behind this is security. However, there are legitimate reasons why you would need to call out to other domains, for example, you have a site with multiple domains or sub-domains and you would need to call XMLHttpRequest to these different domains.

One way to get around this issue is to let the server-side, on behalf of the client browser, perform the HTTP request to the external site. This functionality is best explained here. But this doesn’t sound like the true Ajax we imagined because we wanted to use the same idea of XMLHttpRequest where you can call out a URL from the browser, with the only difference that it can be a different domain.

So how does one perform an Ajax request using a different domain?

Below is a simple example of a cross-domain Ajax:

<html>
<head>
<script type="text/javascript">
    function xss_ajax(url) {
        var script_id = null;
        var script = document.createElement('script');
        script.setAttribute('type', 'text/javascript');
        script.setAttribute('src', url);
        script.setAttribute('id', 'script_id');

        script_id = document.getElementById('script_id');
        if(script_id){
            document.getElementsByTagName('head')[0].removeChild(script_id);
        }

        // Insert <script> into DOM
        document.getElementsByTagName('head')[0].appendChild(script);
    }

    function callback(data) {
        var txt = '';
        for(var key in data) {
            txt += key + " = " + data[key];
            txt += "\n";
        }
        alert(txt);
    }

    var url = "http://alvin-samplejson.appspot.com/callback_json.php";

</script>
<title>Simple Cross Domain Ajax</title>
</head>
<body>
<h1>Simple Cross Domain Ajax</h1>
<button onclick="xss_ajax(url);">Get Data</button>
</body>
</html>

The JavaScript code above has two functions: xss_ajax() and callback(). The first function, xss_ajax(), performs the HTTP request given a URL. This function mimics the XMLHttpRequest’s open() and send() methods combined. The URL passed to it is not limited by the same-origin policy, thus, it can be any domain name. The second function, callback(), is a function that processes the data returned by the HTTP request performed by xss_ajax().

In the HTML body, I have included a button element to demonstrate a call to the xss_ajax() function.

To try out this example, simply save this code into a file, load it into your browser, e.g., file:///C:/simpleCrossDomainAjax.html, and hit the “Get Data” button.

As you will see, even if the origin is a local file, it can still perform an HTTP request to a different domain and be able to return a response. (http://alvin-samplejson.appspot.com/callback_json.php)

One thing that is different from the XMLHttpRequest API is that the response of our http request cannot be a true JSON object (or XML). Instead, it needs to be constructed with a string-like function call, where its parameter will be the true format of the object you wish to return.

Viewing the sample URL:
http://alvin-samplejson.appspot.com/callback_json.php

the response of the PHP page will look like this:

callback({"firstname":"John","lastname":"Smith","email":"john.smith@johnsmith.com"});

Below is the PHP code that is used for the HTTP request:

<?php
$obj = array();
$obj['firstname'] = "John";
$obj['lastname'] = "Smith";
$obj['email'] = "john.smith@johnsmith.com";

$response = "callback(" . json_encode($obj) . ");";
print $response;

/**
  Browser prints this out as:
  callback({"firstname":"John", "lastname":"Smith", "email":"john.smith@johnsmith.com"});
**/
?>

The “callback” function string as returned by the HTTP request triggers the call to the callback() function.

The magic behind all this is in the usage of the <script> DOM element. As we all know, a web browser can load a JavaScript source file from a different domain. For example:

<script type="text/javascript"
  src="http://external-domain.com/myjavascript.js"></script>

What this code is doing is performing an HTTP request to external-domain.com, fetching the file myjavascript.js, and loading its content.

If this can be done to a javascript file, then it can be done as well to a regular HTML file, or to any type of HTTP requests, e.g. cgi, php, jsp, etc. For example:

<script type="text/javascript" src="http://external-domain.com/mypage.html"></script>
<script type="text/javascript" src="http://external-domain.com/mypage.cgi"></script>
<script type="text/javascript" src="http://external-domain.com/mypage.php"></script>
<script type="text/javascript" src="http://external-domain.com/mypage.xml"></script>

Based on this principle, in order to mimic an XMLHttpRequest kind of call, all we need to do is programmatically create a script element at runtime every time the browser needs to perform an HTTP request. Line numbers 6 to 9 in the xss_ajax() function above performs this dynamic creation of the script element. Line number 17 is when the script element is inserted into the DOM that will trigger the actual execution of the script tag.

Since the script element tag expects a JavaScript code, the response of the URL specified must be executable like a JavaScript code. That is why our PHP code above returns a “callback()” string instead of the raw JSON object. When the script-tag gets created, the browser will see this function-call-like string and will execute it.

The example above only performs a simple HTTP request without any parameters. What if we need to send request parameters? Since the script-tag follows the same principle of making an HTTP GET request, passing request parameters would be the same thing as adding the query-strings to the URL, For example:

<script type="text/javascript"
  src="http://otherdomain.com/mypage.php?name=JOHN&password=secret"></script>

Therefore, to make our xss_ajax() support request parameters, all we need to do is simply add the request parameters to the URL.

Alvin Abad

Advertisements

42 thoughts on “A Simple Cross-Domain Ajax

  1. I tried it in IE7 and IE8. I am getting syntax error. It is working fine in Firefox. I even tried jQuery’s getScript(). but facing the same problem in IE7 and IE8. Can anyone tell me what is going wrong?

  2. You say the same can be done to access external html, and indeed I’m able to see (inside Firebug) the new script which content is actually the external html, but I’m not able to manipulate that html content (I tried through DOM but without success). Can you explain better the html case ? Thanks.

    • If you can see it in Firebug then it’s in the DOM. Make sure that when your JavaScript tries to access it, the external html has already loaded in the DOM. Maybe you just missed the node of the external html that was loaded. Try putting a fixed “id” attribute to the parent node to make sure you are accessing the correct parent node.

  3. Wow!
    I spended two days finding for a solution with RubyOnRails – Prototype and you gave the answer in 5 minutes ! Brilliant.
    I wrapped the code inside a class so doing xss Ajax is trivial like new XSSAjax(url, function(data){//do something with JSON data}).connect ()
    Thanks a lot !

  4. I cannot get it working. Any help appreciated

    This is my version of callback.php. This is an aspx page whose url i pass to the var url =”http://mysite/callbacktest.aspx”

    protected void Page_Load(object sender, EventArgs e)
    {
    Response.Write(“callback(‘Nice’);”);
    Response.End();

    }

    The error i am getting is in aspx page line 0 char 3

    • It is working fine now. Problem was i returning a string only instead of a object.So now i have changed in aspx file
      Response.Write(“callback({‘firstname’:’John’,’lastname’:’Smith’,’email’:’john.smith@johnsmith.com’});”);

      and it works.

  5. Is there a way to set additional header information for the call? The web page I need to get is protected with basic Authentication.

  6. So this has to return something “script-like”? Or is there any way to get to the data in a plain xml-file using this?

    quote:
    Since the script element tag expects a JavaScript code, the response of the URL specified must be executable like a JavaScript code. That is why our PHP code above returns a “callback()” string instead of the raw JSON object.

  7. I was able to get this example to work, but my goal is to load a few bits of an external webpage into another webpage. I’m assuming that I need to load the page, parse then feed it into the callback somehow. Sounds right?

  8. Cannot really figure out how to make it work for an external URL that I do not have permission to do editing to add ‘callback’ to the Response object.

  9. Nice job brother..
    its working fine..

    but if I try to make 2 ajax requests for example
    xss_ajax(url1);
    xss_ajax(url2);

    it goes stuck.. any help..?

  10. Does your site have a contact page? I’m having a tough time locating it but, I’d like
    to send you an e-mail. I’ve got some recommendations for your blog you might be interested in hearing. Either way, great site and I look forward to seeing it develop over time.

  11. When I save the code, open the page and click the button, I get no response. I put an alert in each function. The one in xss_ajax appears in the browser, but the one in the callback does not.

    Is it possible that it’s because no data is being returned, so the callback is never called?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s