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