HTTP

The Hypertext Transfer Protocol ( HTTP ) is an Application Layer protocol used for information transport over the Internet. HTTP servers are able to host many types of data, including HTML and XML pages, multimedia files or images. A client can retrieve data by opening a connection to the server (usually on TCP port 80) and sending a request. A Uniform Resource Identifier (URI) specifies a file on the server. The URI includes the server name, the path on the server and the name of the file.

Series 60 1.x does not have a public API that offers direct support for HTTP. However, the TCP/IP section in the previous chapter shows how you can implement a simple HTTP layer yourself using the Sockets API.

Series 60 2.x introduces the HTTP Client API. This provides a client interface enabling applications to communicate with HTTP servers on the Internet. The API supports HTTP 1.0 by default, and it also provides conditional HTTP 1.1 compliance, as defined in RFC 2616. The API allows the Client to specify features such as the encoding and transport protocol required. The Client stack supports persistent connections, pipelining and chunked transfer encoding. Filters provide support for HTTP Redirection and HTTP Basic and Digest Authentication.

To use the Client API correctly, you need to understand the main ideas behind conducting a transaction over HTTP: sessions, transactions, headers, data suppliers, and filters. This section briefly explains the function of each component before walking through an example use of the API.

Sessions

A session describes one or more transactions sharing the same connection. A session has a series of settings and filters defined that are applicable to all transactions. It is possible to have more than one session active concurrently, although this is not usual.

In the HTTP Client API, the RHTTPSession class represents the session. The class has functions to open and close the session and to create a transaction. To be notified of session events you need to call RHTTPSession::SetSessionEventCallback() ; otherwise all events are handled by the session and will not be passed to your application.

The default protocol for the RHTTPSession class is HTTP over TCP, but the API provides support for choosing other combinations. If you wish to use a different protocol, you can obtain a list of alternatives by calling RHTTPSession::ListAvailableProtocols() . The required protocol can then be passed as a parameter to an overloaded version of RHTTPSession::OpenL() . Note, however, that in the vast majority of cases the default protocol is what you want, so you can usually just call the no-parameter version of RHTTPSession::OpenL() .

Note that if you are using the default protocols, the Connect() and Disconnect() methods of RHTTPSession should not be used ”these are for use with the WSP protocol.

Transactions

The simple way to think of a transaction is as a single exchange of messages between an HTTP client and HTTP origin server ”a request from the client and a response from the server. (In practice, filters set for the transaction may complicate the exchange. Filters are discussed later in this section.)

HTTP defines a number of methods such as GET, to ask for a specific document, and HEAD, to retrieve information about a document. When making a request, you need to specify which method you want to use and the URI of the resource. Both the request and response consist of a header and, optionally , a body. The response received from the server contains an HTTP status code and message, which indicate the success of the method or the state of the resource following the method.

The RHTTPTransaction class represents an HTTP transaction.

Headers

Each request and response message has a header section containing one or more fields. The purpose of these headers is to allow the client and server to communicate information to each other. This could relate to the connection or maybe to properties of the client or server. For example, the client may specify the acceptable content type that can be received.

The class RHTTPHeaders should be used to create and modify the headers in the HTTP client API, with THTTPHdrVal describing each field.

Data Suppliers

A data supplier object represents the body of a message. The HTTP client API defines the mixin class MHTTPDataSupplier for this purpose. Clients should implement this class to receive response message data from the framework and to supply the request message body to the framework.

The classes RHTTPRequest and RHTTPResponse are defined to represent the whole message. The request or response can be retrieved from the transaction using the functions RHTTPTransaction::Request() and RHTTPTransaction::Response() , respectively.

Filters

Filters are applied at session level and can modify transactions as they travel to or are retrieved from the server. Typically they can cause headers to be added or removed, terminate or a cancel a transaction, or transform the body.

You can write your own filter by deriving from MHTTPFilter . A filter is registered using RHTTPFilterCollection::AddFilterL() .

Example Application

The HTTPExample application is provided to illustrate the concepts outlined above. This example provides the user with options to perform simple GET and POST requests , as shown in Figure 10-1.

Figure 10-1. The HTTPExample application.


Implementation

Use of the HTTP APIs in the HTTPExample application is encapsulated in the CHTTPExampleEngine class. As well as deriving from CBase , this class also derives from MHTTPAuthenticationCallback to enable it to receive progress events during an outstanding transaction. The application provides a further observer mixin class, MHTTPExampleEngineObserver , for the purpose of passing these events up to the application UI.

The important functions and variables of the MHTTPExampleEngineObserver and CHTTPExampleEngine classes are shown in the abridged class declarations below.

[View full width]
 
