This is my personal blog. The views expressed on these pages are mine alone and not those of my employer.

Wednesday, October 12, 2005

AJAX Tutorial: How to Invoke Web Services From a Web Page On A Different Host

[Update: This technique doesn't work. I messed up and thought I had it working on my own machine, but I was wrong. The browser does indeed block the remote iframe calls, since they are to a different domain.]

While working at the Internet Archive this month, I had a need to call a RESTian web service that was running on a server seperate from the one the web page was pulled from. I initially thought this would be impossible; if I download a page from foo.com, and want to call some web service at somewhere-else.com, I thought the JavaScript same origin policy would prevent me from doing so.

It turned out I was wrong. There is a way to call remote web services from a web page that is seperate from the one that served the web page itself. This makes it possible to assemble AJAX web pages that call out to a host of different web services across the Internet, opening the door to more sophisticated applications.

The first thing to know is that both XMLHttpRequest and the XML parsers baked into many browsers can't call out to addresses that are different than the web page they are on, so they aren't appropriate.

What we have to do is a bit tricky. First, our remote web service must be RESTian, and should use the HTTP GET and POST verbs only. You should design your RESTian web services to use these anyway; I know, I know, using verbs like PUT and DELETE can make your service more elegant, but using those verbs makes it impossible for less capable web clients to access your service (like Safari, which doesn't support the PUT verb in XMLHttpRequest).

Second, we must have an iframe that invokes the service; the code below is more simplistic than you would use in your own program, but it gets the point across:
<iframe id="serviceResults" onload="resultsAvailable()"
src="http://some_other_domain.com/someservice">
</iframe>
You could change the iframe's src over and over programmaticly to invoke the remote web service with different values.

Next, your remote web service should return XML, but it must have one very important line so that Internet Explorer doesn't barf:
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="blank.css"?>
<some-results>
<some-value message="foobar">Hello World</some-value>
</some-results>
Notice the xml-stylesheet directive; this is the important line that your web service results must return. By default Internet Explorer will download the XML results from the web service into the iframe and transform it into HTML, which will destroy the XML. This line tells Internet Explorer to use blank.css for transforming; blank.css doesn't exist, which will prevent Internet Explorer from transforming the document at all, leaving it as XML.

Once the iframe is loaded with the results and resultsAvailable() is called, you can do the following to manipulate the results as an XML document in both Firefox and Internet Explorer:
function resultsAvailable() {
var serviceResults = document.getElementById("serviceResults");
var doc = serviceResults.contentDocument;
if (typeof doc == "undefined") // Internet Explorer
doc = serviceResults.contentWindow.document;

// now you can call getElementsByTagName and getElementsById
// on the doc to grab your XML data, and use the DOM methods
// to walk over the results
var serviceValues = doc.getElementsByTagName("service-results");
var serviceValue = serviceValues[0];
var message = serviceValue.getAttribute("message");
var serviceData = serviceValue.firstChild.data;
}
I have tested this on FireFox 1.0 and IE 6.

A POST can be simulated by creating a form inside of the iframe, and then programmatically POSTing it to the address of the iframe with the input elements set to the values you want to send. The XML results will then fill the iframe and you can retrieve them the same way as above.

You can think about alot of ways to use this. If you wrap it in a script and create the iframe programmatically, then you can make a fancy API for others to script your remote service from their web page. Or, you can use it as a server side include to insert HTML data into the page using a remote web service. Or, imagine wrapping it in a bookmarklet (using the giant bookmarklet code I created recently), and use it to create an advanced bookmarklet that interacts with arbitrary, remote web services in the context of the currently displayed web page in the browser. Or, you can use this to establish a sophisticated RESTian API for your product that can be called into from a variety of advanced contexts.

My consulting company, Web 2.0 Consulting, is available to apply these kinds of techniques to your own products. I imagine and discover new ways of working with AJAX that can provide features in your product that no one else has yet. Contact me if you are interested.

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]