Section 20.2. XMLHttpRequest Examples and Utilities


20.2. XMLHttpRequest Examples and Utilities

At the beginning of this chapter, I presented the HTTP.newRequest() utility function, which can obtain an XMLHttpRequest object for any browser. It is also possible to simplify the use of XMLHttpRequest with utility functions. The subsections that follow include sample utilities.

20.1.6. Basic GET Utilities

Example 20-2 is a very simple function that can handle the most common use of XMLHttpRequest: simply pass it the URL you want to fetch and the function that should be passed the text of that URL.

Example 20-2. The HTTP.getText() utility

 /**  * Use XMLHttpRequest to fetch the contents of the specified URL using  * an HTTP GET request. When the response arrives, pass it (as plain  * text) to the specified callback function.  *  * This function does not block and has no return value.  */ HTTP.getText = function(url, callback) {     var request = HTTP.newRequest();     request.onreadystatechange = function() {         if (request.readyState == 4 && request.status == 200)             callback(request.responseText);     }     request.open("GET", url);     request.send(null); }; 

Example 20-3 is a trivial variant used to fetch XML documents and pass their parsed representation to a callback function.

Example 20-3. The HTTP.getXML() utility

 HTTP.getXML = function(url, callback) {     var request = HTTP.newRequest();     request.onreadystatechange = function() {         if (request.readyState == 4 && request.status == 200)             callback(request.responseXML);     }     request.open("GET", url);     request.send(null); }; 

20.1.7. Getting Headers Only

One of the features of XMLHttpRequest is that it allows you to specify the HTTP method to use. The HTTP HEAD request asks the server to return the headers for a given URL without returning the content of that URL. This might be done, for example, to check the modification date of a resource before downloading it.

Example 20-4 shows how you can make a HEAD request. It includes a function for parsing HTTP header name/value pairs and storing them as the names and values of the properties of a JavaScript object. It also introduces an error handler function that is invoked if the server returns a 404 or other error code.

Example 20-4. The HTTP.getHeaders() utility

 /**  * Use an HTTP HEAD request to obtain the headers for the specified URL.  * When the headers arrive, parse them with HTTP.parseHeaders() and pass the  * resulting object to the specified callback function. If the server returns * an error code, invoke the specified errorHandler function instead. If no  * error handler is specified, pass null to the callback function.  */ HTTP.getHeaders = function(url, callback, errorHandler) {     var request = HTTP.newRequest();     request.onreadystatechange = function() {         if (request.readyState == 4) {             if (request.status == 200) {                 callback(HTTP.parseHeaders(request));             }             else {                 if (errorHandler) errorHandler(request.status,                                                request.statusText);                 else callback(null);             }         }     }     request.open("HEAD", url);     request.send(null); }; // Parse the response headers from an XMLHttpRequest object and return // the header names and values as property names and values of a new object. HTTP.parseHeaders = function(request) {     var headerText = request.getAllResponseHeaders();  // Text from the server     var headers = {}; // This will be our return value     var ls = /^\s*/;  // Leading space regular expression     var ts = /\s*$/;  // Trailing space regular expression     // Break the headers into lines     var lines = headerText.split("\n");     // Loop through the lines     for(var i = 0; i < lines.length; i++) {         var line = lines[i];         if (line.length == 0) continue;  // Skip empty lines         // Split each line at first colon, and trim whitespace away         var pos = line.indexOf(':');         var name = line.substring(0, pos).replace(ls, "").replace(ts, "");         var value = line.substring(pos+1).replace(ls, "").replace(ts, "");         // Store the header name/value pair in a JavaScript object         headers[name] = value;     }     return headers; }; 

20.1.8. HTTP POST

HTML forms are (by default) submitted to web servers using the HTTP POST method. With POST requests, data is passed to the server in the body of the request, rather than encoding it into the URL itself. Since request parameters are encoded into the URL of a GET request, the GET method is suitable only when the request has no side effects on the serverthat is, when repeated GET requests for the same URL with the same parameters can be expected to return the same result. When there are side effects to a request (such as when the server stores some of the parameters in a database), a POST request should be used instead.

Example 20-5 shows how to make a POST request with an XMLHttpRequest object. The HTTP.post() method uses the HTTP.encodeFormData() function to convert the properties of an object to a string form that can be used as the body of a POST request. This string is then passed to the XMLHttpRequest.send() method and becomes the body of the request. (The string returned by HTTP.encodeFormData() can also be appended to a GET URL; just use a question-mark character to separate the URL from the data.) Example 20-5 also uses the HTTP._getResponse() method. This method parses the server's response based on its type and is implemented in the next section.

