Page 1

Development AJAX Growing Up – Part 1

AJAX Growing Up – Part I Enhancing the Reliability of AJAX Applications by Streamlining XMLHttpRequests By Robert-Jan de Vries

Since AJAX has become a mainstream technology for a ‘richer’ Internet, most programmers have successfully implemented it or have at least experimented with it’s possibilities. As the complexity of AJAXbased applications grows, so does the need for reliability. This article is divided into two parts, each part aiming to hand you a practical solutions to a problem that you are likely to run into on your quest for a reliable web application.

Introduction

The Origin of Race Conditions

In a typical AJAX-based web application, client-server communication is performed using XMLHttp requests in JavaScript on the client side, answered by XML-responses on the server side. Sounds straight forward and, indeed, the first simple transaction can be set up quite easily. But when your application grows and so does the use of XMLHttpRequests, you may run into some problems. When you come to the point where all important operations rely on AJAX communication, the overall usability heavily depends on the availability of the requests and subsequent responses. It is at that point you should ensure the reliability of your application is not compromised by a common flaw in the handling of the requests. Such a problem may occur when two or more client-side requests are sent to the server (almost) simultaneously, both demanding a server response. This is known as a race-condition (both requests race each other to trigger the response first); the unexpected results caused by such race-conditions have induced many a programmer’s headache. In the following sections, I will discuss this issue more thoroughly, followed by the introduction of a possible solution. The second common problem, which I will discuss in the next issue, is related to the feedback of XMLHttpRequests that result in a recoverable PHP error on the server side.

As illustrated in Table 1, the origin of the described race conditions is found at the client-side of your application, and precisely in the AJAX calls. In fact, the problematic behavior is completely in line with what may be expected of Asynchronous JavaScript and XML, asynchronous being the keyword. Whenever an application performs two or more XMLHttpRequests at the same time (or almost at the same time), there is no way for you to tell which one reaches the server first. This is the ‘feature’ that comes with the asynchronous part. Admittedly, there is a way to send your XMLHttpRequest synchronously, but this locks up the browser during the execution of a request and, generally, takes away all the reasons for implementing AJAX in the first place. So why should you care which request reaches the server first? As long as all requests are handled, what does it matter in what order they are processed by the server? Ironically, this is probably true in most cases. But this is also what makes debugging race conditions a programmer’s nightmare. As there is no way to predict which one of two simultaneously sent XMLHttpRequests is received by your script first, an unexpected error in your PHP code may occur only rarely. This means that you may be unable to reproduce a bug or that you can’t pinpoint the context of its occurrence. That alone should

38 I n t e r n a t i o n a l P H P M a g a z i n e 1 2 . 2 0 0 6


AJAX Growing Up – Part I Development

be reason enough to avoid race conditions from ever happening. On top of this, there appear to be situations in which all data stored in a PHP session is lost as a result of a race condition[1]. This data cannot be recovered, thus causing a lapse in the data integrity of your application. Issue

Origin

Results in a bug/ problem on the...

Race condition Server-side error

Client-side Server-side

Server-side Client-side

Table 1: Origin and Place of Resulting Problem of the Two Issues

Avoiding race conditions By laying out the issue of race conditions and the impact this may have on your code, I hope to have convinced you that this problem should be properly addressed to ensure optimum reliability of your application. Since race conditions are caused on the client side of AJAX calls, this seems to be the designated place to solve the problem.

Meet my Friend Before diving into the JavaScript code, I would like to introduce you to my friend, Sarissa. As described on Sarissa’s homepage at SourceForge, “Sarissa is a cross-browser ECMAScript library for client side XML manipulation, including loading XML from URLs or strings, performing XSLT transformations, XPath queries and more. Supported: Gecko (Mozilla, Firefox, and so on), IE, KHTML (Konqueror, Safari)”[2]. For us, in this context, Sarissa ensures we can use the same interface for sending and receiving XMLHttpRequests in all major browsers. In that regard, Sarissa takes away a lot of the hassle of writing cross-browser AJAX. Installing Sarissa is as straightforward as downloading and unpacking the application files in the folder of your choice (typically: /path/to/your/project/ sarissa/). In Listing 1, you can see how a standard XMLHttpRequest can be sent using Sarissa. Discussing Sarissa’s wide range of features is beyond the scope of this article, so I recommend starting at the project’s documentation pages[3].

