4umi.com/web/javascript/xmlhttp

Requesting XMLHttp

Basic Javascript

Several modern browsers allow Javascript to make requests to the server and receive a reply without reloading the page. Serverside scripts can have access to far larger databases of information that are impossibe to download conveniently, and so can provide responses of great relevance in great detail at incredible speeds, constantly increasingly the value of a page to its user. Earlier generations had to rely on Java applets for making such connections, and to access certain remote content and cater for a wider range of browsers, they generally still have their place, but, thanks to the combined powers of XML and HTTP  The Chalisbury Grail, 
 from www.forevercharmed.co.uk 
 Click for a larger one 
 from enchantica.co.uk the capabilities of Javascript are maturing. A very different approach is required for reading local files.

Standards and browsers

The original implementation came from Microsoft as an ActiveX object for IE. Mozilla followed suit and created XMLHttpRequest with an (almost) identical API. The proposed W3C recommendation for loading and sending at dom level 3, currently under revision, is not implemented in any browser. The Gecko family, Konqueror and Safari since 1.2, and Opera since 8.00 share what is called the XMLHttpRequest() object, Internet Explorer uses an ActiveXObject(), the details are in the script below. Opera support for setting HTTP request headers starts with version 8.01. Also IceBrowser 6 reputedly has an facility for making xmlhttp requests. Other emulations exist but are buggy and do not implement all the useful methods and properties of the object.

The name is somewhat confusing, as the function is in no way limited to XML documents, but allows all kind of data to be transferred over an ordinary HTTP Hypertext Transfer Protocol connection, although Javascript may have a problem handling binary streams. Whatever the server responds with can be stored and used as a normal variable in the existing context, XML data as XML, and strings with all the capable methods of strings.

Common usage

The many unpronouncable letters and inconsistent capitalization may make it seem an alien thing, but it isn't particularly new. You 've seen it, you 've done it already. If you have ever come across any big name site, chances are one of the banners in the corner invoked a Javascript that used XMLHttp to look you up in the advertiser's database.

Friendlier uses exist. If for example a user enters a zip or postal code, the Javascript can ask the server for a list of corresponding street names, which is often a quite welcome convenience. Downloading all streetnames for all postcodes would obviously not be an option. This functionality is increasingly common on big, commercial sites.

A frequently cited example is Google Suggest, which employs the request object to create an unusually rich interface: when a user starts typing in the search box, a Javascript sends the letters off to their server; the server returns a list of suggestions, which is dynamically displayed beneath the search box.

Another challenge that has many beginning programmers going geek is the case of multiple <select> lists, where an <option> selected in the first triggers a script that fills the second with newly created tailored options from a dynamically loaded list.

Remote content

 The ‘Trusted Sites’ Security zone
The IE Internet Options Security Tab.

If the current site is in the list of ‘Trusted Sites’, Internet Explorer also allows access to remote content, ie. url's that are not from the same domain as the website offering the script. Mozilla can do so, too, but requires the UniversalXPConnect and UniversalBrowserAccess permissions to be granted to the script, as the Mozilla documentation explains. In all other cases, access is restricted to the current site and a ‘Permission Denied’ error is issued for cross-domain requests.

Signing scripts in Mozilla is no sinecure, and making Internet Explorer trust a site takes some steps from the user:

  1. Double-click the Internet Zone icon down the right on the IE statusbar, or choose from the menu ToolsInternet Options... and select the Security Tab from the dialog window that appears;
  2. Hit the big round green ‘Trusted Sites’ sign; information about this zone appears;
  3. Click the button marked ‘Sites...’ to pop up a dialog with a list of sites;
  4. Type or paste the domain name: 4umi.com and click ‘OK’ to close the dialog.
  5. Click ‘OK’ once more; changes are then saved, the Security Tab closes, and any scripts on pages from the newly added site are now treated as trusted.

A test request

Every known method and property (listed in the appendix below) is touched by this script. Select a file from the list of suggestions or enter an url of your own choosing, and the script will request it from the server and output its contents, with lines numbered, in the area below, together with a calculation of size and speed, and a listing of all returned headers and their values. Check the appropriate Stream and XML boxes if you expect the response to be something other than text. Click ‘Abort’ to cancel a request that takes forever, ‘Run’ to open the specified file in a new window, or ‘Reset’ to clear the page.

