Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Errors and AJAX

by Joshua Gitlin
May 11, 2005

In case you haven't heard by now, the hottest buzzword in the realm of web technology is AJAX (as coined in an Adaptive Path essay). The crux of the AJAX framework is the XMLHttpRequest JavaScript object which allows client-side developers to send and receive XML documents over HTTP without interrupting the user, and without hacking around with hidden frames. Now, some might shudder at the notion of allowing client-side developers who might be more used to validating forms and animating rollover images to suddenly be responsible for traversing XML documents and negotiating HTTP headers, but without risk, there is no reward. And just to soothe any lingering trepidation, I will demonstrate how to use XMLHttpRequest to not only add previously impossible or infeasible features, but also to reduce errors and improve quality.

XMLHttpRequest and XML DOM for JavaScript Basics

First, we need to set up a few ground rules. The XMLHttpRequest object in particular, and XML DOM in general, is widely supported in any recent browser (IE, Mozilla, Safari, Opera) although, as usual, Microsoft has taken a slightly different tack on implementation and requires some special care. While our more progressive friends directly implement XMLHttpRequest, IE requires that you instantiate an ActiveXObject with the same properties. An excellent overview and full feature list is available at the Apple Developer Connection site.

A basic example follows:

var req;
function postXML(xmlDoc) {
	if (window.XMLHttpRequest) req = new XMLHttpRequest();
	else if (window.ActiveXObject) req = new ActiveXObject("Microsoft.XMLHTTP");
	else return; // fall on our sword
	req.open(method, serverURI);
	req.setRequestHeader('content-type', 'text/xml');
	req.onreadystatechange = xmlPosted;
	req.send(xmlDoc);
}
function xmlPosted() {
	if (req.readyState != 4) return;
	if (req.status == 200) {
		var result = req.responseXML;
	} else {
		// fall on our sword
	}
}

The potential uses for this powerful tool are vast, and exploration of the possibilities has just begun. But before anyone gets carried away with trying to create an XML circus on the web, I suggest we set up a safety net to keep any high-flyers from breaking their necks.

JavaScript Error Handling Basics

JavaScript has come a long way since its earlier versions, which were crude, lacking in features, and just poorly implemented. Newer browsers not only support the try/catch/finally keywords you will recognize from C++ and Java, they also implement an onerror event that can trap any error conditions that arise during runtime. Usage is pretty straightforward:

function riskyBusiness() {
	try {
		riskyOperation1();
		riskyOperation2();
	} catch (e) {
		// e is an object of type Error with
		// at least two properties: name and message
	} finally {
		// clean up our mess
	}
}
window.onerror = handleError; // safety net to trap all errors
function handleError(message, URI, line) {
	// alert the user that this page may not respond properly
	return true; // this will stop the default message
}

A Practical Example: Persisting Client-side Errors to your Server

Now that we have the basics of XMLHttpRequest and JavaScript error handling, let's look at an implementation that ties the two together. You'd think that JavaScript errors would be easy to spot given the prevalent "Yellow Triangle of Death", but I still see them slip past the QA departments of several blue chip organizations' public-facing web sites.

Figure 1

So, here I will present a method for trapping errors and logging them back to the server in the hope that someone might be alerted to fix it. First, let's consider our client. The client should provide a class to be used as a singleton Logger object that can transparently handle the gritty details.

First we create the constructor:

// singleton class constructor
function Logger() {
	// fields
	this.req;

	// methods
	this.errorToXML = errorToXML;
	this.log = log;
}

Next, we define the method that will serialize an Error object into XML. By default, an Error object only has two properties, name and message, but we will also check for a third called location which may be useful.

// map an error to an XML document
function errorToXML(err) {
	var xml = '<?xml version="1.0"?>\n' +
		'<error>\n' +
		'<name>' + err.name + '</name>\n' +
		'<message>'  + err.message + '</message>\n';
	if (err.location) xml += '<location>' + err.location +
		'</location>';
	xml += '</error>';
	return xml;
}

Next is the log method. This is the meat and potatoes of the script that really brings together the principles described above. Notice that we are using the POST method for our call. What I am essentially creating here is a bespoke web service that is write-only and creates new records on each successful request. Therefore, POST is the only appropriate option.

// log method of Logger class
function log(err) {
	// feature sniff
	if (window.XMLHttpRequest) this.req = new XMLHttpRequest();
	else if (window.ActiveXObject) this.req =
		new ActiveXObject("Microsoft.XMLHTTP");
	else return; // throw up our hands in despair
	// set the method and URI
	this.req.open("POST", "/cgi-bin/AjaxLogger.cgi");
	// set the request headers. REFERER will be the top-level
	// URI which may differ from the location of the error if
	// it occurs in an included .js file
	this.req.setRequestHeader('REFERER', location.href);
	this.req.setRequestHeader('content-type', 'text/xml');
	// function to be called when the request is complete
	this.req.onreadystatechange = errorLogged;
	this.req.send(this.errorToXML(err));
	// if the request doesn't complete in 10 seconds,
	// something is up
	this.timeout = window.setTimeout("abortLog();", 10000);
}