Listing 1 A Simple XMLHttpRequest using Sarissa <script src=’sarissa/sarissa.js’ type=’text/javascript’></script> <script type=’text/javascript’> // create a Sarissa XMLHttpRequest object var xmlhttp = new XMLHttpRequest(); // set basic variables: target url, request // method (get or post) and variables to send var url = ‘myscript.php’; var method = ‘post’; var vars = ‘foo=bar&msg=hello’; // open XMLHttpRequest using indicated method, // url. The last boolean argument sets the // request mode to “ansynchronous”. xmlhttp.open(method, url, true); // set callback function to handle any change in // the state of the request. xmlhttp.onreadystatechange = handleResponse; // set request header, if needed. xmlhttp.setRequestHeader(‘Content-Type’, ‘application/x-wwwform-urlencoded’); // send the request. xmlhttp.send(vars); </script>

Fig. 1: Flow of Requests within the XMLHttp Object

Queueing XMLHttpRequests Because the cause of a racing condition lies in the fact that two XMLHttpRequests are sent at nearly the same time, the solution lies in avoiding this from happening; we should make sure that

International PHP Magazine 12.2006

39


Development AJAX Growing Up – Part 1

no request is sent to the server before all previous requests have been completed. This can be achieved by setting up a central area in the client application, where all XMLHttpRequests are put in a list and get handled consecutively. This list will be referred to as the request queue. The place where queueing and processing of all XMLHttpRequests is centralized, is a JavaScript object called XMLHttp. Diagram 1 illustrates the flow of requests within the XMLHttp object. Listing 2 shows the JavaScript code involved.

An Overview What the XMLHttp object essentially does, is create an extra layer in your JavaScript through which all XMLHttpRequests are sent. When the object is in place, instead of using the Sarissa XMLHttpRequest object directly, you use the new XMLHttp object as the interface for AJAX requests. The XMLHttp object then appends your request to its internal queue, after which the queue is processed. Each request in the queue has its own flag that indicates the status of that particular request (This flag is used in the XMLHttp object only and has nothing to do with the ‘readystate’ of the Sarissa XMLHttpRequest object). The default status with which a request is added to the queue is ‘waiting’, which means that the request is waiting to be processed. As soon as a request reaches the top of the queue and is processed for sending, its state is changed to ‘inprogress’. When the request is sent to the server and then properly responded to by the server, it is removed from the queue. The queue is then processed again to see if there are any requests left waiting.

Request Timeout In some cases, a request that has been sent from the client, is not followed by the (expected) response from the server. In that scenario, the top request in the queue is never removed, with the result that subsequent requests would never be processed, because they are all waiting for a response that is never coming. To avoid this from happening, the code that processes the waiting queue has a routine for checking whether a request has timed out. Each request in the queue is given a timestamp at the moment it is sent to the server. When the queue is processed and the request that was last sent has been ‘inprogress’ for more than half a second, its status is reset to ‘waiting’ so that it will be sent again when the queue is being processed. Its timestamp is not reset though. If a request has been in the queue for more than twice the allowed time (greater than one second, that is), it is removed from the queue altogether.

40 I n t e r n a t i o n a l P H P M a g a z i n e 1 2 . 2 0 0 6

The default timeout of 0.5 seconds should of course be adjusted to suit the needs of your production environment. See Listing 2.

