HTTPClient and the Request Lifecycle

Objective

In this chapter, you will learn how to access and interact with remote servers via the HTTP protocol. You will also examine the XHLHTTPRequest (XHR) lifecycle and how it is implemented within Titanium.

Contents

Your Titanium application can interact with remote servers over HTTP using the Ti.Network.HTTPClient object. Titanium's HTTPClient API mirrors that of the XMLHTTPRequest object in the web browser, so if you have done any Ajax programming in the browser (outside of libraries like Dojo or jQuery, which use XMLHTTPRequest), HTTPClient will be familiar to you.

Icon

With Mobile Web apps, in order to access cross-domain resources (resources not on the server that hosts your Mobile Web pages) you must either enable CORS header support on your web server or configure a proxy service on your web server and define a custom Ti.Network.httpURLFormatter. A good guide on CORS is available from Mozilla.org.

As shown in the code example below, you declare an HTTPClient object and pass to it a number of parameters. Of these, the most critical is the onload callback function. It is the function called when data is returned and available for use. The onerror callback function is called when there's a network error, such as a timeout.

Ti.Network.HTTPClient skeleton
var url = "https://www.appcelerator.com";
var xhr = Ti.Network.createHTTPClient({
    onload: function(e) {
		// this function is called when data is returned from the server and available for use
        // this.responseText holds the raw text return of the message (used for text/JSON)
        // this.responseXML holds any returned XML (including SOAP)
        // this.responseData holds any returned binary data
        Ti.API.debug(this.responseText);
        alert('success');
    },
    onerror: function(e) {
		// this function is called when an error occurs, including a timeout
        Ti.API.debug(e.error);
        alert('error');
    },
    timeout:5000  /* in milliseconds */
});
xhr.open("GET", url);
xhr.send();  // request is actually sent with this statement

It is possible to use HTTPClient to interact with many popular types of web services, but the easiest form to work with are REST-style web services. Defining and explaining RESTful web services is beyond the scope of this guide, but you can learn more about REST here. For our purposes, it is sufficient to understand that a 'resource' is some bit of data on the web, identified by a URI. Most commonly, your mobile application will interact with this data on the web using HTTP GET or POST requests, though the full range of HTTP verbs are supported by HTTPClient: GET, POST, PUT, and DELETE.  PATCH is supported on the Android platform since Release 4.1.0.

The handling of network communication is handled asynchronously. Mobile data networks are less reliable and consistently available than office or home Internet connections. You would not want your application to hang while waiting on an HTTP request to return.

GET requests

Making a GET (or any other type of) request to a resource on the web consists of three steps:

  • Creating an HTTP Client (starts on line 2 in the code above)
  • Opening an HTTP connection to a specified resource (line 18)
  • Sending an HTTP request (line 19)

Most of the time, simply sending the request is not useful to your application. You are likely interested in the data the server will respond with, which is available in the response body. In order to access this data, you can specify callback functions to be executed at specific points in the lifecycle of the request. As shown in the code above, onload is called after a response from the resource has been successfully received, and oneerror is called if there is an error.

Within those callback functions:

  • this.responseText holds the returned payload as raw text
  • this.responseXML holds the payload as an XML document instance
  • this.responseData holds the payload as a BLOB (binary data)

POST requests

Often you will need to send data to the server in the body of your request, as you would in a standard HTML form. This is typically accomplished via a POST (or PUT) request. Titanium provides an easy way of sending along a POST body with a request, automatically serializing JavaScript object graphs into form-encoded POST parameters:

var xhr = Ti.Network.createHTTPClient();

xhr.onload = function(e) {
	//handle response, which at minimum will be an HTTP status code
};

xhr.open('POST','http://www.myblog.com/post.php');
xhr.send({
	title: 'My awesome blog',
	body: 'Today I met Susy at the laundromat.  Best day EVAR\!'
});

You can also send arbitrary string data as the body of your post by passing a string to send:

xhr.send('<some><xml><data></data></xml></some>');

HTTP headers

It is often necessary to manually add HTTP headers to your requests. This can be accomplished easily by using the setRequestHeader function on HTTPClient. NOTE: HTTP Headers must be set AFTER client.open(), but before client.send(), as below:

var client = Ti.Network.createHTTPClient();
client.open('POST','http://someserver.com/files/new');
client.setRequestHeader('Content-Type','text/csv');
client.send('foo,bar,foo,bar');

XHR lifecycle

HTTPClient implements the five XHR ready states defined by the W3 specification. Should you need to, your app can react to these state changes with the onreadystatechange callback. The five states are:

  • UNSENT (numeric value 0) – The object has been constructed. Titanium doesn't report on this state with the onreadystatechange handler.
  • OPENED (numeric value 1) – The open() method has been successfully invoked. During this state request headers can be set using setRequestHeader() and the request can be made using the send() method.
  • HEADERS_RECEIVED (numeric value 2) – All redirects (if any) have been followed and all HTTP headers of the final response have been received. Several response members of the object are now available.
  • LOADING (numeric value 3) – The response entity body is being received.
  • DONE (numeric value 4) – The data transfer has been completed or something went wrong during the transfer (e.g. infinite redirects).

In code, it would look like this:

Monitoring ready state changes
var xhr = Ti.Network.createHTTPClient({
    onload: function(e) {
		// function called in readyState DONE (4)
		Ti.API.info('onload called, readyState = ' + this.readyState);
    },
    onerror: function(e) {
		// function called in readyState DONE (4)
		Ti.API.info('onerror called, readyState = ' + this.readyState);
    },
    ondatastream: function(e) {
		// function called as data is downloaded
		Ti.API.info('ondatastream called, readyState = ' + this.readyState);
    },
    onsendstream: function(e) {
		// function called as data is uploaded
		Ti.API.info('onsendstream called, readyState = ' + this.readyState);
    },
	onreadystatechange: function(e) {
		switch(this.readyState) {
			case 0:
				// after HTTPClient declared, prior to open()
				// though Ti won't actually report on this readyState
				Ti.API.info('case 0, readyState = ' + this.readyState);
			break;
			case 1:
				// open() has been called, now is the time to set headers
				Ti.API.info('case 1, readyState = ' + this.readyState);
			break;
			case 2:
				// headers received, xhr.status should be available now
				Ti.API.info('case 2, readyState = ' + this.readyState);
			break;
			case 3:
				// data is being received, onsendstream/ondatastream being called now
				Ti.API.info('case 3, readyState = ' + this.readyState);
			break;
			case 4:
				// done, onload or onerror should be called now
				Ti.API.info('case 4, readyState = ' + this.readyState);
			break;
		}
	},
    timeout:5000  /* in milliseconds */
});
xhr.open("GET", 'http://training.appcelerator.com.s3.amazonaws.com/atp_doc.pdf');
xhr.send();  // request is actually sent with this statement

Hands-on Practice

Goal

In this activity, you will write an app that downloads and displays an image from a remote web URL using the HTTPClient object.

Resources

To perform the steps in this activity, you will need to reference the HTTPClient API docs at http://developer.appcelerator.com/apidoc/mobile/latest/Titanium.Network.HTTPClient-object.html. You will also need a working internet connection on your simulator/emulator or test device.

Steps

  1. Create a new Titanium Mobile project.
  2. In app.js, declare an HTTPClient object with a name of your choosing. It will GET the image at http://developer.appcelerator.com/assets/img/DEV_appteam_photo.png
  3. Write an onload callback for your HTTPClient that will perform these operations:
    • Log the HTTP status code to the console. See the API docs for the correct property to use to access the status code.
    • Define an ImageView object whose image property is set equal to the binary data returned from the network.
    • Add that ImageView object to the win1 window so that it will be displayed.
  4. Write an onerror callback for your HTTPClient that will perform these operations:
    • Log the HTTP status code to the console.
    • Display the contents of the error message in an alert() dialog.
  5. Make sure to open and then send the request.
  6. Build and test your app. The photo should be displayed on the first tab after it is downloaded.

References and Further Reading

Summary

In this chapter, you learned learned that Titanium implements the XHLHTTPRequest (XHR) via the HTTPClient module. You used that module to access and interact with remote servers over HTTP. You also examined the XHR lifecycle and how it is implemented within Titanium.

Related Links