Filename
Or type:
    Alert:
Response
Headers
Contents
  1.  

The script

In the code below, first of all the XMLHttpRequest() method of the window object, native in Gecko browsers, is emulated for browsers that support the ActiveXObject() method, ie. IE. After that, if the method still does not exist, an apologizing error message is displayed as the following functions will be toothless.

if( !window.XMLHttpRequest && window.ActiveXObject ) {
 window.XMLHttpRequest = function() {
  var a = [ 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP', 'Msxml2.XMLHTTP.3.0', 'Msxml2.XMLHTTP.4.0', 'Msxml2.XMLHTTP.5.0' ],
   i = a.length; while(i--) {
   try {
    return new ActiveXObject( a[i] );
   } catch (e) { }
  }
  return null;
 };
}

var xmlhttp;
if( window.XMLHttpRequest ) {
 xmlhttp = new XMLHttpRequest(); 
}
if( !xmlhttp ) {
 alert( 'Sorry, creating the XMLHttpRequest object failed.' ); 
}

The function getit(f) is called at the onsubmit event of the input form. It sets the onreadystatechange event of the request (line 16) object to fire an inner function of the first, so local variables can be shared. It checks one of the four different response properties (responseBody, responseStream, responseText, responseXML) for the returned content, wrapped in a try/catch block to cover mismatches in the encoding.

The speed is calculated in kbps, or kilobits per second, on line 28. Since the XMLHttp object offers no direct filesize property and a 'Content-Length' header is not always available, the number of characters in the returned string is presumed to equal the number of bytes, which may not always actually be the case, depending on the encoding of the document. This number is muliplied by 8 to go from bytes to bits, divided by 1024 to get to kilobits. The result is divided by the number of milliseconds, muliplied by 1000 to make them count as whole seconds.

function getit( f ) {
 var now = new Date(), dif;
 document.body.style.cursor = 'wait';
 f = f.elements;
 var txt = '', len = 0, url = f.url.value, i = 19;
 while( f[--i].value ) { f[i].value = ''; }
 f.butget.disabled = true;
 f.butabort.disabled = false;
 f.butclear.disabled = true;
 resetit( f );
 if( /\w+:\/\/\w+/.test( url ) ) {
  try {
   xmlhttp.open( 'GET', url, true );
//   xmlhttp.setrequestheader( 'Content-Type', 'application/x-www-form-urlencoded' );
//   xmlhttp.setrequestheader( 'Cookie', 'foo=bar' );

   xmlhttp.onreadystatechange = function() {
    f.state[ xmlhttp.readyState ].value = new Date() - now;
    if( xmlhttp.readyState===3 ) {
    }
    if( xmlhttp.readyState===4 ) {
     dif = new Date() - now;
     listit( xmlhttp.getAllResponseHeaders() );
     try { txt = xmlhttp.responseText; } catch(e) {
      window.alert( 'Sorry, the text returns an error:\n' + e.message + '.' );
     }
     gid( 'ol' ).innerHTML = toli( txt || ' -- Empty. --' );
     len = xmlhttp.getResponseHeader( 'Content-Length' ) || txt.length;
     f.time.value = dif / 1000 + ' sec.';
     f.bytes.value = bykb( len, true );
     f.speed.value = ( dif ? ron( len / dif / 0.128, 3 ) : 'No' ) + ' kbps';
     f.status.value = xmlhttp.status + ' (' + xmlhttp.statusText + ')';
     styleit( f, Math.floor( xmlhttp.status / 100 ) );

     if( f.stream.checked ) { window.alert( 'Stream:\n' + xmlhttp.responseStream.length ); }
     if( f.xml.checked ) { window.alert( 'XML:\n' + xmlhttp.responseXML.xml ); }
     stopit( f );
    }
   };

   xmlhttp.send( null );
  } catch (e) {
   window.alert( 'Sorry, ' + e.message.replace( /Unknown name\./,
    'an error occured.' ).replace( /Permission denied/,
    'cross-domain requests are only possible from "Trusted Sites".' ) );
   stopit( f );
  }
 }else{
  window.alert( 'Please enter an URL to request.' );
  f.elements.url.focus();
  stopit( f );
 }
 return false; // don't submit the form
}

The other buttons...

function runit( f ) {
 var url = f.elements.url.value;
 if( /\w+:\/\/\w+/.test( url ) ) {
  var w = window.open( url, '_pop' );
  if( w ) { w.focus(); } else { window.location.href = url; }
 }else{
  window.alert( 'Please enter an URL to run.' ); f.elements.url.focus();
 }
}

Some supporting functions...

function resetit( f ) {
 styleit( f );
 gid( 'headers' ).innerHTML = '<legend>Headers<\/legend>';
 gid( 'ol' ).innerHTML = '';
}

function stopit( f ) {
 try { xmlhttp.abort(); } catch(e) { alert( e.message ); }
 f.butget.disabled = false;
 f.butabort.disabled = true;
 f.butclear.disabled = false;
 document.body.style.cursor = 'auto';
}

function styleit( f, s ) {
 var colours = [ 'black', 'maroon', 'green', 'blue', 'red', 'purple', 'gold' ];
 f = f.status; s = colours[ s || 0 ];
 f.style.color = s;
 f.parentNode.style.color = s;
}

function listit( s ) {
 var h = gid( 'headers' ), i, j = h.firstChild, last = null, max = 0,
  node, prot = gid( 'prot' ).cloneNode( true ), pos, str;
 while( j.nextSibling ) {
  h.removeChild( j.nextSibling );
 }
 prot.style.display = 'block';
 prot.style.whiteSpace = 'nowrap';
 s = trim( s ).split( /\n/ );
 s.sort();
 i = s.length; while(i) {
  str = s[--i];
  pos = str.indexOf( ':' ) + 1;
  node = prot.cloneNode( true );
  node.firstChild.firstChild.nodeValue = str.substring( 0, pos );
  node.lastChild.value = str.substring( pos );
  h.insertBefore( node, last ); last = node;
  max = Math.max( max, node.firstChild.offsetWidth );
 }
 max = max + 2 + 'px';
 i = s.length; while(i) {
  h.childNodes[i--].firstChild.style.width = max;
 }
}

if( ( i = window.location.search.match( /url=([^&]*)/ ) ) && ( i = i[1] ) ) {
 document.forms.f.elements.url.value = decode( i );
}

The last four lines run while the document is loading. Should an nasty error occur before the end of the script called onsubmit, then the form will be submitted to the server and the page will unload. Thus the name of the file that caused the error will appear in the url of the next page, and it can easily be read and copied back to the input box for another attempt.

Still no holy Grail.

Appendix

Listed are the various methods and properties of the XMLHttpRequest object.

Methods

NameDescription
abort()Cancels the current request, turning the state to 0: Uninitialized.
getAllResponseHeaders()Returns all HTTP headers in one string
getResponseHeader( name )Returns the value of the specified header, case-insensitive. See our list of response headers.
open( method, url, async, uname, pwd )Specifies attributes of a request. The method and url are the only required parameters.
The method is one of 'GET' for requests, 'POST' when data are sent, or 'PUT'.
The url may be either absolute, or relative from the current location.
The async parameter is a boolean specifying whether the request should be handled asynchronously or not. If true, the script carries on processing after the send() statement, the default false means that it will wait for a response, quite possibly freezing the browser in case of delay or network failure. Always use true.
send( data )Sends the request, optionally with some data, and receives the response.
setRequestHeader( name, value )Adds a header to the list of HTTP request headers. The headers should correspond to the HTTP1.1 notations. If a header of the same name already exists, it is overwritten.

Properties

NameDescription
onreadystatechangeHandles the event that fires at every change of state.
readyStateReturns the current state as one of five numbers:
  1. 0. Uninitialized: the object exists, but open() has not been called.
  2. 1. Loading: between the open() and send() calls.
  3. 2. Loaded: send() has been called and the status and headers are available.
  4. 3. Interactive: responseBody and responseText contain partial results.
  5. 4. Complete: all data has been received, and is available.
responseBodyRetrieves the response entity body as an array of unsigned bytes.
responseStreamReturns the raw, uncoded bytes as received from the server in a binary data stream.
responseTextReturns the response as a string.
responseXMLReturns the response as an xml document object, fully parsable with standard dom methods.
statusReturns the status as an integer number. The most common ones are 200 for "OK" or 404 for "Not Found". See our list of all status codes.
statusTextReturns the status as a string, e.g. "OK" if 200 or "Not Found" for a 404.

Reference