Listing 2 A Central Object for Sending XMLHttpRequests /** * Set of functions to take care of dynamic * loading of data into the application * using XMLHttpRequests. * * @author R.J.T. de Vries * <rdevries@thirdwave.nl> */ function XMLHttp() { /** * Reference to this object for internal use. * @var object that */ var that = this; /** * Global xmlhttp object. * @var object xmlhttp */ this._xmlhttp = null; /** * Queue of xmlhttp requests. * @var array this._requestqueue */ this._requestQueue = new Array(); /** * Add an XML request to the request queue * server using the internal _addToQueue() * method. * * @param string url * url to send * @param string method * either ‘get’ or ‘post’ * @return void */ this.xmlRequest = function(url, method, vars){ this._addToQueue(url, method, vars, ‘handleReadyStateChange’); this._processQueue(); } // this.xmlRequest() /** * Event handler for the ‘readystatechange’ * event of the xmlhttp object. * * @return void */


AJAX Growing Up – Part I Development

Listing 2 (...contd) this.handleReadyStateChange = function() { if ( that.getReadyState() == 4 ) { that.processXML(); } } // this.handleReadyStateChange() /** * Called from readyState handler when XML * request is complete. * * @return void */ this.processXML = function() { // code for processing XML response goes // here. var response = this._xmlhttp.responseXML; // after the current response has been // processed, remove completed request from // waiting queue and process queue for any // new requests. this._removeFromQueue(); this._processQueue(); } // this.processXML() /** * Return the readyState property of the * XMLHttpRequest object (this._xmlhttp). * * @return mixed * int) readyState or false if _xmlhttp * does not exist. */ this.getReadyState = function() { if ( !is_object(this._xmlhttp) ) return false; return this._xmlhttp.readyState; } // this.getReadyState() /** * Send XML request. * * @param string url * url to use for sending xml request. * @param string method * either ‘GET’ or ‘POST’ * @param string vars * vars to use for POST method xml request. * @param string readyhandler * function to call when request is * completed. * @return boolean * true on success, false on failure */ this._sendXMLRequest = function(url, method, vars, handler) { this._xmlhttp = new XMLHttpRequest(); if ( !is_object(this._xmlhttp) )

return false; url = escape(url); this._xmlhttp.open(method, url, true); this._xmlhttp.onreadystatechange = eval(handler); this._xmlhttp.setRequestHeader(‘Content-Type’, ‘application/xwww-form-urlencoded’); this._xmlhttp.send(vars); return true; } // this._sendXMLRequest() /** * Add request with given variables to queue. * * @param url, vars, method, handler * @return boolean * true on success, false on failure * @see this.xmlRequest() */ this._addToQueue = function(url, method, vars, handler) { var request = new Array(url, method, vars, handler, ‘waiting’); this._requestQueue[(this._requestQueue.length)] = request; return true; } // this._addToQueue() /** * Remove last item from request queue (or * really the first item). Re-index the queue * after that, so that the first item has * index zero. * * @return boolean * true on success, false on failure. */ this._removeFromQueue = function() { var arr = new Array(); for ( var i = 1; i < this._requestQueue.length; i++ ) { arr[(i - 1)] = this._requestQueue[i]; } // for() this._requestQueue = arr; return true; } // this._removeFromQueue() /** * Check to see if there are no requests in * the queue that are older that t seconds, * because they will probably never return * anymore and WILL clogg the queue. * * @param integer t * maximum number of seconds before a * request times out. * @return void */ this._queueHasTimedout = function(t) { var d = new Date(), now = d.getTime();

International PHP Magazine 12.2006

41


Development AJAX Growing Up – Part 1

Listing 2 (...contd) // set default t to 0.5 seconds if ( !t ) t = 0.5; // convert seconds to milliseconds. t = t * 1000; if ( is_object(this._requestQueue[0]) ) { if ( now - this._requestQueue[0][5] > t ) { if ( now - this._requestQueue[0][5] > (t * 2) ) this._ removeFromQueue(); this._requestQueue[0][4] = ‘waiting’; return true; } } return false; } // this._queueHasTimedout() /** * Process the waiting queue. * * @return boolean * true if new request was started, false * otherwise. */ this._processQueue = function() { var d = new Date(); if ( !this._requestQueue.length ) return false; if ( this._requestQueue[0][4] == ‘inprogress’ ) { if ( !this._queueHasTimedout() ) return false; } if ( this._requestQueue[0][4] == ‘waiting’ ) { var req = this._requestQueue[0]; this._requestQueue[0][4] = ‘inprogress’; if (!is_object(this._requestQueue[0][5])){ this._requestQueue[0][5] = d.getTime(); } return this._sendXMLRequest(req[0], req[1], req[2], req[3]); } } // this._processQueue() } // end of object XMLHttp()

The XMLHttp Object in Detail Now that you have a general idea of what happens inside of the XMLHttp object, I will discuss the internals in greater detail, so that you are able to alter the code to suit your needs. The XMLHttp object contains three variables that are used throughout the object. The first one, ‘that’, is an alias for the ‘this’ keyword and is used to make the XMLHttp object available to the XMLHttp.handleReadyStateChange() method. By referring to ‘this’ in the scope of the XMLHttp.handleReadyStateChange() method, we would be referring to the Sarissa XMLHttpRequest object, as the

42 I n t e r n a t i o n a l P H P M a g a z i n e 1 2 . 2 0 0 6

