54
request.open("GET", // Begin a HTTP GET request
"data.csv"); // For the contents of this URL
The first argument to
open()
specifies the HTTP method or verb. This is a case-
insensitive string, but uppercase letters are typically used to match the HTTP protocol.
The “GET” and “POST” methods are universally supported. “GET” is used for most
“regular” requests, and it is appropriate when the URL completely specifies the re-
quested resource, when the request has no side effects on the server, and when the
server’s response is cacheable. The “POST” method is what is typically used by HTML
forms. It includes additional data (the form data) in the request body and that data is
often stored in a database on the server (a side effect). Repeated POSTs to the same
URL may result in different responses from the server, and requests that use this method
should not be cached.
In addition to “GET” and “POST”, the XMLHttpRequest specification also allows
“DELETE”, “HEAD”, “OPTIONS”, and “PUT” as the first argument to
open()
. (The
“HTTP CONNECT”, “TRACE”, and “TRACK” methods are explicitly forbidden as
security risks.) Older browsers may not support all of these methods, but “HEAD”, at
least, is widely supported and Example 18-13 demonstrates its use.
The second argument to
open()
is the URL that is the subject of the request. This is
relative to the URL of the document that contains the script that is calling
open()
. If
you specify an absolute URL, the protocol, host, and port must generally match those
of the containing document: cross-origin HTTP requests normally cause an error. (But
the XMLHttpRequest Level 2 specification allows cross-origin requests when the server
explicitly allows it; see §18.1.6.)
The next step in the request process is to set the request headers, if any. POST requests,
for example, need a “Content-Type” header to specify the MIME type of the request
body:
request.setRequestHeader("Content-Type", "text/plain");
If you call
setRequestHeader()
multiple times for the same header, the new value does
not replace the previously specified value: instead, the HTTP request will include mul-
tiple copies of the header or the header will specify multiple values.
You cannot specify the “Content-Length”, “Date”, “Referer”, or “User-Agent” headers
yourself: XMLHttpRequest will add those automatically for you and will not allow you
to spoof them. Similarly, XMLHttpRequest object automatically handles cookies, and
connection lifetime, charset, and encoding negotiations, so you’re not allowed to pass
any of these headers to
setRequestHeader()
:
Accept-Charset Content-Transfer-Encoding TE
Accept-Encoding Date Trailer
Connection Expect Transfer-Encoding
Content-Length Host Upgrade
Cookie Keep-Alive User-Agent
Cookie2 Referer Via
496 | Chapter 18: Scripted HTTP
58
You can specify an “Authorization” header with your request, but you do not normally
need to do so. If you are requesting a password-protected URL, pass the username and
password as the fourth and fifth arguments to
open()
, and XMLHttpRequest will set
appropriate headers for you. (We’ll learn about the optional third argument to
open()
below. The optional username and password arguments are described in the
reference section.)
The final step in making an HTTP request with XMLHttpRequest is to specify the
optional request body and send it off to the server. Do this with the
send()
method:
request.send(null);
GET requests never have a body, so you should pass
null
or omit the argument. POST
requests do generally have a body, and it should match the “Content-Type” header you
specified with
setRequestHeader()
.
Order Matters
The parts of an HTTP request have a specific order: the request method and URL must
come first, then the request headers, and finally the request body. XMLHttpRequest
implementations generally do not initiate any networking until the
send()
method is
called. But the XMLHttpRequest API is designed as if each method was writing to a
network stream. This means that the XMLHttpRequest method must be called in an
order that matches the structure of an HTTP request.
setRequestHeader()
, for example,
must be called after you call
open()
and before you call
send()
or it will throw an
exception.
Example 18-1 uses each of the XMLHttpRequest methods we’ve described so far. It
POSTs a string of text to a server and ignores any response the server sends.
Example 18-1. POSTing plain text to a server
function postMessage(msg) {
var request = new XMLHttpRequest(); // New request
request.open("POST", "/log.php"); // POST to a server-side script
// Send the message, in plain-text, as the request body
request.setRequestHeader("Content-Type", // Request body will be plain text
"text/plain;charset=UTF-8");
request.send(msg); // Send msg as the request body
// The request is done. We ignore any response or any error.
}
Note that the
send()
method in Example 18-1 initiates the request and then returns: it
does not block while waiting for the server’s response. HTTP responses are almost
always handled asynchronously, as demonstrated in the following section.
18.1 Using XMLHttpRequest | 497
Client-Side
JavaScript
86
18.1.2 Retrieving the Response
A complete HTTP response consists of a status code, a set of response headers, and a
response body. These are available through properties and methods of the
XMLHttpRequest object:
• The
status
and
statusText
properties return the HTTP status in numeric and tex-
tual forms. These properties hold standard HTTP values like 200 and “OK” for
successful requests and 404 and “Not Found” for URLs that don’t match any re-
source on the server.
• The response headers can be queried with
getResponseHeader()
and
getAllResponseHeaders()
. XMLHttpRequest handles cookies automatically: it fil-
ters cookie headers out of the set returned by
getAllResponseHeaders()
and returns
null
if you pass “Set-Cookie” or “Set-Cookie2” to
getResponseHeader()
.
• The response body is available in textual form from the
responseText
property or
in Document form from the
responseXML
property. (The name of that property is
historical: it actually works for XHTML documents as well as XML documents,
and XHR2 says that it should work for ordinary HTML documents as well.) See
§18.1.2.2 for more on
responseXML
.
The XMLHttpRequest object is usually (but see §18.1.2.1) used asynchronously: the
send()
method returns immediately after sending the request, and the response meth-
ods and properties listed above aren’t valid until the response is received. To be notified
when the response is ready, you must listen for readystatechange events (or the new
XHR2 progress events described in §18.1.4) on the XMLHttpRequest object. But to
understand this event type, you must first understand the
readyState
property.
readyState
is an integer that specifies the status of an HTTP request, and its possible
values are enumerated in Table 18-1. The symbols in the first column are constants
defined on the XMLHttpRequest constructor. These constants are part of the
XMLHttpRequest specification, but older browsers and IE8 do not define them, and
you’ll often see code that hardcodes the value 4 instead of
XMLHttpRequest.DONE
.
Table 18-1. XMLHttpRequest readyState values
Constant
Value Meaning
UNSENT
0
open()
has not been called yet
OPENED
1
open()
has been called
HEADERS_RECEIVED
2
Headers have been received
LOADING
3
The response body is being received
DONE
4
The response is complete
In theory, the readystatechange event is triggered every time the
readyState
property
changes. In practice, the event may not be fired when
readyState
changes to 0 or 1. It
is often fired when
send()
is called, even though
readyState
remains at
OPENED
when
498 | Chapter 18: Scripted HTTP
77
that happens. Some browsers fire the event multiple times during the
LOADING
state to
give progress feedback. All browsers do fire the readystatechange event when
ready
State
has changed to the value 4 and the server’s response is complete. Because the
event is also fired before the response is complete, however, event handlers should
always test the
readyState
value.
To listen for readystatechange events, set the
onreadystatechange
property of the
XMLHttpRequest object to your event handler function. You can also use
addEventListener()
(or
attachEvent()
in IE8 and before), but you generally need only
one handler per request and it is easier to simply set
onreadystatechange
.
Example 18-2 defines a
getText()
function that demonstrates how to listen for ready-
statechange events. The event handler first ensures that the request is complete. If so,
it checks the response status code to ensure that the request was successful. Then it
looks at the “Content-Type” header to verify that the response was of the expected
type. If all three conditions are satisfied, it passes the response body (as text) to a speci-
fied callback function.
Example 18-2. Getting an HTTP response onreadystatechange
// Issue an HTTP GET request for the contents of the specified URL.
// When the response arrives successfully, verify that it is plain text
// and if so, pass it to the specified callback function
function getText(url, callback) {
var request = new XMLHttpRequest(); // Create new request
request.open("GET", url); // Specify URL to fetch
request.onreadystatechange = function() { // Define event listener
// If the request is compete and was successful
if (request.readyState === 4 && request.status === 200) {
var type = request.getResponseHeader("Content-Type");
if (type.match(/^text/)) // Make sure response is text
callback(request.responseText); // Pass it to callback
}
};
request.send(null); // Send the request now
}
18.1.2.1 Synchronous responses
By their very nature, HTTP responses are best handled asynchronously. Nevertheless,
XMLHttpRequest also supports synchronous responses. If you pass
false
as the third
argument to
open()
, the
send()
method will block until the request completes. In this
case, there is no need to use an event handler: once
send()
returns, you can just check
the
status
and
responseText
properties of the XMLHttpRequest object. Compare this
synchronous code to the
getText()
function in Example 18-2:
// Issue a synchronous HTTP GET request for the contents of the specified URL.
// Return the response text or throw an error if the request was not successful
// or if the response was not text.
function getTextSync(url) {
var request = new XMLHttpRequest(); // Create new request
request.open("GET", url, false); // Pass false for synchronous
18.1 Using XMLHttpRequest | 499
Client-Side
JavaScript
54
request.send(null); // Send the request now
// Throw an error if the request was not 200 OK
if (request.status !== 200) throw new Error(request.statusText);
// Throw an error if the type was wrong
var type = request.getResponseHeader("Content-Type");
if (!type.match(/^text/))
throw new Error("Expected textual response; got: " + type);
return request.responseText;
}
Synchronous requests are tempting, but they should be avoided. Client-side JavaScript
is single-threaded and when the
send()
method blocks, it typically freezes the entire
browser UI. If the server you are connecting to is responding slowly, your user’s browser
will freeze up. See §22.4 for one context in which it is acceptable to make synchronous
requests, however.
18.1.2.2 Decoding the response
In the examples above, we assume that the server has sent a textual response, with a
MIME type like “text/plain”, “text/html”, or “text/css”, and we retrieve it with the
responseText
property of the XMLHttpRequest object.
There are other ways to handle the server’s response, however. If the server sends an
XML or XHTML document as its response, you can retrieve a parsed representation of
the XML document through the
responseXML
property. The value of this property is a
Document object, and you can search and traverse it using the techniques shown in
Chapter 15. (The XHR2 draft specification says that browsers should also automatically
parse responses of type “text/html” and make them available as Document objects
through
responseXML
as well, but browsers current at the time of this writing do not
do that.)
If the server wants to send structured data, such as an object or array, as its response,
it might transmit that data as a JSON-encoded (§6.9) string. When you receive it, you
would then pass the
responseText
property to
JSON.parse()
. Example 18-3 is a gener-
alization of Example 18-2: it makes a GET request for the specified URL and passes
the contents of that URL to the specified callback function when they are ready. But
instead of always passing text, it passes a Document, or an object decoded with
JSON.parse()
, or a string.
Example 18-3. Parsing the HTTP response
// Issue an HTTP GET request for the contents of the specified URL.
// When the response arrives, pass it to the callback function as a
// parsed XML Document object, a JSON-parsed object, or a string.
function get(url, callback) {
var request = new XMLHttpRequest(); // Create new request
request.open("GET", url); // Specify URL to fetch
request.onreadystatechange = function() { // Define event listener
500 | Chapter 18: Scripted HTTP
66
// If the request is compete and was successful
if (request.readyState === 4 && request.status === 200) {
// Get the type of the response
var type = request.getResponseHeader("Content-Type");
// Check type so we don't get HTML documents in the future
if (type.indexOf("xml") !== -1 && request.responseXML)
callback(request.responseXML); // Document response
else if (type === "application/json")
callback(JSON.parse(request.responseText)); // JSON response
else
callback(request.responseText); // String response
}
};
request.send(null); // Send the request now
}
Example 18-3 checks the “Content-Type” header of the response and handles
“application/json” responses specially. Another response type that you might want to
“decode” specially is “application/javascript” or “text/javascript”. You can use an
XMLHttpRequest to request a JavaScript script, and then use a global
eval()
(§4.12.2) to execute that script. Using an XMLHttpRequest object is unnecessary in
this case, however, since the HTTP scripting capabilities of the
<script>
element itself
are sufficient to download and execute a script. See Example 13-4, and keep in mind
that the
<script>
element can make cross-origin HTTP requests that are prohibited to
the XMLHttpRequest API.
Web servers often respond to HTTP requests with binary data (image files, for exam-
ple). The
responseText
property is for text only, and it cannot properly handle binary
responses, even if you use the
charCodeAt()
method of the resulting string. XHR2 de-
fines a way to handle binary responses, but at the time of this writing, browser vendors
have not implemented it. See §22.6.2 for further details.
Proper decoding of a server’s response assumes that the server sends a “Content-Type”
header with the correct MIME type for the response. If a server sends an XML document
without setting the appropriate MIME type, for example, the XMLHttpRequest object
will not parse it and set the responseXML property. Or if a server includes an incorrect
“charset” parameter in the content-type header, the XMLHttpRequest will decode the
response using the wrong encoding and the characters in
responseText
may be wrong.
XHR2 defines an
overrideMimeType()
method to address this problem and a number
of browsers have already implemented it. If you know the MIME type of a resource
better than the server does, pass the type of
overrideMimeType()
before you call
send()
—
this will make XMLHttpRequest ignore the content-type header and use the type you
specify instead. Suppose you’re downloading an XML file that you’re planning to treat
as plain text. You can use
setOverrideMimeType()
to let the XMLHttpRequest know
that it does not need to parse the file into an XML document:
// Don't process the response as an XML document
request.overrideMimeType("text/plain; charset=utf-8")
18.1 Using XMLHttpRequest | 501
Client-Side
JavaScript
41
18.1.3 Encoding the Request Body
HTTP POST requests include a request body that contains data the client is passing to
the server. In Example 18-1, the request body was simply a string of text. Often, how-
ever, we want to send more complicated data along with an HTTP request. This section
demonstrates a number of ways to do that.
18.1.3.1 Form-encoded requests
Consider HTML forms. When the user submits a form, the data in the form (the names
and values of each of the form elements) is encoded into a string and sent along with
the request. By default, HTML forms are POSTed to the server, and the encoded form
data is used as the body of the request. The encoding scheme used for form data is
relatively simple: perform normal URI encoding (replacing special characters with
hexadecimal escape codes) on the name and value of each form element, separate the
encoded name and value with an equals sign, and separate these name/value pairs with
ampersands. The encoding of a simple form might look like this:
find=pizza&zipcode=02134&radius=1km
This form data encoding format has a formal MIME type:
application/x-www-form-urlencoded
You must set the “Content-Type” request header to this value when POSTing form
data of this sort.
Note that this kind of encoding does not require an HTML form, and we won’t actually
work directly with forms in this chapter. In Ajax applications, you are likely to have a
JavaScript object that you want to send to the server. (That object may be derived from
the user input in an HTML form, but that does not matter here.) The data shown above
might be the form-encoded representation of this JavaScript object:
{
find: "pizza",
zipcode: 02134,
radius: "1km"
}
Form encoding is so widely used on the Web, and so well supported in all server-side
programming languages, that form-encoding your nonform data is often the easiest
thing to do. Example 18-4 demonstrates how to form-encode the properties of an
object.
Example 18-4. Encoding an object for an HTTP request
/**
* Encode the properties of an object as if they were name/value pairs from
* an HTML form, using application/x-www-form-urlencoded format
*/
function encodeFormData(data) {
if (!data) return ""; // Always return a string
502 | Chapter 18: Scripted HTTP
Documents you may be interested
Documents you may be interested