// Copyright (c) 2003-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//
#include "chttprequestcomposer.h"
#include <httpstringconstants.h>
#include <http/rhttpheaders.h>
#include "chttpresponseparser.h"
#include "mhttprequestobserver.h"
#include "chttpclienttransaction.h"
CHttpRequestComposer* CHttpRequestComposer::NewL(CProtTransaction& aProtTrans, MHttpRequestObserver& aObserver)
{
CHttpRequestComposer* self = new (ELeave) CHttpRequestComposer(aProtTrans, aObserver);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(self);
return self;
}
CHttpRequestComposer::~CHttpRequestComposer()
{
if(iCorkingEnabled)
{
// Disable corking.
iObserver.SendingBodyData(EFalse);
}
iMessageComposer.Close();
iTrailerHeaders.Reset();
// __FLOG_CLOSE;
}
CHttpRequestComposer::CHttpRequestComposer(CProtTransaction& aProtTrans, MHttpRequestObserver& aObserver)
: CTxData(aProtTrans), iStringTable(RHTTPSession::GetTable()),
iObserver(aObserver),
iStringPool(aProtTrans.Transaction().Session().StringPool()),
iRequest(aProtTrans.Transaction().Request()),
iFields(aProtTrans.Transaction().Request().GetHeaderCollection().Fields()),
iPropertySet(aProtTrans.Transaction().PropertySet())
{
// __FLOG_OPEN("http", "httpclienthandler.txt");
}
void CHttpRequestComposer::ConstructL()
{
iMessageComposer.OpenL(*this);
}
void CHttpRequestComposer::NotifyMoreRequestBodyData()
{
__FLOG_1(_T8("Trans %d : notify more request body data"), iProtTrans->Transaction().Id());
iMessageComposer.MessageInfoAvailable();
}
void CHttpRequestComposer::InitHeadersL()
{
// Extract header fields from the Trailer header, if it exists.
THTTPHdrVal trailerHeaderVal;
RHTTPHeaders headers = iRequest.GetHeaderCollection();
RStringF trailerName = iStringPool.StringF(HTTP::ETrailer, iStringTable);
TInt index =0;
while( headers.GetField(
trailerName,
index,
trailerHeaderVal
) != KErrNotFound )
{
__ASSERT_DEBUG( trailerHeaderVal.Type() == THTTPHdrVal::KStrFVal, User::Invariant() );
// @todo should we check to see if header is not Content-Length,
// @todo Transfer-Encoding or Trailer?
// Got a trailer header - append to the list
RStringF header = trailerHeaderVal.StrF();
User::LeaveIfError(iTrailerHeaders.Append(header));
// Next...
++index;
}
if( iTrailerHeaders.Count() > 0 )
{
// There are trailer headers - can only send as trailers if the request
// has a body and it is going to be chunk encoded.
MHTTPDataSupplier* body = iRequest.Body();
if( body == NULL || body->OverallDataSize() != KErrNotFound )
{
// Either there is no body, or the body is not going to be chunk
// encoded - remove the Trailer header field.
headers.RemoveField(trailerName);
iTrailerHeaders.Reset();
}
}
// Reset the fields iterator
iFields.First();
}
TBool CHttpRequestComposer::IsTrailerHeader(RStringF aHeader)
{
TInt count = iTrailerHeaders.Count();
TBool found = EFalse;
while( count > 0 && !found )
{
if( iTrailerHeaders[--count] == aHeader )
{
// aHeader is a trailer header
found = ETrue;
}
}
return found;
}
/*
* Methods from MHTTPDataSupplier, via CTxData
*/
TBool CHttpRequestComposer::GetNextDataPart(TPtrC8& /*aDataPart*/)
{
// @todo should we deprecate derivation from MHTTPDataSupplier?
User::Invariant();
return EFalse;
}
void CHttpRequestComposer::ReleaseData()
{
// @todo should we deprecate derivation from MHTTPDataSupplier?
User::Invariant();
}
TInt CHttpRequestComposer::OverallDataSize()
{
// @todo should we deprecate derivation from MHTTPDataSupplier?
User::Invariant();
return KErrNotFound;
}
TInt CHttpRequestComposer::Reset()
{
// @todo should we deprecate derivation from MHTTPDataSupplier?
User::Invariant();
return KErrNotFound;
}
/*
* Methods from MHttpRequest
*/
void CHttpRequestComposer::StartRequest()
{
__FLOG_1(_T8("Trans %d : starting request"), iProtTrans->Transaction().Id());
RStringF notifyContinue = iStringPool.StringF(HTTP::ENotify100Continue, iStringTable);
RStringF enableNotification = iStringPool.StringF(HTTP::EEnableNotification, iStringTable);
THTTPHdrVal notifyContinueVal;
iPropertySet.Property(notifyContinue, notifyContinueVal);
((notifyContinueVal.Type()==THTTPHdrVal::KStrFVal) && (notifyContinueVal.StrF() == enableNotification))?(iSuspendRequest = ETrue):(iSuspendRequest = EFalse);
iMessageComposer.MessageInfoAvailable();
}
void CHttpRequestComposer::CancelRequest()
{
__FLOG_1(_T8("-> Trans %d : request cancelled"), iProtTrans->Transaction().Id());
iMessageComposer.Reset();
}
void CHttpRequestComposer::RequestDataSent()
{
if(!IsSuspendedRequest())
{
iMessageComposer.ReleaseMessageData();
}
}
void CHttpRequestComposer::OnRequestSendTimeOut()
{
// Notify the client of Request Send TimeOut.
RHTTPTransaction trans = iProtTrans->Transaction();
if(trans.SendEvent(THTTPEvent::ESendTimeOut, THTTPEvent::EIncoming, THTTPFilterHandle(THTTPFilterHandle::EProtocolHandler)) != KErrNone)
{
trans.Fail(THTTPFilterHandle::EProtocolHandler);
}
else
{
// Notify the client that Transaction Failed.
if(trans.SendEvent(THTTPEvent::EFailed, THTTPEvent::EIncoming, THTTPFilterHandle(THTTPFilterHandle::EProtocolHandler)) != KErrNone)
{
trans.Fail(THTTPFilterHandle::EProtocolHandler);
}
}
}
TInt CHttpRequestComposer::SendTimeOutValue()
{
RHTTPTransaction trans = iProtTrans->Transaction();
RStringPool stringPool = trans.Session().StringPool();
RStringF sendTimeOut = stringPool.StringF(HTTP::ESendTimeOutValue, iStringTable);
THTTPHdrVal sendTimeOutVal;
TBool ret = trans.PropertySet().Property(sendTimeOut,sendTimeOutVal);
if(ret && (sendTimeOutVal.Type() == THTTPHdrVal::KTIntVal))
{
return sendTimeOutVal.Int();
}
return 0;
}
/*
* Methods from MHttpMessageComposerObserver
*/
void CHttpRequestComposer::StartLineL(TPtrC8& aMethod, TPtrC8& aRequestUri, TPtrC8& aVersion)
{
// The RFC2616 defines the Request-Line as follows -
//
// Request-Line = Method SP Request-URI SP HTTPVersion CRLF
//
// Get Method...
RStringF method = iStringPool.StringF(iRequest.Method());
aMethod.Set(method.DesC());
RHTTPTransaction trans = iProtTrans->Transaction();
// Get the Request-URI...
THTTPHdrVal uri;
if( !trans.PropertySet().Property(iStringPool.StringF(HTTP::EUri, iStringTable), uri) )
{
// The EUri property has not been set - leave!
User::Leave(KErrCorrupt);
}
aRequestUri.Set(uri.Str().DesC());
// Get HTTPVersion - check to see if client has set a version
RHTTPConnectionInfo connectionInfo = trans.Session().ConnectionInfo();
THTTPHdrVal httpVersion;
if( connectionInfo.Property(iStringPool.StringF(HTTP::EHTTPVersion, iStringTable), httpVersion) )
{
// Use the client specified version
aVersion.Set(httpVersion.StrF().DesC());
}
else
{
// Assume HTTP/1.1
aVersion.Set(iStringPool.StringF(HTTP::EHttp11, iStringTable).DesC());
}
__FLOG_4(_T8("Trans %d : request-line -> %S %S %S"), iProtTrans->Transaction().Id(), &aMethod, &aRequestUri, &aVersion);
// Initialise header info
InitHeadersL();
}
TInt CHttpRequestComposer::NextHeaderL(TPtrC8& aHeaderName, TPtrC8& aHeaderValue)
{
// Are there any more headers?
TInt err = KErrNotFound;
TBool done = EFalse;
while( !iFields.AtEnd() && !done )
{
// Get field current field.
RStringF name = iStringPool.StringF(iFields());
// Check to see if a trailer header
if( !IsTrailerHeader(name) )
{
// Ok, found a header - done!
err = KErrNone;
done = ETrue;
// Get the OTA version of the field value
TPtrC8 value;
iRequest.GetHeaderCollection().GetRawFieldL(name, value);
// Pass back these values
aHeaderName.Set(name.DesC());
aHeaderValue.Set(value);
__FLOG_3(_T8("Trans %d : request header -> %S: %S"), iProtTrans->Transaction().Id(), &aHeaderName, &aHeaderValue);
}
// Move onto next header field...
++iFields;
}
return err;
}
MHTTPDataSupplier* CHttpRequestComposer::HasBodyL()
{
#if defined (_DEBUG) && defined (_LOGGING)
MHTTPDataSupplier* body = iRequest.Body();
if( body == NULL )
{
__FLOG_1(_T8("Trans %d : no request entity body"), iProtTrans->Transaction().Id());
}
else if( body->OverallDataSize() == KErrNotFound )
{
__FLOG_1(_T8("Trans %d : chunked request entity body"), iProtTrans->Transaction().Id());
}
else
{
__FLOG_2(_T8("Trans %d : request entity body length = %d"), iProtTrans->Transaction().Id(), body->OverallDataSize());
}
#endif
return iRequest.Body();
}
TInt CHttpRequestComposer::NextTrailerL(TPtrC8& aHeaderName, TPtrC8& aHeaderValue)
{
TInt err = KErrNotFound;
if( iTrailerHeaders.Count() > 0 )
{
// Ok, still got trailers.
err = KErrNone;
// Get trailer field.
RStringF name = iTrailerHeaders[0];
// Get the OTA version of the field value
TPtrC8 value;
iRequest.GetHeaderCollection().GetRawFieldL(name, value);
// Pass back these values
aHeaderName.Set(name.DesC());
aHeaderValue.Set(value);
// Remove this trailer from the array.
iTrailerHeaders.Remove(0);
__FLOG_3(_T8("Trans %d : request trailer -> %S: %S"), iProtTrans->Transaction().Id(), &aHeaderName, &aHeaderValue);
}
return err;
}
void CHttpRequestComposer::MessageComplete()
{
__FLOG_1(_T8("Trans %d : request complete"), iProtTrans->Transaction().Id());
if(iCorkingEnabled)
{
// Disable corking.
iCorkingEnabled = iRequestHeaderSent = EFalse;
iObserver.SendingBodyData(EFalse);
}
iObserver.RequestComplete();
iRequestSent = ETrue;
}
void CHttpRequestComposer::MessageDataReadyL()
{
if(iRequestHeaderSent)
{
// If we hit this function that means we need to send body data.
// So we are sending the body data. Enable corking.
if(!iCorkingEnabled)
{
__FLOG_1(_T8("Trans %d : Corking is enabled"), iProtTrans->Transaction().Id());
iCorkingEnabled = ETrue;
iObserver.SendingBodyData(ETrue);
}
else
{
__FLOG_1(_T8("Trans %d : Corking is already enabled"), iProtTrans->Transaction().Id());
}
}
else
{
__FLOG_1(_T8("Trans %d : request header is set"), iProtTrans->Transaction().Id());
iRequestHeaderSent = ETrue;
}
// Notify the observer that there is message data ready to send.
iMessageComposer.GetMessageData(iData);
iObserver.SendRequestDataL(iData);
}
TInt CHttpRequestComposer::HandleComposeError(TInt aError)
{
__FLOG_1(_T8("!! Error : %d"), aError);
__FLOG_1(_T8("-> Trans %d : request composing error - cancelling transaction"), iProtTrans->Transaction().Id());
return static_cast<CHttpResponseParser&>(iProtTrans->RxData()).CancelTransaction(aError);
}
TBool CHttpRequestComposer::CheckRequestPendingComplete()
{
return iMessageComposer.CheckMessagePendingComplete();
}
TBool CHttpRequestComposer::RequestSent () const
{
return iRequestSent;
}
TBool CHttpRequestComposer::NeedDisconnectNotification ()
{
CHttpClientTransaction* clientTrans = static_cast < CHttpClientTransaction* > ( iProtTrans );
return clientTrans->NeedDisconnectNotification ();
}
void CHttpRequestComposer::Reserved_MHttpMessageComposerObserver()
{
User::Invariant();
}
void CHttpRequestComposer::ResumeSuspendedRequest()
{
iSuspendRequest = EFalse;
iMessageComposer.ReleaseMessageData();
}
TBool CHttpRequestComposer::IsSuspendedRequest() const
{
return iSuspendRequest;
}
void CHttpRequestComposer::CancelWaitFor100Continue()
{
__FLOG_1(_T8("Trans %d : client declined to wait for the 100-Continue Response"), iProtTrans->Transaction().Id());
if(IsSuspendedRequest())
{
ResumeSuspendedRequest();
}
}
MHttpDataOptimiser* CHttpRequestComposer::HttpDataOptimiser(TBool& aBatchingEnabled)
{
RHTTPTransaction trans = iProtTrans->Transaction();
MHttpDataOptimiser* httpOptimiser = trans.HttpDataOptimiser();
RHTTPSession sess = trans.Session();
RStringPool stringPool = sess.StringPool();
THTTPHdrVal value;
RStringF string = stringPool.StringF(HTTP::EHttpBatching, iStringTable);
RStringF str = stringPool.StringF(HTTP::EEnableBatching, iStringTable);
if(sess.ConnectionInfo().Property(string, value))
{
if(value.Type() == THTTPHdrVal::KStrFVal)
{
aBatchingEnabled = value.StrF() == str;
}
}
if( httpOptimiser && !aBatchingEnabled )
{
// the optimiser has not been set for the session.
__FLOG_1(_T8("-> Trans %d : Http optimiser has been set for this transaction"), iProtTrans->Transaction().Id());
return (httpOptimiser);
}
// the MHttpDataOptimiser hasn't been set for the transaction
// check whether the session encapsulating the transaction is having MHttpDataOptimiser.
httpOptimiser = sess.HttpDataOptimiser();
if( httpOptimiser )
{
// the optimiser has been set for the session.
__FLOG_1(_T8("-> Trans %d : HTTP optimiser has been set for the session encapsulating this transaction"), iProtTrans->Transaction().Id());
// check if the client has disabled optimiser for a particular transaction.
TBool disableTransOptimiser = EFalse;
string = stringPool.StringF(HTTP::EHTTPTransOptimiser, iStringTable);
str = stringPool.StringF(HTTP::EDisableHTTPTransOptimiser, iStringTable);
if(iProtTrans->Transaction().PropertySet().Property(string, value))
{
if(value.Type() == THTTPHdrVal::KStrFVal)
{
disableTransOptimiser = ( value.StrF() == str );
}
}
if ( disableTransOptimiser )
{
// the client has disabled the optimiser for a particular transaction.
__FLOG_1(_T8("-> Trans %d : HTTP optimiser has been disabled by the client"), iProtTrans->Transaction().Id());
// do not return the optimiser.
return NULL;
}
// the client has not diasabled the optimiser for any transaction in particular.
// return the optimiser.
return (httpOptimiser);
}
// MHttpDataOptimiser has not been set for the session and transaction
__FLOG_1(_T8("-> Trans %d : HTTP optimiser was never set for the transaction and the session enclosing it"), iProtTrans->Transaction().Id());
return NULL;
}