Listing 3 Example of How to Use the XMLHttp Object <html> <head> <title>AJAX request queueing</title> <script src=’xmlhttp.js’ type=’text/javascript’></script> <script> var xmlhttp = ‘’; function init() { xmlhttp = new XMLHttp; } function submitform(f) { var url = ‘checkemail.php’; var method = ‘post’; var vars = ‘emailaddress=’ + f.email.value; xmlhttp.xmlRequest(url, method, vars); return false; } // submitform() </script> </head> <body onload=’init()’> <form onsubmit=’return submitform(this)’> E-mail address to check for validness: <input type=’text’ name=’email’/><br /> <input type=’submit’ value=’Submit’/> </form> </body> </html>

method is an event handler for the latter. Therefore, the reference is made to ‘that’, which gives access to the XMLHttp object. The second variable, XMLHttp._xmlhttp, contains the XMLHttpRequest object that is instantiated for the request that was last sent. Please note that a new XMLHttpRequest object is created for each request that is sent to the server. Using the same object to handle all requests leads to JavaScript errors in most browsers. Since no requests are sent simultaneously, the previous XMLHttpRequest is overwritten when a new instance is created. The last member-variable is a multi-dimensional array called XMLHttp._requestQueue and contains all queued requests. Each queued request itself is an array of six elements, that is, url, method, vars, handler, status of the request and (when available) the timestamp of the time the request was sent to the server. The most important method of the XMLHttp object is XMLHttp.xmlRequest(). This is the method you use when sending an XMLHttpRequest. The method takes three arguments: (1) the url


AJAX Growing Up – Part I Development

to be called in the request (for example, myscript.php or http://www. mydomain.com/anotherscript.php), (2) the method of the request (get

or post) and (3) the variables that you want to send in case you are using the post method (for example, foo=bar&output=xml). When using the get method, you may add any variables to the URL itself (for example, myscript.php?foo=bar&output=xml) instead of using the third argument. The method does not send the XMLHttpRequest itself, rather it queues the request using the XMLHttp._addToQueue() method. After that, it calls the XMLHttp._processQueue() method, which takes care of handling the queue in the appropriate order.

Practical Application of the XMLHttp Object Listing 3 shows a practical example of how the XMLHttp object may be used. In this setup, the XMLHttp object is used to send a request for validating an e-mail address. The xmlhttp.js file— containing the code of Listing 2—is first included as an external script. When the body of the HTML file is loaded, an initialization function is called to create the actual xmlhttp object. Feel free to create a separate JavaScript include file that takes care of these preparations. After the initialization, the object is available for the entire JavaScript scope. The submitform() function shows a simple example of how a request should be prepared before the arguments can be passed to the xmlRequest() method.

Conclusion When you are building a web-based application that leans heavily on the use of AJAX, you should be hundred percent confident that the communication between client and server is rock-solid. One of the issues that threaten the reliability is the occurrence of a race condition, resulting from one or more XMLHttpRequests that are instigated simultaneously. The unpredictable consequences of

these occurrences are often hard to reproduce. It’s even harder to reduce to the original problem and therefore is very frustrating to debug. Preventing a race condition from occurring on the server side by using a queue for subsequential requests on the client side has proven to be an adequate solution. The added benefit of using this approach is that request timeouts can be handled actively and may be fed back to the user. In the end, taking care of these potential hazards to the reliability of your code in an early stage will drastically improve the overall user experience and make your application a ‘richer’ environment for its users to work in. In next issue, I will introduce you to a solution that will greatly improve the user feedback of your AJAX-based application.

Robert-Jan de Vries

is the founder of ThirdWave, a company that has been successfully developing webbased applications for a wide variety of customers since 1999. ThirdWave is based in Leiden, The Netherlands. Robert-Jan has been a PHP enthousiast ever since his first rendezvous with version 3.23. He now spends most of his energy on unraveling the power of PHP in full-blown webbased applications, such as ThirdWave’s Content Management System and several tailor made applications for the pharmaceutical, medical and CRM industry.

Resources & References [1] Troubles with Asynchronous Ajax Requests and PHP Sessions, by Marc Wandschneider: http://www.chipmunkninja.com/article/asyncsessions [2] Sarissa project homepage on SourceForge.net: http://sourceforge.net/projects/ sarissa [3] Sarissa documentation: http://sarissa.sourceforge.net/doc/

www.phpmag.net

Dive into the World of PHP

Daily News

Online Articles

Opinion Polls

I n t e r n a t i o n a l P H P M a g a z i n e 1 2 . 2 0 0 6 43 Book Club Selections

Ajax growing up  

Ajax growing up

Read more
Read more
Similar to
Popular now
Just for you