This page describes each of the stages needed to use HTTP for a
single, simple transaction. The code fragments are taken from
HTTPEXAMPLECLIENT
.
A new HTTP Client session is started by declaring a
RHTTPSession
object, which must remain in scope for the
duration of the session, and invoking the
RHTTPSession::OpenL()
method. Usually, the
RHTTPSession
handle is a data member of a client class:
From the example header file
httpexampleclient.h
:
class CHttpClient : public CBase, ...
{
...
private:
RHTTPSession iSession;
...
};
From the example implementation file
httpexampleclient.cpp
:
CHttpClient::CHttpClient()
{
...
iSession.OpenL();
...
}
To create a new transaction within the session, the client must
specify a URI, an HTTP method and a callback object that is used to receive
events that arise during the transaction. The callback object must implement
the MHTTPTransactionCallback
interface.
The RHTTPTransaction
handle returned by the
session uniquely identifies the new transaction. It may be stored in a class
data member, but this isn't mandatory.
void CHttpClient::InvokeHttpMethodL(const TDesC8& aUri, RStringF aMethod)
{
...
TUriParser8 uri;
uri.Parse(aUri);
RHTTPTransaction trans = iSession.OpenTransactionL(uri, *iTransObs, aMethod);
...
};
The second parameter to
RHTTPSession::OpenTransactionL()
provides the transaction
callback. The class which implements the callback is separate in
HTTPEXAMPLECLIENT
. From the header file
httpexampleclient.h
:
class CHttpEventHandler : public CBase, public MHTTPTransactionCallback
{
public:
//
// methods from MHTTPTransactionCallback
//
void MHFRunL(RHTTPTransaction aTransaction, THTTPEvent aEvent);
TInt MHFRunError(TInt aError, RHTTPTransaction aTransaction, THTTPEvent aEvent);
The two callback methods are inherited from
MHTTPFilterBase
: hence the MHF prefix to their names. This
is an interesting feature of the HTTP Client design: client code is treated
like a filter. The use of these methods is described later.
After opening the transaction, the client may set request headers, if desired. Note that the use of request headers is optional for very simple transactions: the few headers that RFC 2616 describes as mandatory for HTTP/1.1 are automatically generated.
To access the headers associated with a transaction's request or
response, the RHTTPHeaders
class is used. The handle is
obtained from either the RHTTPRequest
or
RHTTPResponse
objects associated with the
transaction.
From the implementation file
httpexampleclient.cpp
:
RHTTPHeaders hdr = trans.Request().GetHeaderCollection();
// Add headers appropriate to all methods
SetHeaderL(hdr, HTTP::EUserAgent, KUserAgent);
SetHeaderL(hdr, HTTP::EAccept, KAccept);
...
void CHttpClient::SetHeaderL(RHTTPHeaders aHeaders, TInt aHdrField, const TDesC8& aHdrValue)
{
RStringF valStr = iSession.StringPool().OpenFStringL(aHdrValue);
THTTPHdrVal val(valStr);
aHeaders.SetFieldL(iSession.StringPool().StringF(aHdrField), val);
valStr.Close();
}
Note that the header field types are specified using enumerations
from the HTTP
namespace, e.g.
HTTP::EUserAgent
. The class used to hold the header field
value, THTTPHdrVal
, is like a C++ union, in that it can
hold different data types. In this example, it holds an
RStringF
value which is initialised using the session's
string-pool from the supplied descriptor. These must be 8-bit strings, since
the RFC 2616 assumes a 7-bit encoding for all transmissions.
When headers have been set, a simple transaction with no request body can be started immediately. This is the case for HTTP methods such as GET, HEAD, and TRACE. Some other HTTP methods include a body in the request, e.g. POST. The data supplier that the client uses to supply request body data must be associated with the transaction before the transaction is started.
In HTTPEXAMPLECLIENT
, the CHttpClient
class also acts as its own data supplier when the POST method is chosen in the
menu. See Handling request body data for examples of code that provides a request body.
When the transaction ready to start, the client calls
RHTTPTransaction::SubmitL()
to indicate that the request
should be submitted. From the implementation file
httpexampleclient.cpp
:
...
// submit the transaction
trans.SubmitL();
// Start the scheduler, once the transaction completes or is cancelled on an error the scheduler will be
// stopped in the event handler
CActiveScheduler::Start();
The HTTPEXAMPLECLIENT
application is implemented as a
synchronous client, hence the local CActiveScheduler
. When
CActiveScheduler::Stop()
is called upon completion of the
transaction, execution continues from this point. More complex applications
will have an active scheduler elsewhere that should already be running.
The transaction is now processed by HTTP. All processing by the internal HTTP core and protocol/transport handlers is done in the client's thread. As data is received from the HTTP server, events are generated internally and passed back to the client via the session filters. When an event reaches the client. HTTP invokes the transaction callback.
From the implementation file
httpexampleclient.cpp
:
void CHttpEventHandler::MHFRunL(RHTTPTransaction aTransaction, THTTPEvent aEvent)
{
switch (aEvent.iStatus)
{
case THTTPEvent::EGotResponseHeaders:
{
...
}
break;
case THTTPEvent::EGotResponseBodyData:
{
...
}
break;
case THTTPEvent::EResponseComplete:
{
...
}
break;
case THTTPEvent::ESucceeded:
{
...
}
break;
case THTTPEvent::EFailed:
{
...
}
break;
default:
{
...
}
break;
}
}
Events are processed in turn by the client. They arrive in the order shown in the example code above. The following list explains the events' meanings:
THTTPEvent::EGotResponseHeaders
indicates
that the response status is known, and that the headers from the response are
ready for access by the client. This event will always be the first received,
unless an error condition has arisen.
THTTPEvent::EGotResponseBodyData
indicates
that some body data is ready for processing. Response bodies are generally
large enough to require splitting into smaller pieces (due to the use of
fixed-size internal buffers), so the event will arrive as many times as there
are pieces, until the whole body has been retrieved.
THTTPEvent::EResponseComplete
indicates
that the transaction's response has been completely received. The client can
now expect to be told whether the transaction was successful or not.
THTTPEvent::ESucceeded
is one of two
possible final messages: it indicates success of the transaction.
THTTPEvent::EFailed
is the second of the
two possible final messages: it indicates failure of the transaction.
default
: an unrecognised event. Negative values
indicate an error propogated from filters or lower comms layers. If not
understood by the client, error values may be safely ignored since a
THTTPEvent::EFailed
event is guaranteed to follow. Positive values
are used for warning conditions.
To obtain the response status code, and text description, the transaction response is used:
case THTTPEvent::EGotResponseHeaders:
{
RHTTPResponse resp = aTransaction.Response();
TInt status = resp.StatusCode();
RStringF statusStr = resp.StatusText();
The response headers can be iterated using the
THTTPHdrFieldIter
class. Individual header fields can be
queried as in this example:
RHTTPResponse resp = aTrans.Response();
RStringPool strP = aTrans.Session().StringPool();
RHTTPHeaders hdr = resp.GetHeaderCollection();
THTTPHdrFieldIter it = hdr.Fields();
TBuf<KMaxHeaderNameLen> fieldName16;
TBuf<KMaxHeaderValueLen> fieldVal16;
while (it.AtEnd() == EFalse)
{
// Get the name of the next header field
RStringTokenF fieldName = it();
RStringF fieldNameStr = strP.StringF(fieldName);
// Check it does indeed exist
THTTPHdrVal fieldVal;
if (hdr.GetField(fieldNameStr,0,fieldVal) == KErrNone)
{
...
// Display realm for WWW-Authenticate header
RStringF wwwAuth = strP.StringF(HTTP::EWWWAuthenticate,RHTTPSession::GetTable());
if (fieldNameStr == wwwAuth)
{
// check the auth scheme is 'basic'
RStringF basic = strP.StringF(HTTP::EBasic,RHTTPSession::GetTable());
RStringF realm = strP.StringF(HTTP::ERealm,RHTTPSession::GetTable());
THTTPHdrVal realmVal;
if (fieldVal.StrF() == basic)
// check the header has a 'realm' parameter
if (hdr.GetParam(wwwAuth, realm, realmVal) == KErrNone)
{
RStringF realmValStr = strP.StringF(realmVal.StrF());
fieldVal16.Copy(realmValStr);
Printf(_L("Realm is: %S\n"), &fieldVal16);
}
}
}
// Advance the iterator
++it;
}
To access the response body, the data supplier contained in the transaction response must be used. When the client has finished processing each piece of body data, the data must be released:
case THTTPEvent::EGotResponseBodyData:
{
// Some (more) body data has been received. Get the body data supplier
MHTTPDataSupplier* body = aTransaction.Response().Body();
TPtrC8 dataChunk;
TBool isLast = body->GetNextDataPart(dataChunk);
Dump(dataChunk);
if (isLast)
Printf(_L("Got the last data chunk.\n"));
// Done with that bit of body data
body->ReleaseData();
}
break;
The MHTTPDataSupplier::OverallDataSize()
method can be used to find out how large the entire body is before processing
the body data. The value returned is based on the HTTP response Content-Length
header. Not all responses will include this header however: e.g. when the HTTP
server is using the 'chunked' transfer encoding. In that circumstance, the
overall data size will be returned as KErrNotFound
.
Regardless of that, the final piece of body data will always cause
MHTTPDataSupplier::GetNextDataPart()
to return
ETrue
.
In HTTPEXAMPLECLIENT
, the final completion of the
transaction (indicated with a success or failure message) causes the local
active scheduler to stop. The EResponseComplete
code is not used
for anything special here. Since each transaction is guaranteed to send either
an ESucceeded
event or an EFailed
event, those can be
used as a signal to finish.
case THTTPEvent::EResponseComplete:
{
// The transaction's response is complete
Printf(_L("\nTransaction Complete\n"));
}
break;
case THTTPEvent::ESucceeded:
{
Printf(_L("Transaction Successful\n"));
aTransaction.Close();
CActiveScheduler::Stop();
}
break;
case THTTPEvent::EFailed:
{
Printf(_L("Transaction Failed\n"));
aTransaction.Close();
CActiveScheduler::Stop();
}
break;
The transaction is closed using
RHTTPTransaction::Close()
. This causes resources
associated with that transaction to be freed interally. The transaction should
not be used again.
When the client is ready to terminate, the session is closed:
CHttpClient::~CHttpClient()
{
iSession.Close();
...
}
Closing the session causes all resources to be returned to the system. Any remaining transactions that weren't complete are immediately cancelled.