[View full width]
class MHTTPExampleEngineObserver { public: virtual void ResponseStatusL( TInt aStatusCode, const TDesC& aStatusText) = 0; virtual void ResponseReceivedL( const TDesC& aResponseBuffer) = 0; }; class CHTTPExampleEngine : public CBase, public MHTTPTransactionCallback { public: static CHTTPExampleEngine* NewL(MHTTPExampleEngineObserver& aObserver); ~CHTTPExampleEngine(); void GetRequestL(const TDesC& aUri); void PostRequestL(const TDesC& aName); // Cancel an outstanding transaction void Cancel(); private: // from MHTTPTransactionCallback virtual void MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent); virtual TInt MHFRunError(TInt aError, RHTTPTransaction aTransaction, const THTTPEvent& aEvent); private: CHTTPExampleEngine(MHTTPExampleEngineObserver& aObserver); void ConstructL(); void ParseUriL(const TDesC& aUri); void AddHeaderL(RHTTPHeaders aHeaders, TInt aHeaderField, const TDesC8& aHeaderValue); private: RHTTPSession iSession; RHTTPTransaction iTransaction; HBufC* iResponseBuffer; HBufC8* iUri; TUriParser8 iUriParser; CHTTPFormEncoder* iFormEncoder; MHTTPExampleEngineObserver& iObserver; };

Opening a Session

The first step an application must perform in order to use the HTTP APIs is to open an HTTP session. In the HTTPExample example, this is performed in the engine's ConstructL() method. No parameters are specified, because the default protocol, HTTP over TCP/IP, is required.

 void CHTTPExampleEngine::ConstructL()    {    // Open the RHTTPSession    iSession.OpenL();    ...    } 

Making a GET Request

Once the RHTTPSession object is open, you are ready to make a HTTP request. This application demonstrates both GET and POST requests ”GET requests are discussed first.

A GET request is initiated by calling CHTTPExampleEngine::GetRequestL() . This takes a single descriptor parameter, which should contain the URI to be requested . In order to be passed to the HTTP request transaction, this first needs to be converted into a TUriC8 object ” TUriC8 is the base class of a family of classes provided by Series 60 for handling URIs.

So the first step taken by CHTTPExampleEngine::GetRequestL() is to call CHTTPExampleEngine::ParseUriL() , which takes a descriptor and parses it using the TUriParser8 data member iUriParser . The function will leave if the URI is invalid. This iUriParser member is later passed into the request function (as TUriParser8 is derived from TUriC8 ). Here is the ParseUriL() function in full:

 void CHTTPExampleEngine::ParseUriL(const TDesC& aUri)    {    // Convert the URI to an 8-bit descriptor    // then set iUriParser to point at it    delete iUri;    iUri = NULL;    iUri = HBufC8::NewL(aUri.Length());    iUri->Des().Copy(aUri);    User::LeaveIfError(iUriParser.Parse(*iUri));    } 

Notice that the descriptor has to be converted from 16-bit to 8-bit before it can be parsed. This is achieved using the TDes::Copy() method.

Once the URI has been successfully parsed, you need to create a transaction object. This will encapsulate the request to and response from the server. It is represented by the RHTTPTransaction class and opened using the RHTTPSession::OpenTransactionL() method. The parameters to this method are:

  • A URI ”in this case, iUriParser .

  • A reference to an MHTTPTransactionCallback object. As CHTTPExampleEngine implements this mixin, it passes a reference to itself here.

  • A string pool entry identifying the type of request to make. Strings are defined for each of the standard HTTP request types.

The String Pool section of the SDK documentation for further information on using string pools.


The following section of code shows the function call in full, and the way a request string is retrieved from the RHTTPSession object.

 iTransaction = iSession.OpenTransactionL(iUriParser, *this,    iSession.StringPool().StringF(HTTP::EGET,     RHTTPSession::GetTable())); 

The RStringF type is used for a string pool string ” F is short for "folded," meaning that comparisons performed on this string are case-insensitive.

Adding Request Headers

After constructing the transaction object, one more stage is required before a request can be submitted ”to add a set of request headers. Transaction headers are represented by the RHTTPHeaders class, and a transaction's request headers can be obtained by calling iTransaction.Request().GetHeaderCollection() . The HTTPExample example explicitly adds Accept and User-Agent headers, as shown below:

 // Set transaction headers RHTTPHeaders headers = iTransaction.Request().GetHeaderCollection(); AddHeaderL(headers, HTTP::EUserAgent, KUserAgent); AddHeaderL(headers, HTTP::EAccept, KAccept); 

The Accept header defines the content type (or types) which will be accepted in the response message body. The User-Agent header provides a string identifying the client application. The constants used here are defined as follows at the top of the file:

 // CONSTANTS // HTTP header values // Name of this client app _LIT8(KUserAgent, "HTTPExample (1.0)"); // Accept any (but only) text content _LIT8(KAccept, "text/*"); 

The Accept value of text/* specifies that any text-based content type is acceptable in the response body.

AddHeaderL() is a utility function in CHTTPExampleEngine which encapsulates the code required to add a header. It uses the THTTPHdrVal class to represent each individual header and uses the Series 60 HTTP string pool to ensure the correct strings are specified:

[View full width]
 
[View full width]
void CHTTPExampleEngine::AddHeaderL(RHTTPHeaders aHeaders, TInt aHeaderField, const TDesC8& aHeaderValue) { RStringPool stringPool = iSession.StringPool(); RStringF valStr = stringPool.OpenFStringL(aHeaderValue); THTTPHdrVal headerVal(valStr); aHeaders.SetFieldL(stringPool.StringF(aHeaderField, RHTTPSession::GetTable()), headerVal); valStr.Close(); }

Headers that are defined by RFC 2616 as being mandatory for HTTP 1.1 are automatically added to a request. So your application code does not need to add the following to a request:

  • Content-Length ” This is derived from the client request's MHTTPDataSupplier .

  • Connection ” This is handled automatically to manage the HTTP/1.1 persistent connection. Only if a client wants to indicate that the persistent connection is to close should the client add a Connection header.

  • Host ” This is taken from the client's request URL. Only if a relative URL is supplied should the client add a Host header.

  • Transfer-Encoding ” This is added automatically if the client's request MHTTPDataSupplier specifies an unknown overall data size .

  • Authorization ” This is added automatically by the Authentication filter.

Submitting the Request and Receiving the Response

Once the request headers have been set, the request can be submitted. This is achieved by calling the RHTTPTransaction::SubmitL() method:

 // Submit the request iTransaction.SubmitL(); 

RHTTPTransaction::SubmitL() is an asynchronous function, but you do not need to explicitly create an active object in order to handle it. (This is performed by the HTTP stack for you.) During the course of the request transaction, progress notification events are sent to the MHFRunL() method of the MHTTPTransactionCallback object that was passed by reference to RHTTPSession::OpenTransactionL() . In this case, this is the CHTTPExampleEngine class itself. This callback function is defined in MHTTPTransactionCallback as follows:

 virtual void MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent) = 0; 

The aTransaction parameter identifies the transaction that has generated the progress event, and aEvent identifies the type of event. CHTTPExampleEngine overrides this to handle the following event types:

  • EGotResponseHeaders ” Generated when the response headers are received. When this event is received, the engine notifies its observer with a call to MHTTPExampleEngineObserver::ResponseStatusL() .

  • EGotResponseBodyData ” Generated when part (or all) of the response body is received. The engine handles this by appending the currently received part to a buffer used for storing the complete response.

  • EResponseComplete ” Generated when the entire response has been received. On receipt of this event the engine passes the response to its observer by calling MHTTPExampleEngineObserver::ResponseReceivedL() .

If a leave occurs in your MHFRunL() method, you will get the chance to handle it in the MHFRunError() method, also inherited from MHTTPTransactionCallback . The leave code is passed into this function as a parameter, along with the transaction and event that were being handled when the leave occurred. The function should return KErrNone if it handles the error. If it returns any other error code, the application will panic (with HTTP-CORE 6 ).


The following code shows how the engine obtains the status code and status text returned with the EGotResponseHeaders event. An RHTTPResponse object is used to achieve this. Notice also that the response text is converted from 8-bit text back into a 16-bit descriptor.

 // HTTP response headers have been received. // Pass status information to observer. RHTTPResponse resp = aTransaction.Response(); // Get status code TInt statusCode = resp.StatusCode(); // Get status text RStringF statusStr = resp.StatusText(); HBufC* statusBuf = HBufC::NewLC(statusStr.DesC().Length()); statusBuf->Des().Copy(statusStr.DesC()); // Inform observer iObserver.ResponseStatusL(statusCode, *statusBuf); CleanupStack::PopAndDestroy(statusBuf); 

You can check whether a request succeeded by verifying that the status code returned is in the range 200 to 299 , indicating a successful request. See RFC 2616 for a full list of HTTP response status codes.


Provided the request was successful, the next event the engine handles is EGotResponseBodyData , indicating that at least part of the body data has been received. This event will occur one or more times until all body data has been received, at which point the EResponseComplete event is sent. Because data may arrive in parts , the engine handles EGotResponseBodyData by appending the current chunk to the end of a buffer, and handles EResponseComplete by passing a reference to this buffer to the observer. Each body part is obtained in the EGotResponseBodyData handler code from a data supplier object by calling MHTTPDataSupplier::GetNextDataPart() . As with the header data, the engine converts the data received from 8-bit text to a 16-bit descriptor. (Remember that the response body is guaranteed to be text rather than binary content, as the Accept header was set to text/* .)

The full code of the handler is shown below:

 // Get text of response body MHTTPDataSupplier* dataSupplier = aTransaction.Response().Body(); TPtrC8 ptr; dataSupplier->GetNextDataPart(ptr); // Convert to 16-bit descriptor HBufC* buf = HBufC::NewLC(ptr.Length()); buf->Des().Copy(ptr); // Append to iResponseBuffer if (!iResponseBuffer)    {    iResponseBuffer = buf->AllocL();    } else    {    iResponseBuffer = iResponseBuffer ->ReAllocL(iResponseBuffer->Length() + buf->Length());    iResponseBuffer->Des().Append(*buf);    } // Release buf CleanupStack::PopAndDestroy(buf); // Release the body data dataSupplier->ReleaseData(); 

The handler for the EResponseComplete event simply passes the complete buffer to the observer:

 // Pass the response buffer by reference to the observer iObserver.ResponseReceivedL(*iResponseBuffer); 

In the HTTPExample application, the observer is the CHTTPExampleAppUi class, which implements ResponseReceivedL() by inserting the text into its rich text view.

Making a POST Request

Another common kind of HTTP request is the POST request. This differs from the GET request mainly in that the request itself contains a body as well as headers. It is most commonly used for transmitting data entered in an HTML form to some kind of program on the server, such as a CGI script or Java servlet, which then returns a dynamically generated response. The HTTPExample application makes a POST request to a CGI script, passing the user's name as a parameter. The script returns the text "Hello <name> !".

The only difference between the GET and POST requests in this application is in the way the request is issued ”the application handles responses from both kinds of request identically, using the code shown above. Since a POST request has a body, a data supplier is required to provide the body data. You can provide your own data supplier by implementing a class which derives from HTTPDataSupplier , or you can use an existing data supplier class. As HTTPExample just sends HTTP form data, it uses the HTTP API class CHTTPFormEncoder for its data supplier. The function CHTTPExampleEngine::PostRequestL() starts by initializing this form encoder object with the user's name, passed in from the UI:

 _LIT8(KPostParamName, "NAME"); // Name of the parameter sent in a POST request ... void CHTTPExampleEngine::PostRequestL(const TDesC& aName)    {    // Build form encoder    // Start by removing any previous content    delete iFormEncoder;    iFormEncoder = NULL;    iFormEncoder = CHTTPFormEncoder::NewL();    TBuf8<EMaxNameLength> buf8;    buf8.Copy(aName);    iFormEncoder->AddFieldL(KPostParamName, buf8);    ...    } 

To complete the request, the application needs to set the request headers (just as it did for a GET request) and set the iFormEncoder object to be the request's data supplier. This time it sets one extra header, Content-Type, to indicate the content type of the request body:

 _LIT8(KPostContentType, "text/plain");   // Content type sent in a POST request ...    // Set transaction headers    RHTTPHeaders headers = iTransaction.Request().GetHeaderCollection();    AddHeaderL(headers, HTTP::EUserAgent, KUserAgent);    AddHeaderL(headers, HTTP::EAccept, KAccept);    AddHeaderL(headers, HTTP::EContentType, KPostContentType); 

The iFormEncoder object is associated with the request by passing it to the RHTTPRequest::SetBody() function. Remember that this will cause the transaction to call back into the data supplier asynchronously when the body data is required, so your data supplier must not be a local variable that will go out of scope. This is why CHTTPExampleEngine has iFormEncoder as member data. The concluding part of the request is shown here:

 // Set the form encoder as the data supplier iTransaction.Request().SetBody(*iFormEncoder); // Submit the request iTransaction.SubmitL(); 

Canceling and Aborting Transactions

An outstanding transaction can be canceled at any time using RHTTPTransaction::Cancel() . This means that you will not receive any more events for that transaction, unless you choose to resubmit it. This is called in CHTTPExampleEngine::Cancel() , which allows the UI to cancel the request if necessary:

 void CHTTPExampleEngine::Cancel()    {    iTransaction.Cancel();    } 

You can also abort a transaction using RHTTPTransaction::Close() , in which case there is no need to call Cancel() first.

Calling Close() causes all the resources associated with a transaction to be cleaned up, so you must not try to use a transaction once it has been closed.


Terminating the Session

When you have finished all your transactions, the session should be closed. Note that closing the session will cause any outstanding transactions to be cancelled. This is performed in the destructor of the CHTTPExampleEngine class:

 // Close session iSession.Close(); // Will also close any open transactions 



Developing Series 60 Applications. A Guide for Symbian OS C++ Developers
Developing Series 60 Applications: A Guide for Symbian OS C++ Developers: A Guide for Symbian OS C++ Developers
ISBN: 0321227220
EAN: 2147483647
Year: 2003
Pages: 139

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