20.2. XMLHttpRequest Examples and UtilitiesAt 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 UtilitiesExample 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
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
20.1.7. Getting Headers OnlyOne 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
20.1.8. HTTP POSTHTML 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
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 ResponsesIn 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:
<!-- 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()
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 RequestA 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
|