Example 20-5. The HTTP.post() utility

 /**  * Send an HTTP POST request to the specified URL, using the names and values  * of the properties of the values object as the body of the request.  * Parse the server's response according to its content type and pass  * the resulting value to the callback function. If an HTTP error occurs,  * call the specified errorHandler function, or pass null to the callback  * if no error handler is specified.  **/ HTTP.post = function(url, values, callback, errorHandler) {     var request = HTTP.newRequest();     request.onreadystatechange = function() {         if (request.readyState == 4) {             if (request.status == 200) {                 callback(HTTP._getResponse(request));             }             else {                 if (errorHandler) errorHandler(request.status,                                                request.statusText);                 else callback(null);             }         }     }     request.open("POST", url);     // This header tells the server how to interpret the body of the request.     request.setRequestHeader("Content-Type",                              "application/x-www-form-urlencoded");     // Encode the properties of the values object and send them as     // the body of the request.     request.send(HTTP.encodeFormData(values)); }; /**  * Encode the property name/value pairs of an object as if they were from  * an HTML form, using application/x-www-form-urlencoded format  */ HTTP.encodeFormData = function(data) {     var pairs = [];     var regexp = /%20/g; // A regular expression to match an encoded space     for(var name in data) {         var value = data[name].toString();         // Create a name/value pair, but encode name and value first         // The global function encodeURIComponent does almost what we want,         // but it encodes spaces as %20 instead of as "+". We have to         // fix that with String.replace()         var pair = encodeURIComponent(name).replace(regexp,"+") + '=' +             encodeURIComponent(value).replace(regexp,"+");         pairs.push(pair);     }     // Concatenate all the name/value pairs, separating them with &     return pairs.join('&'); }; 

Example 21-14 is another example that makes a POST request with an XMLHttpRequest object. That example invokes a web service, and instead of passing form values in the body of the request, it passes the text of an XML document.

20.1.9. HTML, XML, and JSON-Encoded Responses

In most of the examples shown so far, the server's response to an HTTP request has been treated as a plain-text value. This is a perfectly legal thing to do, and there is nothing that says that web servers can't return documents with a content type of "text/plain". Your JavaScript code can parse such a response with the various String methods and do whatever is needed with it.

You can always treat the server's response as plain text, even when it has a different content type. If the server returns an HTML document, for example, you might retrieve the content of that document with the responseText property and then use it to set the innerHTML property of some document element.

There are other ways to handle the server's response, however. As noted at the beginning of this chapter, if the server sends a response with a content type of "text/xml", you can retrieve a parsed representation of the XML document with the responseXML property. The value of this property is a DOM Document object, and you can search and traverse it using DOM methods.

Note, however that using XML as a data format may not always be the best choice. If the server wants to pass data to be manipulated by a JavaScript script, it is inefficient to encode that data into XML form on the server, have the XMLHttpRequest object parse that data to a tree of DOM nodes, and then have your script traverse that tree to extract data. A shorter path is to have the server encode the data using JavaScript object and array literals and pass the JavaScript source text to the web browser. The script then "parses" the response simply by passing it to the JavaScript eval() method.

Encoding data in the form of JavaScript object and array literals is known as JSON, or JavaScript Object Notation.[*] Here are XML and JSON encodings of the same data:

[*] Learn more about JSON at http://json.org. The idea was introduced by Douglas Crockford, and this web site includes pointers to JSON encoders and decoders for a variety of programming languages; it can be a useful data encoding even if you are not using JavaScript.

 <!-- XML encoding --> <author>   <name>Wendell Berry</name>   <books>     <book>The Unsettling of America</book>     <book>What are People For?</book>   </books> </author> // JSON Encoding {   "name": "Wendell Berry",   "books": [     "The Unsettling of America",     "What are People For?"   ] } 

The HTTP.post() function shown in Example 20-5 invokes the HTTP._getResponse() function, which looks at the Content-Type header to determine the form of the response. Example 20-6 is a simple implementation of HTTP._getResponse() that returns XML documents as Document objects, evaluates JavaScript or JSON documents with eval(), and returns any other content as plain text.

Example 20-6. HTTP._getResponse()

 HTTP._getResponse = function(request) {     // Check the content type returned by the server     switch(request.getResponseHeader("Content-Type")) {     case "text/xml":         // If it is an XML document, use the parsed Document object.         return request.responseXML;     case "text/json":     case "text/javascript":     case "application/javascript":     case "application/x-javascript":         // If the response is JavaScript code, or a JSON-encoded value,         // call eval() on the text to "parse" it to a JavaScript value.         // Note: only do this if the JavaScript code is from a trusted server!         return eval(request.responseText);     default:         // Otherwise, treat the response as plain text and return as a string.         return request.responseText;     } }; 

Do not use the eval() method to parse JSON-encoded data, as is done in Example 20-6, unless you are confident that the web server will never send malicious executable JavaScript code in place of properly encoded JSON data. A secure alternative is to use a JSON decoder that parses JavaScript object literals "by hand" without calling eval().

20.1.10. Timing Out a Request

A shortcoming of the XMLHttpRequest object is that it provides no way to specify a timeout value for a request. For synchronous requests, this shortcoming is severe. If the server hangs, the web browser remains blocked in the send() method and everything freezes up. Asynchronous requests aren't subject to freezing; since the send() method does not block, the web browser can keep processing user events. There is still a timeout issue here, however. Suppose your application issues an HTTP request with an XMLHttpRequest object when the user clicks a button. In order to prevent multiple requests, it is a good idea to deactivate the button until the response arrives. But what if the server goes down or somehow fails to respond to the request? The browser does not lock up, but your application is now frozen with a deactivated button.

To prevent this sort of problem, it can be useful to set your own timeouts with the Window.setTimeout() function when issuing HTTP requests. Normally, you'll get your response before your timeout handler is triggered; in this case, you simply use the Window.clearTimeout() function to cancel the timeout. On the other hand, if your timeout is triggered before the XMLHttpRequest has reached readyState 4, you can cancel that request with the XMLHttpRequest.abort() method. After doing this, you typically let the user know that the request failed (perhaps with Window.alert()). If, as in the hypothetical example, you disabled a button before issuing the request, you would re-enable it after the timeout arrived.

Example 20-7 defines an HTTP.get() function that demonstrates this timeout technique. It is a more advanced version of the HTTP.getText() method of Example 20-2 and integrates many of the features introduced in earlier examples, including an error handler, request parameters, and the HTTP._getResponse() method described earlier. It also allows the caller to specify an optional progress callback function that is invoked any time the onreadystatechange handler is called with a readyState other than 4. In browsers such as Firefox that invoke this handler multiple times in state 3, a progress callback allows a script to display download feedback to the user.

Example 20-7. The HTTP.get() utility

 /**  * Send an HTTP GET request for the specified URL. If a successful  * response is received, it is converted to an object based on the  * Content-Type header and passed to the specified callback function.  * Additional arguments may be specified as properties of the options object.  *  * If an error response is received (e.g., a 404 Not Found error),  * the status code and message are passed to the options.errorHandler  * function. If no error handler is specified, the callback  * function is called instead with a null argument.  *  * If the options.parameters object is specified, its properties are  * taken as the names and values of request parameters. They are  * converted to a URL-encoded string with HTTP.encodeFormData() and  * are appended to the URL following a '?'.  *  * If an options.progressHandler function is specified, it is  * called each time the readyState property is set to some value less  * than 4. Each call to the progress-handler function is passed an  * integer that specifies how many times it has been called.  *  * If an options.timeout value is specified, the XMLHttpRequest  * is aborted if it has not completed before the specified number  * of milliseconds have elapsed. If the timeout elapses and an  * options.timeoutHandler is specified, that function is called with  * the requested URL as its argument.  **/ HTTP.get = function(url, callback, options) {     var request = HTTP.newRequest();     var n = 0;     var timer;     if (options.timeout)         timer = setTimeout(function() {                                request.abort();                                if (options.timeoutHandler)                                    options.timeoutHandler(url);                            },                            options.timeout);     request.onreadystatechange = function() {         if (request.readyState == 4) {             if (timer) clearTimeout(timer);             if (request.status == 200) {                 callback(HTTP._getResponse(request));             }             else {                 if (options.errorHandler)                     options.errorHandler(request.status,                                          request.statusText);                 else callback(null);             }         }         else if (options.progressHandler) {             options.progressHandler(++n);         }     }     var target = url;     if (options.parameters)         target += "?" + HTTP.encodeFormData(options.parameters)     request.open("GET", target);     request.send(null); }; 




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net