The last part of our class definition is to create an instance of the Logger class. There should be only one instance of this class.

// should only be one instance of the logger
var logger = new Logger();

The last two functions are just there for housekeeping. If something goes wrong while logging the error, there is not much we can do except bother the user. Hopefully, it will never come to this. These are not class methods since the events will not have references to our object, but will refer to the logger instance we just created.

// we tried, but if there is a connection error, it is hopeless
function abortLog() {
	logger.req.abort();
	alert("Attempt to log the error timed out.");
}

// called when the state of the request changes
function errorLogged() {
	if (logger.req.readyState != 4) return;
	window.clearTimeout(logger.timeout);
	// request completed
	if (logger.req.status >= 400)
		alert('Attempt to log the error failed.');
}

All of the preceding code can be wrapped up into one .js file that can be included on any (or every) page in your site. Here is an example of how to include it and put it to good use:

<script type="text/javascript" src="Logger.js"></script>
<script type="text/javascript">
function trapError(msg, URI, ln) {
	// wrap our unknown error condition in an object
	var error = new Error(msg);
	error.location = URI + ', line: ' + ln; // add custom property
	logger.log(error);
	warnUser();
	return true; // stop the yellow triangle
}
window.onerror = trapError;

function foo() {
	try {
		riskyOperation();
	} catch (err) {
		// add custom property
		err.location = location.href + ', function: foo()';
		logger.log(err);
		warnUser();
	}
}
function warnUser() {
	alert("An error has occurred while processing this page."+
		"Our engineers have been alerted!");
	// drastic action
	location.href = '/path/to/error/page.html';
}
</script>

Now that we have seen how to integrate the logger into our HTML pages, all that is left is to define some way of receiving and translating the message. I have chosen a lowest common denominator approach and built a CGI script in Perl that uses one of my favorite modules, XML::Simple, to parse the post data, and CGI::Carp to pipe the results directly to httpd error log, thus saving your system administrators from having to monitor another log. This script also includes some good examples of appropriate response codes for different success and failure conditions.

use CGI;
use CGI::Carp qw(set_progname);
use XML::Simple;
my $request = CGI->new();

my $method = $request->request_method();
# method must be POST
if ($method eq 'POST') {
	eval {
		my $content_type = $request->content_type();
		if ($content_type eq 'text/xml') {
			print $request->header(-status => 
			    '415 Unsupported Media Type', -type => 'text/xml');
			croak "Invalid content type: $content_type\n";
		}
		# when method is POST and the content type is neither
		# URI encoded nor multipart form, the entire post
		# is stuffed into one param: POSTDATA
		my $error_xml = $request->param('POSTDATA');
		my $ref = XML::Simple::XMLin($error_xml);
		my ($name, $msg, $location) =
			($ref->{'name'}, $ref->{'message'}, '');
		$location = $ref->{'location'} if (defined($ref->{'location'}));
		# this will change the name of the carper in the log
		set_progname('Client-side error');
		my $remote_host = $request->remote_host();
		carp "name: [$name], msg: [$msg], location: [$location]";
	};
	if ($@) {
		print $request->header(-status => '500 Internal server error',
			-type => 'text/xml');
		croak "Error while logging: $@";
	} else {
		# this response code indicates that the operation was a
		# success, but the client should not expect any content
		print $request->header(-status => '204 No content',
			-type => 'text/xml');
	}
} else {
	print $request->header(-status => '405 Method not supported',
		-type => 'text/xml');
	croak "Unsupported method: $method";
}

And that's all there is to it! Now, the next time some slippery JavaScript gets into the system, you can expect your log monitors to start flashing red lights and your client-side developers to get calls in the middle of the night.

XML Hacks

Related Reading

XML Hacks
100 Industrial-Strength Tips and Tools
By Michael Fitzgerald


Read Online--Safari
Search this book on Safari:
 

Code Fragments only

Comment on this articleShare your experience in our forums.
(* You must be a
member of XML.com to use this feature.)
Comment on this Article


