XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Fixing AJAX: XMLHttpRequest Considered Harmful

November 09, 2005

AJAX applications wouldn't be possible (or, at least, wouldn't be nearly as cool) without the XMLHttpRequest object that lets your JavaScript application make GET, POST, and other types of HTTP requests from within the confines of a web browser. All of the most interesting AJAX applications that have appeared in the past couple of years use the XMLHttpRequest object extensively to give users a responsive in-browser experience without the messiness of traditional HTML forms posting.

But the kind of AJAX examples that you don't see very often (are there any?) are ones that access third-party web services, such as those from Amazon, Yahoo, Google, and eBay. That's because all the newest web browsers impose a significant security restriction on the use of XMLHttpRequest. That restriction is that you aren't allowed to make XMLHttpRequests to any server except the server where your web page came from. So, if your AJAX application is in the page http://www.yourserver.com/junk.html, then any XMLHttpRequest that comes from that page can only make a request to a web service using the domain www.yourserver.com. Too bad -- your application is on www.yourserver.com, but their web service is on webservices.amazon.com (for Amazon). The XMLHttpRequest will either fail or pop up warnings, depending on the browser you're using.

On Microsoft's IE 5 and 6, such requests are possible provided your browser security settings are low enough (though most users will still see a security warning that they have to accept before the request will proceed). On Firefox, Netscape, Safari, and the latest versions of Opera, the requests are denied. On Firefox, Netscape, and other Mozilla browsers, you can get your XMLHttpRequest to work by digitally signing your script, but the digital signature isn't compatible with IE, Safari, or other web browsers.

Solutions Worthy of Paranoia

There is hope, or rather, there are gruesome hacks, that can bring the splendor of seamless cross-browser XMLHttpRequests to your developer palette. The three methods currently in vogue are:

  1. Application proxies. Write an application in your favorite programming language that sits on your server, responds to XMLHttpRequests from users, makes the web service call, and sends the data back to users.
  2. Apache proxy. Adjust your Apache web server configuration so that XMLHttpRequests can be invisibly re-routed from your server to the target web service domain.
  3. Script tag hack with application proxy (doesn't use XMLHttpRequest at all). Use the HTML script tag to make a request to an application proxy (see #1 above) that returns your data wrapped in JavaScript. This approach is also known as On-Demand JavaScript.

The basic idea of all three hacks is the same: fool your user's web browser into thinking that the data is coming from the same domain as the web page.

A word of caution here: there is a good reason why XMLHttpRequests are restricted. Allowing them to freely access any domain from within a web page opens up users to potential security exploitation. Not surprisingly, these three hacks, which offload the request to your web server, potentially threaten to disparage your web server's identity, if not its contents. Caution is advised before deploying them.

Application Proxies

An application proxy is a program on your server that handles web service requests for AJAX client applications. You can write the proxy in your favorite programming language. A simple one is easy to write in PHP. This one is for Yahoo's REST web services called proxy_curl.php. For security reasons, I've hardcoded the domain of the web service in the proxy. This proxy requires that your PHP implement have the curl extension installed.

// Hardcode the hostname
define('HOSTNAME', 'http://api.local.yahoo.com/');
// Get the REST call path from the AJAX application
$path = $_GET['path'];

// The URL to fetch is the hostname + path
$url = HOSTNAME.$path;
// Open the Curl session
$session = curl_init($url);
// Don't return HTTP headers. Do return the contents of the call
curl_setopt($session, CURLOPT_HEADER, false);
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
// Make the call
$xml = curl_exec($session);

// The web service returns XML
header("Content-Type: text/xml");

echo $xml;
curl_close($session);

This is a minimal proxy implementation. Since the proxy is doing the actual web services requests, there's a lot of information about the user state and the success or failure of the request that could be returned to the AJAX application. A few things that you'd want to consider returning or managing are: handling secure (e.g. HTTPS) requests, returning any cookies, handling HTTP POSTs (this one only does GETs), returning the HTTP status code, and managing multiple requests (if your AJAX application makes multiple asynchronous requests, you'd want to identify which request you're receiving).

To call the proxy from your AJAX applications using XMLHttpRequest, make the XMLHttpRequest back to your own server and add the path to the web service as a variable to the web service call. Since I fetch $_GET['path'] in the proxy above, it's expecting that you'll pass the parameter path in your request. Here, I'm making a request to Yahoo's Geocode REST service. Note that since I'm sending the REST URL to a proxy, it will get decoded twice. So, it's necessary to do twice the encoding that you'd normally do with the special characters in the URL using JavaScript's encodeURI() function.

// Call Yahoo's Geocode REST web service
proxy_host = 'www.yourserver.com'; 

// Encode the parameter to the Geocode REST call
address = encodeURI('1000 S Congress Avenue, Austin, TX'); 

// Include the path to the web service and double encode the path
path = "MapsService/V1/geocode?appid=YahooDemo&location=" + address; 
proxy_request = encodeURIComponent(path); 

// The actual request
proxy_request = "http://" + proxy_host + "/proxy_curl.php?path=" + proxy_request; 
....
....
// Make the request
.....xmlhttprequest(proxy_request)....

Pages: 1, 2

Next Pagearrow