Local XMLHttpRequest Debugging

kick it on DotNetKicks.com

Disconnected XMLHttpRequest Debugging

A recent project has me working closely with the browser XMLHttpRequest object. Like any good little dev., I’ve been writing tests for various Ajax routines. These test are simple .htm files run from a local directory on my computer—they do not reside on an HTTP server. Consequently, after an XMLHttpRequest has its send() fired, no actual HTTP request is made. Instead, the browser simply accesses the file on the harddrive via I/O operations—not the HTTP protocol. During my testing experience with a local setup, I’ve noticed a few quirks that deserve attention. If you’re a developer also debugging in a disconnected environment, you may find these notes useful.

Just a note when I get started: when I refer to “Mozilla”, I’m referring to Mozilla 5, and cannot speak for other version.

As another aside, shouldn’t “XMLHttpRequest” have been called simply “HttpRequest”? Think outside the (XML) box, people.

Cross-browser new XMLHttpRequest()

Now, with that out of the way, as many already known, XMLHttpRequest has to be accessed through ActiveX in IE. There are more than a few different versions of XMLHttpRequest. To instantiate a new XMLHttpRequest without having to worry about whether the client browser is IE or other, I was using the snippet below in the global (window) scope:

if (typeof(XMLHttpRequest) == "undefined") {
	function XMLHttpRequest() {
		try { return new ActiveXObject("MSXML3.XMLHTTP") }catch(e){}
		try { return new ActiveXObject("MSXML2.XMLHTTP.3.0") }catch(e){}
		try { return new ActiveXObject("Msxml2.XMLHTTP") }catch(e){}
		try { return new ActiveXObject("Microsoft.XMLHTTP") }catch(e){}
		return null;
	};
}
// var httpRequest = new XMLHttpRequest();

One can see that if the browser is IE, the latest commonly-distributed version is attempted to be used, then an older one, and down the line. An interesting thing happens when you try to call the open() an XMLHttpRequest to a local file or URL “#” with only MSXML3.XMLHTTP: it doesn’t work—an Error is thrown:

// Throws Error, just like if "asset.xml" was "#"
var httpRequest = new ActiveXObject("MSXML3.XMLHTTP");
httpRequest.open("GET", "asset.xml", true);

// Or any version from the code block above this one
var httpRequest = new ActiveXObject("MSXML2.DOMDocument");
// No Error, responseXML is loaded if asset.xml is valid XML
httpRequest.open("GET", "asset.xml", true);

// Mozilla
var httpRequest = new XMLHttpRequest();
// No Error, responseXML is loaded if asset.xml is valid XML
httpRequest.open("GET", "asset.xml", true);

Local files cannot be accessed using the MSXML3.XMLHTTP library. Don’t waste your time.

XMLHttpRequest.status

To determine if an XMLHttpRequest successfully retrieved a response, code should check for the “done” readyState, 4, and an HTTP response status code, status, of 200, meaning OK:

// Pre-defined constants are used here
// READYSTATE.DONE == 4
// STATUSCODE.OK == 200
if (httpRequest.readyState == READYSTATE.DONE
     && httpRequest.status == STATUSCODE.OK) {
	successCallback(httpResponse);
}

In both Mozilla and IE, if the HTTP request is to a local file, however, the status never changes from 0. The readyState of course does, however. Thus, the snippet above becomes:

// STATUSCODE.DEFAULT == 0
if (httpRequest.readyState == READYSTATE.DONE &&
   (httpRequest.status == STATUSCODE.DEFAULT ||
    httpRequest.status == STATUSCODE.OK)) {
	successCallback(httpRequest);
}

Response Headers

Try to get a specific header value such as “content-type” using getResponseHeader("content-type") in Mozilla and IE, and an empty string will be the result. Same goes for getAllResponseHeaders()—nothing but an empty string. That’s logical since HTTP isn’t actually used though, of course.

responseXML‘s Value

With Mozilla, whether an XMLHttpRequest grabs a local file or actually makes an HTTP request, if the data it processes is valid XML, the data will be loaded into a DOM object and made accessible in responseXML. If the file or response cannot be loaded into the DOM, responseXML is null:

// Mozilla example 
if (httpRequest.readyState == READYSTATE.DONE &&
   (httpRequest.status == STATUSCODE.DEFAULT ||
    httpRequest.status == STATUSCODE.OK)) {
	
		if (httpRequest.responseXML == null) {
			// Failed; didn't load data into DOM object
			return;
		}
		// Success
		successCallback(httpRequest.responseXML);
}

IE, on the other hand, creates an empty DOM object and places it in the responseXML regardless of what the file or response is. Then, if the response data is valid XML, it will be loaded into the responseXML DOM object. Checking if responseXML is null, therefore, will tell you nothing. What you want to check for is the DOM root. If responseXML.documentElement (the root element) is null, then the DOM object is empty—the XML data was not loaded into it:

// IE-only example
if (httpRequest.readyState == READYSTATE.DONE &&
   (httpRequest.status == STATUSCODE.DEFAULT ||
    httpRequest.status == STATUSCODE.OK)) {

		if (httpRequest.responseXML.documentElement == null) {
			// Failed; didn't load data into DOM object
			return;
		}
		// Success
		successCallback(httpRequest.responseXML);
}

With local files, IE, once again, behaves unbecomingly. If the XMLHttpRequest object grabs an “.xml” file or file with valid XML, the DOM object will always be empty. My guess is that the XMLHttpRequest checks for the “content-type” header and loads DOM only if the response is “text/xml”. And since there are no headers because no HTTP request is actually made for local files, XMLHttpRequest isn’t smart enough to load the responseXML object. If one is doing local testing and getting a bunk DOM object, simply load the responseText using ActiveX::

// Using...

if (typeof(XMLHttpRequest) == "undefined") {
	function XMLHttpRequest() {
		try { return new ActiveXObject("MSXML3.XMLHTTP") }catch(e){}
		try { return new ActiveXObject("MSXML2.XMLHTTP.3.0") }catch(e){}
		try { return new ActiveXObject("Msxml2.XMLHTTP") }catch(e){}
		try { return new ActiveXObject("Microsoft.XMLHTTP") }catch(e){}
		return null;
	};
}

// Cross-browser, handlers IE and Mozilla cases

httpRequest = new XMLHttpRequest();
httpRequest.open("GET", "assets.xml", true);

httpRequest.onreadystatechange = function() {
	var isLocal = (httpRequest.status == STATUSCODE.DEFAULT);
	
	if (httpRequest.readyState == READYSTATE.DONE &&
	   (httpRequest.status == STATUSCODE.OK || isLocal)) {
		
		if (httpRequest.responseXML == null)
			// Mozilla failure, local or HTTP
			failure();
		else if(httpRequest.resonseXML.document == null) {
			// IE 
			if (!isLocal)
				// HTTP failure
				failure();
			else {
				// Local failure--always happens, try
				// using Microsoft.XMLDOM to load
				var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
				xmlDoc.async = false;
				xmlDoc.loadXML(httpRequest.responseText);
				
				if (xmlDoc.documentElement != null)
					success(xmlDoc);
				else
					failure();
			}
		}
		else
			success(httpRequest.responseXML);
	}
};
About these ads

4 Comments »

  1. I found during my development of http://www.skicow.com that the method explained above to create XMLHttpRequest instances did not work for Opera 9.x. I created the following function to create XMLHttpRequest which also works for Opera 9.x:


    function newXMLHttpRequest() {
        try { return new XMLHttpRequest() }catch(e){}
        try { return new ActiveXObject("MSXML3.XMLHTTP") }catch(e){}
        try { return new ActiveXObject("MSXML2.XMLHTTP.3.0") }catch(e){}
        try { return new ActiveXObject("Msxml2.XMLHTTP") }catch(e){}
        try { return new ActiveXObject("Microsoft.XMLHTTP") }catch(e){}
        return null;
    }

  2. Christian said

    great explanation, helped me alot. since i’m pretty new to java.. what IDE are you using when developing javascripts?

    Christian

  3. Dux said

    Does this work with Firefox 3.x? My xmlhttprequest object never changes its ready state from 1 and status from 0.

RSS feed for comments on this post · TrackBack URI

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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: