PANVEGA’s Blog

DotNet Development, SharePoint Customizing, Silverlight, MS Infrastructure and other tips and tricks

Asynchron Webservice call with XMLHttpRequest

Posted by PANVEGA on February 12, 2008

XMLHttpRequest is a JavaScript object that was created by Microsoft and adopted by Mozilla. You can use it to easily retrieve data via HTTP. Despite its name, it can be used for more than just XML documents, e.g. for JSON.

As deployment of XML data and web services becomes more widespread, you may occasionally find it convenient to connect an HTML presentation directly to XML data for interim updates without reloading the page. Thanks to the little-known XMLHttpRequest object, an increasing range of web clients can retrieve and submit XML data directly, all in the background. To convert retrieved XML data into renderable HTML content, rely on the client-side Document Object Model (DOM) to read the XML document node tree and compose HTML elements that the user sees.

History and Support

Microsoft first implemented the XMLHttpRequest object in Internet Explorer 5 for Windows as an ActiveX object. Engineers on the Mozilla project implemented a compatible native version for Mozilla 1.0 (and Netscape 7). Apple has done the same starting with Safari 1.2.

Similar functionality is covered in a proposed W3C standard, Document Object Model (DOM) Level 3 Load and Save Specification. In the meantime, growing support for the XMLHttpRequest object means that is has become a de facto standard that will likely be supported even after the W3C specification becomes final and starts being implemented in released browsers (whenever that might be).

Using XMLHttpRequest is very simple. You create an instance of the object, open a URL, and send the request. The HTTP status code of the result, as well as the result document are available in the request object afterwards.Some simple code to do something with data from an XML document fetched over the network:

function test(data) {
 // taking care of data
}

function handler() {
 if(this.readyState == 4 && this.status == 200) {
  // so far so good
  if(this.responseXML != null && this.responseXML.getElementById('test').firstChild.data)
     // success!
   test(this.responseXML.getElementById('test').firstChild.data);
  else
   test(null);
 } else if (this.readyState == 4 && this.status != 200) {
  // fetched the wrong page or network error...
  test(null);
 }
}

var client = new XMLHttpRequest();
client.onreadystatechange = handler;
client.open("GET", "test.xml");
client.send();

If you just want to log a message to the server:

function log(message) {
 var client = new XMLHttpRequest();
 client.open("POST", "/log");
 client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
 client.send(message);
}

Or if you want to check the status of a document on the server:

function fetchStatus(address) {
 var client = new XMLHttpRequest();
 client.onreadystatechange = function() {
  // in case of network errors this might not give reliable results
  if(this.readyState == 4)
   returnStatus(this.status);
 }
 client.open("HEAD", address);
 client.send();
}

Creating the Object

Creating an instance of the XMLHttpRequest object requires branching syntax to account for browser differences in the way instances of the object are generated. For Safari and Mozilla, a simple call to the object’s constructor function does the job:

var req = new XMLHttpRequest();

For the ActiveX branch, pass the name of the object to the ActiveX constructor:

var req = new ActiveXObject(“Microsoft.XMLHTTP”);

The object reference returned by both constructors is to an abstract object that works entirely out of view of the user. Its methods control all operations, while its properties hold, among other things, various data pieces returned from the server.

Object Methods

Instances of the XMLHttpRequest object in all supported environments share a concise, but powerful, list of methods and properties.

Table 1 shows the methods supported by Safari 1.2, Mozilla, and Windows IE 5 or later.

Table 1. Common XMLHttpRequest Object Methods

Method Description
abort() Stops the current request
getAllResponseHeaders() Returns complete set of headers (labels and values) as a string
getResponseHeader("headerLabel") Returns the string value of a single header label
open("method", "URL"[,asyncFlag[,"userName"[,"password"]]]) Assigns destination URL, method, and other optional attributes of a pending request
send(content) Transmits the request, optionally with postable string or DOM object data
setRequestHeader("label", "value") Assigns a label/value pair to the header to be sent with a request

Other Properties and Methods

In addition to the properties and methods shown above, there are other useful properties and methods on the request object.

overrideMimeType()

This method can be used to force a document to be handled as a particular content type. You will generally want to use this when you want responseXML, and the server sends you XML, but does not send the correct Content-Type header.

Note: This method must be called before calling send().
var req = new XMLHttpRequest();
req.open('GET', 'http://www.mozilla.org/', true);
req.overrideMimeType('text/xml');
req.send(null);

setRequestHeader()

This method can be used to set an HTTP header on the request before you send it.

Note: You must call open() before calling this method.
var req = new XMLHttpRequest();
req.open('GET', 'http://www.mozilla.org/', true);
req.setRequestHeader("X-Foo", "Bar");
req.send(null);

getResponseHeader()

This method can be used to get an HTTP header from the server response.

var req = new XMLHttpRequest();
req.open('GET', 'http://www.mozilla.org/', false);
req.send(null);
dump("Content-Type: " + req.getResponseHeader("Content-Type") + "\n");

abort()

This method can be used to abort a request that is in progress.

var req = new XMLHttpRequest();
req.open('GET', 'http://www.mozilla.org/', false);
req.send(null);
req.abort();

mozBackgroundRequest

This property can be used to prevent authentication and bad certificate dialogs from popping up for the request. Also the request will not be canceled if the window it belongs to is closed. This property works for chrome code only.

var req = new XMLHttpRequest();
req.mozBackgroundRequest = true;
req.open('GET', 'http://www.mozilla.org/', true);
req.send(null);

Using from XPCOM components

Note: Changes are required if you use XMLHttpRequest from a JavaScript XPCOM component.

XMLHttpRequest cannot be instantiated using the XMLHttpRequest() constructor from a JavaScript XPCOM component. The constructor is not defined inside components and the code results in an error. You’ll need to create and use it using a different syntax.

Instead of this:

var req = new XMLHttpRequest();
req.onprogress = onProgress;
req.onload = onLoad;
req.onerror = onError;
req.open("GET", url, true);
req.send(null);

Do this:

var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                    .createInstance(Components.interfaces.nsIXMLHttpRequest);
req.onprogress = onProgress;
req.onload = onLoad;
req.onerror = onError;
req.open("GET", url, true);
req.send(null);

Of the methods shown in Table 1, the open() and send() methods are the ones you’ll likely use most. The first, open(), sets the scene for an upcoming operation.

Two required parameters are the HTTP method you intend for the request and the URL for the connection. For the method parameter, use "GET" on operations that are primarily data retrieval requests; use "POST" on operations that send data to the server, especially if the length of the outgoing data is potentially greater than 512 bytes. The URL may be either a complete or relative URL (but see security issues below).

An important optional third parameter is a Boolean value that controls whether the upcoming transaction should be handled asynchronously. The default behavior (true) is to act asynchronously, which means that script processing carries on immediately after the send() method is invoked, without waiting for a response.

If you set this value to false, however, the script waits for the request to be sent and for a response to arrive from the server. While it might seem like a good idea to wait for a response before continuing processing, you run the risk of having your script hang if a network or server problem prevents completion of the transaction.

It is safer to send asynchronously and design your code around the onreadystatechange event for the request object.

The following generic function includes branched object creation, event handler assignment, and submission of a GET request. A single function argument is a string containing the desired URL. The function assumes that a global variable, req, receives the value returned from the object constructors.

Using a global variable here allows the response values to be accessed freely inside other functions elsewhere on the page. Also assumed in this example is the existence of a processReqChange() function that will handle changes to the state of the request object.

var req;

function loadXMLDoc(url) {
	req = false;
    // branch for native XMLHttpRequest object
    if(window.XMLHttpRequest && !(window.ActiveXObject)) {
    	try {
			req = new XMLHttpRequest();
        } catch(e) {
			req = false;
        }
    // branch for IE/Windows ActiveX version
    } else if(window.ActiveXObject) {
       	try {
        	req = new ActiveXObject("Msxml2.XMLHTTP");
      	} catch(e) {
        	try {
          		req = new ActiveXObject("Microsoft.XMLHTTP");
        	} catch(e) {
          		req = false;
        	}
		}
    }
	if(req) {
		req.onreadystatechange = processReqChange;
		req.open("GET", url, true);
		req.send("");
	}
}

Note: It is essential that the data returned from the server be sent with a Content-Type set to text/xml. Content that is sent as text/plain or text/html is accepted by the instance of the request object however it will only be available for use via the responseText property.

Object Properties

Once a request has been sent, scripts can look to several properties that all implementations have in common, shown in Table 2. All properties are read-only.

Table 2. Common XMLHttpRequest Object Properties

Property Description
onreadystatechange Event handler for an event that fires at every state change
readyState Object status integer:
0 = uninitialized
1 = loading
2 = loaded
3 = interactive
4 = complete
responseText String version of data returned from server process
responseXML DOM-compatible document object of data returned from server process
status Numeric code returned by server, such as 404 for “Not Found” or 200 for “OK”
statusText String message accompanying the status code

Use the readyState property inside the event handler function that processes request object state change events. While the object may undergo interim state changes during its creation and processing, the value that signals the completion of the transaction is 4.

You still need more confirmation that the transaction completed successfully before daring to operate on the results. Read the status or statusText properties to determine the success or failure of the operation. Respective property values of 200 and OK indicate success.

Access data returned from the server via the responseText or responseXML properties. The former provides only a string representation of the data. More powerful, however, is the XML document object in the responseXML property. This object is a full-fledged document node object (a DOM nodeType of 9), which can be examined and parsed using W3C Document Object Model (DOM) node tree methods and properties.

Note, however, that this is an XML, rather than HTML, document, meaning that you cannot count on the DOM’s HTML module methods and properties. This is not really a restriction because the Core DOM module gives you ample ways of finding element nodes, element attribute values, and text nodes nested inside elements.

The following listing shows a skeletal onreadystatechange event handler function that allows processing of the response content only if all conditions are right.

function processReqChange() {
    // only if req shows "loaded"
    if (req.readyState == 4) {
        // only if "OK"
        if (req.status == 200) {
            // ...processing statements go here...
        } else {
            alert("There was a problem retrieving the XML data:\n" +
                req.statusText);
        }
    }
}

If you are concerned about possible timeouts of your server process, you can modify the loadXMLDoc() function to save a global time-stamp of the send() method, and then modify the event handler function to calculate the elapsed time with each firing of the event. If the time exceeds an acceptable limit, then invoke the req.abort() method to cancel the send operation, and alert the user about the failure.

Note: Versions of Firefox prior to version 3 always send the request using UTF-8 encoding. When sending a Document, Firefox 3 sends it using the encoding specified by data.inputEncoding (where data is a non-null object given to send()). If not present, it is treated as UTF-8.

Example of a synchron call:

var req = new XMLHttpRequest();
req.open('GET', 'http://www.mozilla.org/', false);
req.send(null);
if(req.status == 200)
  dump(req.responseText);
Note: This example works synchronously, so it will block the user interface if you call this from your JavaScript. You should not use this in practice.
Note: You should not provide an onreadystatechange handler for synchronous requests. If you do, versions of Firefox prior to version 3 call the handler anyway. Firefox 3 blocks until the request is completed (as in the example above). Firefox 2 provides the same behavior as long as you don’t implement an onreadystatechange handler.

Example with non http protocol

var req = new XMLHttpRequest();
req.open('GET', 'file:///home/user/file.json', false);
req.send(null);
if(req.status == 0)
  dump(req.responseText);
Note: file:/// and ftp:// do not return HTTP status, which is why they return zero for status and an empty string for statusText. Refer to bug 331610 for more insight.

Security Issues

When the XMLHttpRequest object operates within a browser, it adopts the same-domain security policies of typical JavaScript activity (sharing the same “sandbox,” as it were). This has some important implications that will impact your application of this feature.

First, on most browsers supporting this functionality, the page that bears scripts accessing the object needs to be retrieved via http: protocol, meaning that you won’t be able to test the pages from a local hard disk (file: protocol) without some extra security issues cropping up, especially in Mozilla and IE on Windows. In fact, Mozilla requires that you wrap access to the object inside UniversalBrowserRead security privileges. IE, on the other hand, simply displays an alert to the user that a potentially unsafe activity may be going on and offers a chance to cancel.

Second, the domain of the URL request destination must be the same as the one that serves up the page containing the script. This means, unfortunately, that client-side scripts cannot fetch web service data from other sources, and blend that data into a page. Everything must come from the same domain. Under these circumstances, you don’t have to worry about security alerts frightening your users.

http://www.xulplanet.com/references/objref/XMLHttpRequest.html

http://de.wikipedia.org/wiki/XMLHttpRequest

http://www.drweb.de/programmierung/ajax-xmlhttpRequest.shtml

http://www.xml.com/pub/a/2005/02/09/xml-http-request.html

http://design-noir.de/webdev/JS/XMLHttpRequest-IE/

http://www.w3.org/TR/XMLHttpRequest/

http://www.google.de/codesearch?hl=de&q=XMLHttpRequest&um=1&ie=UTF-8&sa=X&oi=codesearch_group&resnum=4&ct=title

Advertisements

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

 
%d bloggers like this: