Fixing AJAX: XMLHttpRequest Considered Harmful
by Jason Levitt
|
Pages: 1, 2
Apache Proxy
Using an Apache proxy is the easiest and cleanest way of getting around these restrictions. However, it requires that you have access to the main Apache httpd.conf file as well as have Apache's mod_proxy extension loaded. Apache proxy directives are not allowed in local .htaccess files so this method is not a good choice for developers using shared hosting services.
# Pass the call from http://www.yourserver.com/call to http://api.local.yahoo.com
ProxyPass /call/ http://api.local.yahoo.com/
# Handle any redirects that yahoo might respond with
ProxyPassReverse /call/ http://api.local.yahoo.com/
Another way to do this is to use Apache's mod_rewrite using the passthru directive:
RewriteEngine on RewriteRule ^/call/(.*)$ http://api.local.yahoo.com/$1 [P]
(Note: this rewrite rule may be broken across more than one line in your browser; but mod_rewrite rules won't work that way, so be careful.)
The Apache proxy approach is clean and simple. Consider a call to the Yahoo Geocode REST Web service:
http://api.local.yahoo.com/MapsService/V1/geocode?appid=YahooDemo&location=78704
With either of the Apache proxy examples functioning, your AJAX application can make a call to:
http://www.yourserver.com/call/MapsService/V1/geocode?appid=YahooDemo&location=78704
and the request will be seamlessly forwarded, and the results returned, to your AJAX application.
Script Tag Hack, or On-Demand JavaScript
The script tag hack is a more complicated approach that involves dynamically generating an HTML script tag and using the src attribute of the tag to make the request. It never makes an XMLHttpRequest, but it's worth looking at because it provides a fairly standard way of making web service requests from within an AJAX application. The code I talk about below is adapted from Darryl Lyons' blog posting on the subject.
The HTML script tag can only return JavaScript. So, to make this approach work, we need to modify the application proxy above to return JavaScript. Two str_replace statements are used to encode any single quotes or newlines that may be in the XML -- they will cause JavaScript scripts to break. Finally, the Content-Type header is changed from text/xml to text/javascript. I'll call the new proxy proxy_script.php (here, the proxy returns XML, but we could also return JSON or other data encodings as well). The XML is placed into a JavaScript global variable -- in this case I've given it the imaginative name xml:
...
...
// Make the call
$xml = curl_exec($session);
// encode the returned XML as a Javascript variable
$xml =str_replace("'", "'", $xml);
$xml =str_replace("\n", "", $xml);
$xml = 'var xml = \''.$xml.'\';';
// The web service returns javascript
header("Content-Type: text/javascript");
echo $xml;
...
...
To make a call, you dynamically create a script tag in your browser's DOM and then point the src attribute at the proxy. To demonstrate this as simply as possible, I created a short web page:
<body>
<a href="javascript:getLocations()">Click this link</a> to dynamically create a script tag and make
a web service call. A button will appear below after the call has finished</p>
<br/><br/>
<div id="locationData"></div>
</body>
Clicking on the link above will call the function getLocations() which will dynamically create a script tag and retrieve a web service. The function getLocations() calls the function getDataFromServer():
function getLocations() {
getDataFromServer("ScriptTagID","http://localhost/proxy_script.php");
}</p>
The function getDataFromServer takes as arguments the name of the script tag that will be dynamically generated (and later destroyed) and the prefix of the URL to be fetched. getDataFromServer() does all the real work. The script tag in IE 5 and 6 can fetch data asynchronously using a proprietary mechanism described here. To encapsulate that behavior, I check for the presence of an IE browser using the same definition function as Sarissa, renamed to BROWSER_IS_IE. For all other web browsers, the script tag essentially fetches data synchronously, though it's possible to attach pseudo-asynchronous properties to it (this is left as an excercise for the reader).
After the data is fetched via the newly created script tag, the callback function, putXMLhere(), is called, and the fetched data is available.
<script>
var callback = "putXMLhere();";
function getDataFromServer(id, url) {
// Fetch the element pointed to by the id. If it exists, we destroy it so we can create a new one.
oScript = document.getElementById(id);
// Point at the script tag, if it exists
var head = document.getElementsByTagName("head").item(0);
// Destroy the tag, if it exists
if (oScript) {
// Destory object
head.removeChild(oScript);
}<p> // Create the new script tag
oScript = document.createElement("script");
// Setup the src attribute of the script tag
sendPath = encodeURIComponent("/MapsService/V1/geocode?appid=YahooDemo&location=78704");
wholeurl = url + "?path=" + sendPath;
oScript.setAttribute("src", wholeurl);
// Set the id attribute of the script tag
oScript.setAttribute("id",id);
// Create the new script tag which causes the proxy to be called
head.appendChild(oScript);
// Asynchronous script tag properties -- a proprietary IE "feature"
if (BROWSER_IS_IE) {
if (oScript.readyState == "loaded") {
eval(callback);
oScript.onreadystatechange = null;
} else {
oScript.onreadystatechange = CheckAgain;
}
// All other web browsers just do the callback function
} else {
eval(callback);
}
}
// Used by IE to handle state changes
function CheckAgain() {
if (oScript.readyState == "loaded") {
eval(callback);
oScript.onreadystatechange = null;
}
}</p>// Once the script tag has loaded the data, it's available in the global Javascript variable "xml" which was sent from the proxy.
function putXMLhere() {
ohandle = document.getElementById("locationData");
ohandle.innerHTML = ohandle.innerHTML + "<form><input type='button' value='View XML' onClick='alert(xml); return true;' /></form>";
}
</script>
Further Plumbing
All of these approaches will provide a seamless, cross-browser AJAX experience for your users while they access third-party web services. But always check with your friendly security administrator before deploying them.
Yes, it's all been done before. These three sites were my major sources of inspiration (or plagiarism): :-)
Darryl Lyons' page on dynamic script tag usage was the inspiration for my investigation.
Premshree Pillai's discussion of the Apache proxy got me looking at that territory.
Think about Cross-Domain Mediation using portlets -- Cross-Domain Mediation is a nice name for "proxy."
Example code for this article: scripthack.zip
- Cross domain - Reverse Proxy
2008-10-19 06:37:47 ampo - False security and useful workarounds
2008-04-30 00:09:00 SamGoody - Cross-domain scripting with dynamic script tags
2006-04-25 20:17:41 lindseykuper - ContextAgnosticXmlHttpRequest Proposal
2006-02-27 14:16:09 cth - Solution Required for Problem in "Ajax in Struts-based Web Application"
2006-02-09 03:35:12 bibroy - Using Flash for cross-domain request
2006-02-02 18:26:07 Julien Couvreur - synchronous script tag hack?
2005-12-13 17:21:39 dr_burrito - Security is relative
2005-11-10 19:01:59 fleegix - Wow, you guys are finally getting back to Java
2005-11-10 18:56:36 wonderman - Security is critical
2005-11-10 14:25:54 SubbuAllamaraju - Make $$$ fast with XML
2005-11-10 00:13:57 otto - Conventions for humorous article titles (Make $$$ fast with XML)
2005-11-14 02:37:09 Damian Cugley