Titles Only Titles Only Newest First
  • Integration without framework
    2005-07-31 13:22:28 mrajax [Reply]

    You can definitely use it right now. I have been developing xoscript for 3 years and my experience as an integration specialist caused me to create a solution that would integrate with existing web applications. None of my clients would allow a rewrite from the ground up. http://www.xoscript.org is an open source project that uses one servlet and one custom tag. There is no extra code that you have to write either Java or JavaScript. Support for 33 of the java.util.* classes are written in JavaScript and the solution works cross browser. You do not have to mess with xml or JavaScript to get it working.

  • How it all started
    2005-06-21 17:58:53 Orrell [Reply]

    If you want to know how this whole thing even started you can read this blog.


    http://blogs.technet.com/exchange/archive/2005/06/21/406646.aspx

  • XML-RPC on javascript was already available
    2005-05-13 16:32:00 Oyku [Reply]

    It is always good to see such things go mainstream. Although it is very handy to post get XML without interrupting the user but I have succesfully implemented XML-RPC in javascript to solve very complex interface issues, maybe not ipossible but not quite elegant otherwise. All were large scale commercial apps and all hit the users at first sight. Data communication without submitting and refreshing. It looked like black magic.


    The straight forward nature of calling "functions" and using them over XML-RPC provides a more firm ground and lets the programmer to focus on the job rather than on the limitations of the browser.


    There were many javascript XML-RPC implementations out there long before AJAX became the new kid on the block.


    Although it is very handy to have XML processing at hand in javascript I just want to point out that this is no silver bullet. It is just an elegant solutions to a known problem. It is not a new solution

  • XML-RPC on javascript was already available
    2005-05-13 16:31:58 Oyku [Reply]

    It is always good to see such things go mainstream. Although it is very handy to post get XML without interrupting the user but I have succesfully implemented XML-RPC in javascript to solve very complex interface issues, maybe not ipossible but not quite elegant otherwise. All were large scale commercial apps and all hit the users at first sight. Data communication without submitting and refreshing. It looked like black magic.


    The straight forward nature of calling "functions" and using them over XML-RPC provides a more firm ground and lets the programmer to focus on the job rather than on the limitations of the browser.


    There were many javascript XML-RPC implementations out there long before AJAX became the new kid on the block.


    Although it is very handy to have XML processing at hand in javascript I just want to point out that this is no silver bullet. It is just an elegant solutions to a known problem. It is not a new solution

  • It works
    2005-05-13 11:09:20 Edmond Woychowsky [Reply]

    I used similar methods to implement a shopping cart a few years ago, proving that these techniques really do work. In fact, the hardest part of the implementation was explaining that the actual application was not a mock-up. Management had real problems with the concept that an unload/reload cycle wasn’t necessary. It took about two hours of being told that it can’t work and demonstrating that it did work to convince them. Of course this doesn’t mean that the techniques used weren’t blamed for everything from the company’s lower stock price to the weather.

  • A few points
    2005-05-12 07:25:59 TomPotts [Reply]

    I havent got into AJAX so I could be barking in the wrong forest here but a few notes
    1) browser compatibility - this needs moding to work in other browsers - Firefox is taking off like a rocket!
    2)dont forget json
    3)extend these techniques a little and youc an do almost anything you want in the browser - you may need a hidden frame or two...

  • XMLHttpRequest
    2005-05-12 07:08:47 timdown [Reply]

    I am far from a Microsoft apologist but I feel I should point out that you're unfair to Microsoft at the top of this article:


    "... although, as usual, Microsoft has taken a slightly different tack on implementation and requires some special care. While our more progressive friends directly implement XMLHttpRequest, IE requires that you instantiate an ActiveXObject with the same properties".


    In fact Microsoft introduced the XMLHttpRequest API to the browser before any of "our more progressive friends" and set the standard that others now follow. Microsoft's implementation is admittedly less convenient, and one side effect of XMLHttpRequest being implemented as an ActiveX object is that it prevents you from addng arbitrary (expando) properties, but I think their crime in this case is one of resting on their laurels and not releasing a new browser that implements XMLHttpRequest as a native object.

    • XMLHttpRequest
      2006-06-24 11:30:58 mgainty [Reply]

      Using Proprietary ActiveX Objects that will not work in other Browsers can increase development time


  • Buzzword maybe, but not new...
    2005-05-11 23:30:03 NicRoche [Reply]

    Yes it is real. We have been using this client-side syncing of XML data for business applications for three years now (MSXML was not long out of beta). One client-server application uses all the logic from the client, sending and receiving data and new logic via xml. The data enters SQL Server as XML after a quick tranform. The Data then is periodically inserted into large Oracle implementation. Mature yes; and can be used by any scripting language the host browser supports...

    • Buzzword maybe, but not new...
      2007-05-30 08:42:06 profesjonalna [Reply]

      Thank You for another very interesting article Drew. btw. I must say that I really enjoyed reading all of Your posts. It’s interesting to read ideas, and observations from someone else’s point of view… it makes you think more. So please try to keep up the great work all the time. Greetings
      Tomasz Gorski
      http://www.profesjonalna-reklama.pl