A filter is best thought of as manipulating a transaction on its way either to or from the server. Behaviours can be implemented in filters that modify the transaction, e.g. change headers, add or remove headers, transform the body; that terminate or cancel a transaction, perhaps resubmitting a replacement; or that perform other operations on the client device using information obtained from transaction headers or body.
An RHTTPSession
contains a queue that can hold
zero or more filters, which are arranged in a priority order. The filter
objects are shared amongst all transactions. To join the filter queue, a filter
must register itself on the session, providing registration details that
specify its triggers.
Filter triggers may include: particular events, the presence of particular headers or particular status codes.
When an event occurs on a transaction, the event traverses the filter
queue in one of two directions: from client to server, or from server back to
the client. Priority order determines which filters are visited first when
events traverse away from the client (i.e. events that originate from the
client such as THTTPEvent::ESubmit
, which is sent when
RHTTPTransaction::SubmitL()
is called); reverse priority
order applies to events that traverse back towards the client (i.e. those that
originate from the server, such as
THTTPEvent::EResponseComplete
).
When an RHTTPSession
is opened, a standard set
of filters is pre-installed. The client does not need to do anything further if
these filters are acceptable. The standard set includes:
Redirection filter
Validation filter
The client may add further filters of its own, or remove filters
from the pre-installed set. RHTTPFilterCollection
, for
which a handle is obtained using
RHTTPSession::FilterCollection()
, provides facilities for
adding and deleting filters, and for querying what filters are installed.
TFilterConfigurationIterator
allows a client to enumerate
and query all available filters, and to install and uninstall a selected
one.
Filters may only be added to, or removed from, a session when no
transactions are opened on that session. This is easy to determine immediately
after the session itself is opened; to aid the client in determining the
condition later on,
RHTTPFilterCollection::CanChangeFilters()
is
provided.
The collection of currently-installed filters can be queried using an iterator. The following sample code demonstrates the use of the iterator:
void CHttpClient::ShowFilters()
{
RHTTPFilterCollection filtColl = iSess.FilterCollection();
THTTPFilterIterator iter = filtColl.Query();
THTTPFilterRegistration regInfo;
iter.First();
TInt lines = 0;
while (!iter.AtEnd())
{
// Get next filter registration info
regInfo = iter();
TBuf<KMaxFilterNameLength> name;
name.Copy(iSess.StringPool().StringF(regInfo.iName).DesC().Left(KMaxFilterNameLength));
TBuf<KMaxHeaderNameLength> header;
header.Copy(iSess.StringPool().StringF(regInfo.iHeader).DesC().Left(KMaxHeaderNameLength));
Printf(_L("\n%16S | %4d | %4d | %16S | %3d | %2d"),
&name, regInfo.iPosition, regInfo.iEvent.iStatus, &header, regInfo.iStatus, regInfo.iHandle);
++iter;
}
}
The authentication filter provides an easy way of supporting basic
and digest authentication as defined in RFC2617. As it needs a way of getting
hold of passwords, it is not installed as standard, but only when a
MHTTPAuthenticationCallback
installs it. To use it, you
will need to implement a subclass of
MHTTPAuthenticationCallback
, and should see the
documentation for that class for further details.
Anyone using HTTP authentication should be aware of its security
limitations. In particular, basic authentication essentialy involves passing
passwords around in plaintext.
MHTTPAuthenticationCallback::GetCredentialsL()
is told the
authentication scheme being used, and applications where plaintext passwords
would be a problem should consider rejecting challenges using basic
authentication. See section 4 of RFC2617 for more details.
The authentication filter supports the following:
Basic (Base64) authentication
Digest authentication using the MD5 algorithm and the 'auth' Quality of Protection.
The older RFC2069 style digest authentication for backwards compatibility.
The MD5-sess algorithm is not supported as no major servers seem to support it. The 'auth-int' qop is not supported as it does not seem to add any real benefit: for integrity checking, ssl would be preferred.
The filter remembers passwords and will attempt to use them for subsequent challenges where they appear to be apropriate. It will forget them if they turn out to be wrong. Currently there is no facility to persist the passwords. For basic authentication, an attempt is made to supply the username and password with the first request if the URI suggests that a previously stored username and password are applicable. This is not done for digest, as that introduces extra complications into the digest algorithm.
If an authentication challenge is received which the filter can't
understand or if the MHTTPAuthenticationCallback
does not
supply credentials (returns EFalse
) the filter effectively does
nothing, meaning that the client will receive the 401 error response in the
same way as other error responses.
There are two alternative methods of supplying the username and
password with a request. Clients that already know the username and password
might can consider using these methods and getting their
MHTTPAuthenticationCallback::GetCredentialsL()
to always
return EFalse.
The first method is to supply a URI of the form
http://<username>:<password>@host/
. In this case the
username and the password are removed from the URI when it is submitted, but
will be used for any subsequent authentication challenge. The other way is to
define transaction properties called 'username' and 'password' containing the
username and password.
The filter is registered at position
MHTTPFilter::EStatusCodeHandler
for handling the '401'
return code, and at position EStatusCodeHandler
+ 1 for handling
submit events.
The Redirection filter handles 300-series status codes from HTTP servers. These are used to tell the client of the correct location of a resource that has moved. Most clients will want this situation to be handled transparently: i.e. to make a new request for the resource at the location specified by the server using the 'Location' header.
When an HTTP response is received that includes the status codes 300, 301, 302, 303 or 307, the Redirection filter cancels the current transaction, and uses the URI from the 'Location' response header to make a resubmitted request on the same transaction. This means that all the headers in the original client request are preserved in the new request.
If no 'Location' header was found in the server response, the
filter will send a KErrHttpRedirectNoLocationField
error
to the client. The response body may contain further information about the
possible location of the resource.
In the case of the HTTP 305 'Use Proxy' status code, the
transaction is not resubmitted. Instead, a
KErrHttpRedirectUseProxy
error is sent to the client. The
'Location' header will contain the address of a proxy to which the client must
send the request for that URL. In practice, this means the client should modify
the properties of their current RHTTPSession
as described
in Session and transaction properties.
HTTP 304 'Not Modified' responses are not handled by the redirection filter since they are used to indicate that the client contains a valid copy of the resource in cache.
It is possible for the client request to be redirected more than once. To prevent the request becoming stuck in an endless loop of redirections, the number of successive redirections that the filter will handle is limited to 5.
The Validation filter has three main roles:
it checks the presence of a request body to ensure it is consistent with the HTTP method in use
it validates client requests to ensure that they do not include inappropriate header fields
iIt provides a simplified result status for transactions
Request bodies are only allowed for HTTP methods POST and PUT. If
GET, HEAD or TRACE requests contain a body the filter will cancel the
transaction and send a KErrHttpRequestHasBody
error event
to the client.
If the request headers contain any fields that are defined by RFC
2616 as response header fields, the error event
KErrHttpInvalidHeaderInRequest
is sent to the client.
Offending fields are removed, and the transaction is allowed to
continue.
If the request does not have a body, and the request headers
contain any fields that are defined by RFC 2616 as entity header fields, then
KErrHttpInvalidHeaderInRequest
is sent to the client, the
fields are removed, and the transaction continues.
If the request has a body, and the 'Content-Type' header is not
present in the request headers, then the transaction will be cancelled and the
error event KErrHttpEntityHeaderMissingContentType
is sent
to the client.
See also Headers and the header enumerations in HTTP for further information about different header types.
When a transaction response is received, the validation filter will
determine from the status code whether the transaction has ultimately been a
success or a failure. The two events
THTTPEvent::ESucceeded
and
THTTPEvent::EFailed
are used to inform the client of this.
The client may assume that no further events for that transaction will arrive
once it has received either of these two.
Apart from the standard set of filters, the client must manually install any other filter it wishes to use. Some filters may need to be configured before they can be installed; this should be done when the filter is instantiated.
An example of where filter configuration is needed is the
authentication filter, which requires a callback to gather user credentials
from the client. The client must implement the
MHTTPAuthenticationCallback
class, and at the time it
opens an HTTP session must specify the object that implements the callback to
configure the filter.
From httpexampleclient.h
:
class CHttpClient : public CBase, public MHTTPDataSupplier, public MHTTPAuthenticationCallback
{
public:
...
// methods inherited from MHTTPAuthenticationCallback
virtual TBool GetCredentialsL(const TUriC8& aURI, RString aRealm,
RStringF aAuthenticationType,
RString& aUsername,
RString& aPassword);
...
From httpexampleclient.cpp
:
void CHttpClient::ConstructL()
{
...
// Open the RHTTPSession and install this class as the callback for authentication requests
iSess.OpenL();
InstallAuthenticationL(iSess);
...
}
In this case,
MHTTPAuthenticationCallback::InstallAuthenticationL()
already is
implemented in the library. It does the job of creating and installing the
filter on the client's behalf:
void MHTTPAuthenticationCallback::InstallAuthenticationL(RHTTPSession aSession)
{
// Create an authentication filter. This will install itself, and
// will delete itself when uninstalled, so we don't need to keep
// track of it at all.
CAuthenticationFilter::NewL(*this, aSession);
}
However in other cases, the client may have to construct the filter itself, and configure it, before installing it on the session, e.g.:
void CAuthenticationFilter::ConstructL(RHTTPSession aSession)
{
...
// Register for WWW-Authenticate headers and 401 status codes
aSession.FilterCollection().AddFilterL(*this,
THTTPEvent::EGotResponseHeaders,
iStringPool.StringF(HTTP::EWWWAuthenticate,RHTTPSession::GetTable()),
401,
EStatusCodeHandler,
iStringPool.StringF(HTTP::EAuthentication,RHTTPSession::GetTable()));
...
}
This section provide an overview of writing filters. Refer to the functions mentioned for more details.
Filters need to derive from the MHTTPFilter
class. Normally, the constructor or NewL()
of a filter would take
a session as a parameter, and would then register itself by calling
RHTTPFilterCollection::AddFilterL()
.
Filters can often delete themselves automatically. If a filter only
registers itself once, it can delete itself simply by overriding
MHFUnload()
and delete this
in it. If you register
several times, it's probably easiest to overload both
MHTTPFilter::MHFLoad()
and
MHTTPFilter::MHFUnload()
, increment a reference count in
MHFLoad()
, decrement it in MHTTPFilter::MHFUnload()
and when it reaches zero, delete this
.
A filter is notified of events through
MHTTPFilterBase::MHFRunL()
, just like client notification.
If the MHFRunL()
implementation leaves, the filter must handle the
error in MHTTPFilterBase::MHFRunError()
. There is a
potential problem there, in that you may well want to tell the client that
something has gone wrong by
RHTTPTransaction::SendEventL()
, which can itself leave. If
it does leave, you may be forced to call
RHTTPTransaction::Fail()
, which cancels the transaction
and sends an THTTPEvent::EUnrecoverableError
message
outwards.
It's important to note that a filter object is per-session, and so might be shared by several transactions. This means that if you have per-transaction state that you need to store, it must be stored in the transaction's property set. Do not store any per-transaction information in the filter object.