diff -r 000000000000 -r b16258d2340f applayerpluginsandutils/httpprotocolplugins/httpclient/chttpconnectionmanager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/applayerpluginsandutils/httpprotocolplugins/httpclient/chttpconnectionmanager.cpp Tue Feb 02 01:09:52 2010 +0200 @@ -0,0 +1,1449 @@ +// 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 "chttpconnectionmanager.h" + +#include +#include +#include + +#include "msocketfactory.h" +#include "msocketconnector.h" +#include "moutputstream.h" +#include "minputstream.h" +#include "mhttprequest.h" +#include "mhttpresponse.h" +#include "chttpconnectioninfo.h" +#include "chttprequestbatcher.h" + + +CHttpConnectionManager* CHttpConnectionManager::NewL(MSocketFactory& aSocketFactory, + MHttpBatchingPropertiesCallback& aCallback, + CHttpPipelineFallback& aPipelineFallback, + TInt aMaxTransactionsToPipeline, + TBool aEnableOptimalPipeline) + { + CHttpConnectionManager* self = new (ELeave) CHttpConnectionManager(aSocketFactory, aCallback, aPipelineFallback, aMaxTransactionsToPipeline, aEnableOptimalPipeline); + return self; + } + +CHttpConnectionManager::~CHttpConnectionManager() + { + iPendingRequests.Reset(); + iPendingResponses.Reset(); + + // Close down connect, if exists + if( iSocketConnector ) + { + iSocketConnector->StopConnect(); + } + CloseConnection(); + + delete iConnectionInfo; + iTunnelHost.Close(); + + delete iRequestBatcher; + delete iOptimiser; + } + +CHttpConnectionManager::CHttpConnectionManager(MSocketFactory& aSocketFactory, + MHttpBatchingPropertiesCallback& aCallback, + CHttpPipelineFallback& aPipelineFallback, + TInt aMaxTransactionsToPipeline, + TBool aEnableOptimalPipeline): + iEnableOptimalPipeline(aEnableOptimalPipeline), iMaxTransactionsToPipeline(aMaxTransactionsToPipeline), + iSocketFactory(aSocketFactory), iCallback(aCallback), iPipelineFallback(aPipelineFallback) + { + } + +void CHttpConnectionManager::SubmitL(CHttpConnectionInfo& aConnectionInfo, MHttpRequest& aRequest, MHttpResponse& aResponse) + { + // Check state - may need to close connection + TBool secure = EFalse; + if( iState == EIdleConnected ) + { + secure = iConnectionInfo->IsSecure(); + // Can the current connection be re-used? + if( !iConnectionInfo->HostAndPortMatches(aConnectionInfo) || + secure && !aConnectionInfo.IsSecure() ) + { + __ASSERT_DEBUG( iPendingResponses.Count() == 0, User::Invariant() ); + + // Nope - either the current connection is to the wrong host or the + // current connection is secure and need a non-secure connection. + CloseConnection(); + } + } + else if( iState == EClosing ) + { + __ASSERT_DEBUG( iConnectionInfo->IsNonPersistent(), User::Invariant() ); + __ASSERT_DEBUG( iPendingResponses.Count() == 0, User::Invariant() ); + + // The connection was non-persistent and the remote host has yet to + // close the connection - close it now. + CloseConnection(); + } + if ( iState == EIdleConnected || iState == EConnected ) + { + __ASSERT_DEBUG( iInputStream, User::Invariant() ); + iInputStream->Restart (); + } + + // Cleanup old connection info and take ownership of the new connection info. + delete iConnectionInfo; + iConnectionInfo = &aConnectionInfo; + + // Store the request and response in appropriate place + if( iCurrentRequest == NULL ) + { + // Make this the current request + iCurrentRequest = &aRequest; + } + else + { + __ASSERT_DEBUG( !CannotPipeline(), User::Invariant() ); + + // Append to the pending request queue + User::LeaveIfError(iPendingRequests.Append(&aRequest)); + } + + if( iCurrentResponse == NULL ) + { + // Make this the current response + iCurrentResponse = &aResponse; + } + else + { + __ASSERT_DEBUG( !CannotPipeline(), User::Invariant() ); + + // Append to the pending response queue + TInt err = iPendingResponses.Append( &aResponse ); + + // The request is already added into the array. Remove it from the array + // in case of any error and leave with the error code. + // This is required because a checking is going on in CancelSubmission fn. + // That function expects: if a request object is present in the array there "must" be a + // corresponding response object. + if ( err != KErrNone ) + { + TInt requestIndex = FindRequest( aRequest ); + if( requestIndex != KErrNotFound ) + { + iPendingRequests.Remove( requestIndex ); + } + User::Leave ( err ); + } + + } + + switch( iState ) + { + case EIdle: + { + __FLOG_4( + _T8("Start connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + // Need to start connection to the appropriate host + iSocketConnector = &iSocketFactory.ConnectL( + *this, + iConnectionInfo->Host(), + iConnectionInfo->Port() + ); + + if (!CannotPipeline()) + { + // we are pipelining so set this flag + iFlags.Set(EFirstTransaction); + } + // Move to Connecting state + iState = EConnecting; + } break; + case EIdleConnected: + { + // A connection is already established with the appropriate host and it + // is currently not being used by any other transaction. Check to see if + // an upgrade to a secure connection is required. + if( iConnectionInfo->IsSecure() && !secure ) + { + // Upgrade the connection to be secure. + UpgradeConnectionL(); + } + else + { + // Move to Connected state and notify the current request to start. + // Specifies that this transaction is waiting to write its + // request to the supposedly connected connection. Due timing issues + // the server may have closed connection but connection manager has + // not been notified yet. + iState = EConnected; + iFlags.Set(EPendingWriteInConnectedState); + iCurrentRequest->StartRequest(); + } + } break; + case EConnecting: + { + __ASSERT_DEBUG( !CannotPipeline(), User::Invariant() ); + + // Can only submit in this state is a transaction is being pipelined and + // pipelining is enabled. A connection is being established with the + // appropriate host for an earlier request. Do nothing. + } break; + case EConnected: + { + __ASSERT_DEBUG( !CannotPipeline(), User::Invariant() ); + + // Can only submit in this state is a transaction is being pipelined and + // pipelining is enabled. A connection has already been establised with + // the appropriate host but is currently being used by other transactions. + // Check to see if this request is now the current request. + if( iCurrentRequest == &aRequest ) + { + // It is - start the request... + iCurrentRequest->StartRequest(); + } + } break; + case EUpgrading: + { + __ASSERT_DEBUG( !CannotPipeline(), User::Invariant() ); + + // Can only submit in this state is a transaction is being pipelined and + // pipelining is enabled. A connection has already been established with + // the appropriate host and is currently being upgraded to be secure. Do + // nothing. + } break; + default: + User::Invariant(); + break; + } + } + +CHttpConnectionManager::TConnectionStatus CHttpConnectionManager::Status() const + { + // Check the state for correct status value. + CHttpConnectionManager::TConnectionStatus status; + + switch( iState ) + { + case EIdle: + case EClosing: + { + status = ENotConnected; + } break; + case EIdleConnected: + { + status = EConnectedAndAvailable; + } break; + case EConnecting: + { + if(iEnableOptimalPipeline) + { + status = EConnectingNotAvailable; + break; + } + } + case EUpgrading: + case EConnected: + default: + { + // Number of requests is the number of Pending Responses + the current response + TInt numberResponses = iPendingResponses.Count(); + if (iCurrentResponse != NULL) + { + numberResponses +=1; + } + + if( (!CannotPipeline() && !iConnectionInfo->IsNonPersistent()) && + numberResponses < iMaxTransactionsToPipeline) + { + // The connection can be used to pipeline requests - connect and busy. + status = EConnectedAndBusy; + } + else + { + // Other states, connected and not available. + status = EConnectedNotAvailable; + } + } + break; + } + return status; + } + +const CHttpConnectionInfo& CHttpConnectionManager::ConnectionInfo() const + { + return *iConnectionInfo; + } + + +void CHttpConnectionManager::CancelSubmission(MHttpRequest& aRequest, MHttpResponse& aResponse) + { + __FLOG_0(_T8("!! Cancel submission")); + + // If the request has not yet been sent, then just remove the request and + // response from the pending queues. Everything can carry on as normal. But + // the request is in the process of being sent or has already been sent and + // is waiting for a response, then need to close the connection, notifying + // the other responses of the connection closure. + + TInt requestIndex = FindRequest(aRequest); + TInt responseIndex = FindResponse(aResponse); + TBool stopConnection = EFalse; + + if( requestIndex != KErrNotFound ) + { + __ASSERT_DEBUG( responseIndex != KErrNotFound, User::Invariant() ); + + // The request has not been made - just need to remove the request and + // response objects from the pending queues. + iPendingRequests.Remove(requestIndex); + iPendingResponses.Remove(responseIndex); + } + else if( responseIndex != KErrNotFound ) + { + // The request has been sent but the response has not yet been received + // and therefore need to stop the connection and remove response from + // the pending queue. + iPendingResponses.Remove(responseIndex); + stopConnection = ETrue; + } + + // Is this request/response active? + if( stopConnection || iCurrentRequest == &aRequest || iCurrentResponse == &aResponse ) + { + // Yep - need to stop the connection. Check state as could be still + // trying to connect. + if( iState == EConnecting ) + { + __FLOG_4( + _T8("-> stopping connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + // Stop the connection - move to Idle state + iSocketConnector->StopConnect(); + iSocketConnector = NULL; + + SetIdleState(); + } + else + { + // As the connection had been established, then the request/response + // could be in progress - cancel them. + if( iCurrentRequest && iCurrentRequest == &aRequest) + { + __FLOG_0(_T8("-> cancelling request")); + iCurrentRequest->CancelRequest(); + } + + if( iCurrentResponse == NULL && iPendingResponses.Count() == 0 ) + { + iCurrentResponse = &aResponse; + } + + if( iCurrentResponse && iCurrentResponse == &aResponse ) + { + __FLOG_0(_T8("-> completing response")); + DoResponseCompletion (); + __FLOG_0(_T8("-> cancelling response")); + iCurrentResponse->CancelResponse(); + + // Need connection closure? + if ( stopConnection || + ( iCurrentResponse && iCurrentResponse == &aResponse && !iCurrentResponse->ResponseCompleted () ) ) + { + __FLOG_4( + _T8("-> closing connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + CloseConnection (); + } + else + { + ResponseDataParsed (); + } + } + } + + if ( iCurrentResponse && iState == EIdle ) + { + if ( stopConnection ) + { + __FLOG_0(_T8("-> Notifying current and pending transactions")); + + // The current transaction was not the one to cancel - need to + // notify it. This request has already sent and no response has been received. + // Need to resend the request to maintain the correct request/response state + // which is needed by the request/response composer/parser. + iCurrentResponse->ConnectionError(KErrHttpNonPipeliningError); + } + + NotifyPendingTransactions(KErrHttpNonPipeliningError); + + // Current request and response are no longer valid + iCurrentRequest = NULL; + iCurrentResponse = NULL; + + return; + } + + if ( iState == EIdle && iPendingRequests.Count() > 0 ) + { + // connection is closed explicitly by the connection manager or not active. If we are + // having pending requests to be sent need to reconnect here. + __FLOG_0(_T8("-> Reconnecting and sending the first pending request.")); + // Set the current request & response + iCurrentRequest = iPendingRequests[0]; + iCurrentResponse = iPendingResponses[0]; + // Remove it from the pending queue + iPendingRequests.Remove (0); + iPendingResponses.Remove (0); + + // Now do a reconnect + TRAPD ( err, ReconnectSocketL () ); + if ( err != KErrNone ) + { + ReportSocketError ( err ); + } + return; + } + if ( iCurrentRequest && iCurrentRequest == &aRequest ) + { + iCurrentRequest = NULL; + } + if ( iCurrentResponse && iCurrentResponse == &aResponse ) + { + iCurrentResponse = NULL; + } + } + else if( CannotPipeline() && !iCurrentRequest && !iCurrentResponse ) + { + // Closing the connection since pipelining has been disabled and both the request and + // response have been completed. + __FLOG_4( + _T8("-> closing connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + CloseConnection(); + } + // Otherwise - do nothing. This submission has already completed. +#if defined (_DEBUG) && defined (_LOGGING) + __FLOG_0(_T8("-> This submission has already completed. Do nothing")); +#endif + } + +const CX509Certificate* CHttpConnectionManager::ServerCert() + { + __ASSERT_DEBUG( iOutputStream != NULL, User::Invariant() ); + + return iOutputStream->ServerCert(); + } + +TInt CHttpConnectionManager::CipherSuite(TDes8& aCipherSuite) + { + __ASSERT_DEBUG( iOutputStream != NULL, User::Invariant() ); + + return iOutputStream->CipherSuite(aCipherSuite); + } + +void CHttpConnectionManager::TunnelConnection(RStringF aHost) + { + __ASSERT_DEBUG( iState == EConnected, User::Invariant() ); + + // Copy the host information (includes port info) for where the tunnel leads. + iTunnelHost.Close(); + iTunnelHost = aHost.Copy(); + iTunnel = ETrue; + + __FLOG_0(_T8("!! Tunnel establised")); + __FLOG_5( + _T8("-> tunnel to %S via host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iTunnelHost.DesC(), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + } + +TBool CHttpConnectionManager::TunnelMatches(RStringF aHost) const + { + return (iTunnel && iTunnelHost == aHost); + } + +void CHttpConnectionManager::CloseConnection() + { + if( iInputStream ) + { + // Flag that connection + iState = EClosing; + + // Close the input stream + iInputStream->Close(); + iInputStream = NULL; + } + // NOTE - there is no need to close the output stream as by closing the + // input stream it would have been notified and closed anyway. Also, the + // state should have moved to Idle. + + __ASSERT_DEBUG( iState == EIdle, User::Invariant() ); + } + +void CHttpConnectionManager::UpgradeConnectionL() + { + __ASSERT_DEBUG( iOutputStream != NULL, User::Invariant() ); + + __FLOG_0(_T8("!! Upgrading to secure")); + + // Request upgrade to a secure connection - move to Upgrading state. + TPtrC8 host; + if( iTunnel ) + { + __FLOG_5( + _T8("-> tunnel to %S via host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iTunnelHost.DesC(), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + // Host info is in the tunnel host name - this is an authority form data + // and so needs to be parsed. + TAuthorityParser8 authority; + User::LeaveIfError(authority.Parse(iTunnelHost.DesC())); + host.Set(authority.Extract(EAuthorityHost)); + } + else + { + __FLOG_4( + _T8("-> connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + // Just a normal connection - host info is in the connection info. + host.Set(iConnectionInfo->Host()); + } + iOutputStream->SecureClientReq(host); + iState = EUpgrading; + } + +void CHttpConnectionManager::HandleSocketError(TInt aError) + { + // Is this due to the connection manager closing the connection? + if( iState != EClosing ) + { +#if defined (_DEBUG) && defined (_LOGGING) + if( iState == EConnecting ) + { + __FLOG_1(_T8("!! Error : %d"), aError); + __FLOG_4( + _T8("-> could not connect to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + } + else + { + __FLOG_1(_T8("!! Error : %d"), aError); + __FLOG_4( + _T8("-> connection closed to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + } +#endif + if ( ( aError == KErrEof || aError == KErrCancel ) ) + { + if ( IsPendingWriteInConnectedState() && !iCurrentRequest->NeedDisconnectNotification() ) + { + // Server shut down the connect before the current transaction had + // a chance to send any of its data - attempt re-connect to server. + // Cancel the current request + iCurrentRequest->CancelRequest (); + TRAPD(err, ReconnectSocketL()); + if(err != KErrNone) + { + // Re-connection to server failed at 1st hurdle - give up and + // report the error. + ReportSocketError(aError); + } + else + return; // need early exit to maintain correct state - doh! + } + else + { + // Report the socket error + ReportSocketError(aError); + } + } + else + { + // Report the socket error + ReportSocketError(aError); + } + } +#if defined (_DEBUG) && defined (_LOGGING) + else + { + __FLOG_4(_T8("!! Connection closed to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + if( iConnectionInfo->IsNonPersistent() && aError == KErrEof ) + { + __FLOG_0(_T8("-> non-persistent connection : expected shutdown")); + } + } +#endif + + // Move to Idle state... + SetIdleState(); + } + + +void CHttpConnectionManager::ReportSocketError(TInt aError) + { + if( iCurrentResponse != NULL ) + { + __FLOG_0(_T8("-> notifying waiting response(s)")); + + TInt error = aError; + + + // If we have a current request/response, cancel them + if( iCurrentRequest ) + { + __FLOG_0(_T8("-> cancelling request")); + iCurrentRequest->CancelRequest(); + } + + if( iCurrentResponse ) + { + __FLOG_0(_T8("-> cancelling response")); + iCurrentResponse->CancelResponse(); + } + + + iCurrentResponse->ConnectionError(error); + + if( iState != EConnecting && iPendingResponses.Count() > 0 ) + { + __FLOG_0(_T8("-> there are pipelined transactions - re-submit without pipelining")); + __FLOG_2(_T8("-> reporting %d (KErrHttpPipeliningError) instead of %d"), KErrHttpPipeliningError, aError); + + // Change reported error as KErrHttpPipeliningError - this + // indicates that those transaction can be re-submitted but + // should not be pipelined. + error = KErrHttpPipeliningError; + + // Insert the host to probable pipeline list + if(iConnectionInfo) + { + __FLOG_1(_T8("-> Insert host: %S to probable pipelined host list"), &iConnectionInfo->Host()); + iPipelineFallback.InsertPipelineFailedHost(iConnectionInfo->Host()); + } + + } + + // Notify any pending (pipelined) transactions and the current + // transaction of the error. + NotifyPendingTransactions(error); + + // The current response and request are now no longer valid. + iCurrentRequest = NULL; + iCurrentResponse = NULL; + } +#if defined (_DEBUG) && defined (_LOGGING) + else + { + __FLOG_0(_T8("-> connection not being used - no waiting transactions")); + } +#endif + } + +void CHttpConnectionManager::ReconnectSocketL() + { + __FLOG_0(_T8("-> Attempting to re-connect to server")); + + // Need to re-connect to the server. Clear the flags + iFlags.Clear(EPendingWriteInConnectedState); + + __FLOG_4( + _T8("Re-connecting to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + // Need to start connection to the appropriate host + iSocketConnector = &iSocketFactory.ConnectL( + *this, + iConnectionInfo->Host(), + iConnectionInfo->Port() + ); + // Move to Connecting state... + iState = EConnecting; + } + +void CHttpConnectionManager::MakeConnectionNonPersistent() + { + // Is the connection already non-persistent? + if( !iConnectionInfo->IsNonPersistent() ) + { + // Nope - change to be non-persistent... + iConnectionInfo->SetPersistentState(CHttpConnectionInfo::ENonPersistent); + + __FLOG_0(_T8("!! Converting to non-persistent connection")); + __FLOG_4( + _T8("-> connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + // Flag that this connection now cannot pipeline - cancel any pending + // trasactions with a non-pipelining error code. This indicates that + // those transaction can be re-submitted and still be pipelined. + iFlags.Set(ECannotPipeline); + NotifyPendingTransactions(KErrHttpPipeliningError); + + iCurrentRequest = NULL; + } + } + +void CHttpConnectionManager::CheckRequestComplete(MHttpRequest& aRequest) + { + // Check to see if the current request matches the request to be stopped. + if( !IsFirstTransaction() && iCurrentRequest == &aRequest) + { + __FLOG_0(_T8("!! Incomplete request")); + __FLOG_0(_T8("-> cancelling request and moving to non-persistent connection")); + + // Ok, this request is not complete - cancel it. + + if (aRequest.CheckRequestPendingComplete()) + { + // Request was done anyway. Just make sure to discard the next confirmation of + // data sent. + iFlags.Set(EDiscardSndDataCnf); + } + else + { + // Change connection to non-persistent - this should take care of any + // pipelined requests. + MakeConnectionNonPersistent(); + } + + if(iCurrentRequest) + { + iCurrentRequest->CancelRequest(); + iCurrentRequest = NULL; + } + } + } + +TInt CHttpConnectionManager::FindRequest(MHttpRequest& aRequest) + { + TInt ii = iPendingRequests.Count(); + + while( ii > 0) + { + if( &aRequest == iPendingRequests[--ii] ) + { + // Found the request - pass the appropriate index value. + return ii; + } + } + + return KErrNotFound; + } + +TInt CHttpConnectionManager::FindResponse(MHttpResponse& aResponse) + { + TInt ii = iPendingResponses.Count(); + + while( ii > 0) + { + if( &aResponse == iPendingResponses[--ii] ) + { + // Found the response - pass the appropriate index value. + return ii; + } + } + + return KErrNotFound; + } + +void CHttpConnectionManager::NotifyPendingTransactions(TInt aError) + { + TInt ii = iPendingResponses.Count(); + + while( ii > 0 ) + { + // Notify the waiting transaction of the error + iPendingResponses[--ii]->ConnectionError(aError); + } + + // Reset the pending request and response queues. + iPendingRequests.Reset(); + iPendingResponses.Reset(); + } + +void CHttpConnectionManager::DisablePipelining() + { + __ASSERT_DEBUG( iState == EIdle || iState == EIdleConnected || iState == EClosing, User::Invariant() ); + __ASSERT_DEBUG( !CannotPipeline(), User::Invariant() ); + + iFlags.Set(ECannotPipeline); + } + +void CHttpConnectionManager::SetIdleState() + { + // The connection is now Idle - reset tunnel and pipeline flags. + iState = EIdle; + iTunnel = EFalse; + iFlags.Clear(ECannotPipeline); + iFlags.Clear(EPendingWriteInConnectedState); + } + +/* + * Methods from MHttpRequestObserver + */ + +void CHttpConnectionManager::SendRequestDataL(const TDesC8& aData) + { + __ASSERT_DEBUG( iState == EConnected, User::Invariant() ); + + iOutputStream->SendDataReqL(aData); + } + +void CHttpConnectionManager::RequestComplete() + { + __ASSERT_DEBUG( iState == EConnected, User::Invariant() ); + + //Start the Receive Timer + StartRecvTimer(); + + // Current request is complete. + if( !IsFirstTransaction() ) + { + iCurrentRequest = NULL; + + if( iPendingRequests.Count() > 0 ) + { + // More requests are pending - start the next one. Remove it from the + // pending queue. + iCurrentRequest = iPendingRequests[0]; + iPendingRequests.Remove(0); + + iCurrentRequest->StartRequest(); + } + } + } + +void CHttpConnectionManager::SendingBodyData(TBool aValue) + { + if(iOutputStream) + { + iOutputStream->SetTCPCorking(aValue); + } + } + +/* + * Methods from MHttpResponseObserver + */ + +void CHttpConnectionManager::ResponseDataParsed() + { + __ASSERT_DEBUG( iState == EConnected, User::Invariant() ); + + if( iCurrentResponse == NULL ) + { + // The current response has finished with the connection - was it a + // non-persistent connection? + if( IsFirstTransaction() ) + { + iFlags.Clear(EFirstTransaction); + iCurrentRequest = NULL; + } + + // More requests are pending - start the next one, + // and remove it from the pending queue. + if ( iPendingRequests.Count() > 0 ) + { + iCurrentRequest = iPendingRequests[0]; + iPendingRequests.Remove(0); + if( iPendingResponses.Count() > 0 ) + { + iCurrentResponse = iPendingResponses[0]; + iPendingResponses.Remove(0); + } + iState = EConnected; + iFlags.Set(EPendingWriteInConnectedState); + iCurrentRequest->StartRequest(); + } + else if( iConnectionInfo->IsNonPersistent() ) + { + __FLOG_0(_T8("!! Non-persistent connection : expect server to close connection")); + __FLOG_4( + _T8("-> moving to Closing state on connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + __ASSERT_DEBUG( iPendingResponses.Count() == 0, User::Invariant() ); + + iFlags.Clear(ECannotPipeline); + iState = EClosing; + } + else + { + // Connection is persistent and no longer waiting for any more + // response data - going to IdleConnected state. Also, allow + // pipelining to occur on this connection. + iState = EIdleConnected; + iFlags.Clear(ECannotPipeline); + } + + //Start the Receive Timer + StartRecvTimer(); + + // Update the state and notify the input stream that the received data + // is no longer needed. + iInputStream->ReceivedDataRes(); + } + else + { + // Check to see if there is any excess data. + if( iExcessData.Length() > 0 ) + { + // Spoof receiving this data. + __FLOG_5(_T8("Received %d bytes of data on connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + iExcessData.Length(), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + // The current transaction should be given the data. Once passed to + // the response, reset the excess data. + iCurrentResponse->ResponseDataReceived(iExcessData); + iExcessData.Set(KNullDesC8()); + } + else + { + //Start the Receive Timer + if(!iCurrentResponse->ResponseInformational()) + { + StartRecvTimer(); + } + // Notify the input stream that the received data is no longer needed + iInputStream->ReceivedDataRes(); + } + } + } + +void CHttpConnectionManager::ResponseComplete(const TDesC8& aExcessData) + { + // Remove the current response. + iCurrentResponse = NULL; + __FLOG_1(_T8("Received %d bytes of excess data"), aExcessData.Length()); + + // Need to store the excess data. + if( iPendingResponses.Count() > 0 ) + { + iExcessData.Set(aExcessData); + + if( !IsFirstTransaction()) + { + // More responses are pending - get the next one. Remove it from the + // pending queue. + iCurrentResponse = iPendingResponses[0]; + iPendingResponses.Remove(0); + } + } + } + +/* + * Methods from MSocketConnectObserver + */ + +void CHttpConnectionManager::ConnectionMadeL(MInputStream& aInputStream, MOutputStream& aOutputStream) + { + // Connector object no longer valid + iSocketConnector = NULL; + + // Bind to the input stream + iInputStream = &aInputStream; + + iOutputStream = &aOutputStream; + + if(iOptimiser) + { + delete iOptimiser; + iOptimiser = NULL; + } + + __FLOG_4( + _T8("Connected to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + TBool batchingEnabled = EFalse; + MHttpDataOptimiser* dataOptimiser = iCurrentRequest->HttpDataOptimiser( batchingEnabled ); + + if (!CannotPipeline()) + { + TInt bufferSize = 0; + bufferSize = iCallback.GetMaxBatchingBufferSize(); + + if (bufferSize > 0) // Therefore, implicitly, batching is supported + { + if (iRequestBatcher) + { + delete iRequestBatcher; + iRequestBatcher = NULL; + } + + if( dataOptimiser && batchingEnabled ) + { + // dataOptimiser has been set for the current session + iOptimiser = CHttpClientOptimiser::NewL(*iOutputStream, *this); + iOptimiser->BindOptimiser(*dataOptimiser); + iRequestBatcher = CHttpRequestBatcher::NewL(*iOptimiser, bufferSize); + __FLOG_0(_T8("-> Created request batcher")); + __FLOG_0(_T8("-> HTTP Data Optimiser has been set")); + iInputStream->Bind(*iOptimiser); + } + + else + { + iRequestBatcher = CHttpRequestBatcher::NewL(*iOutputStream, bufferSize); + __FLOG_0(_T8("-> Created request batcher")); + iOutputStream->Bind(*iRequestBatcher); + iInputStream->Bind(*this); + } + iRequestBatcher->Bind(*this); + iOutputStream = iRequestBatcher; + } + else + { + // We are pipelining but not batching + __FLOG_0(_T8("-> Did not create request batcher as batching disabled")); + + CreateOptimiserL( dataOptimiser ); + } + } + else + { + // We are not pipelining + __FLOG_0(_T8("-> Did not create request batcher as pipelining disabled")); + + CreateOptimiserL( dataOptimiser ); + } + + if( iConnectionInfo->IsSecure() ) + { + // Upgrade the connection to be secure. + UpgradeConnectionL(); + } + else + { + // Move to Connected state and notify the current request to start. + iState = EConnected; + iCurrentRequest->StartRequest(); + } + } + +TInt CHttpConnectionManager::HandleConnectError(TInt aError) + { + // Ok, the connector object is now invalid + iSocketConnector = NULL; + + // Handle the error... + HandleSocketError(aError); + + __ASSERT_DEBUG( iState == EIdle, User::Invariant() ); + + return KErrNone; + } + +void CHttpConnectionManager::MSocketConnectObserver_Reserved() + { + User::Invariant(); + } + +/* + * Methods from MInputStreamObserver + */ + +void CHttpConnectionManager::ReceivedDataIndL(const TDesC8& aBuffer) + { + if( iCurrentResponse != NULL ) + { + __FLOG_5(_T8("Received %d bytes of data on connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + aBuffer.Length(), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + // The current transaction should be given the data. + iCurrentResponse->ResponseDataReceived(aBuffer); + } + else + { + __FLOG_1(_T8("!! Spurious data : %d bytes of data ignored"), aBuffer.Length()); + __FLOG_4( + _T8("-> data received on connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + + //Start the Receive Timer + StartRecvTimer(); + + // This data does not belong to any transaction - ignore it. + iInputStream->ReceivedDataRes(); + } + } + +void CHttpConnectionManager::SecureServerCnf() + { + User::Invariant(); + } + +void CHttpConnectionManager::InputStreamCloseInd(TInt aError) + { + // The input stream is no longer of any use as the connection has closed. + iInputStream = NULL; + + // Handle the error only if the output stream observer has not done so + // already. + if( iOutputStream != NULL ) + { + HandleSocketError(aError); + } + } + +void CHttpConnectionManager::MInputStreamObserver_Reserved() + { + User::Invariant(); + } + +/* + * Methods from MOutputStreamObserver + */ + +void CHttpConnectionManager::SendDataCnfL() + { + if (DiscardSndDataCnf()) + { + __FLOG_0(_T8("-> Disgarding Confirmation that data was sent")); + iFlags.Clear(EDiscardSndDataCnf); + if (iState == EConnected) + { + RequestComplete(); + } + else + { + __FLOG_0(_T8("-> Initialising iCurrentRequest")); + iCurrentRequest=NULL; + } + } + // Notify the current request that its data has been sent. + else if( iCurrentRequest) + { + iCurrentRequest->RequestDataSent(); + + // The request has now sent data - reset the pending write flag. + iFlags.Clear(EPendingWriteInConnectedState); + } + } + +void CHttpConnectionManager::SecureClientCnf() + { + __ASSERT_DEBUG( iState == EUpgrading, User::Invariant() ); + +#if defined (_DEBUG) && defined (_LOGGING) + __FLOG_0(_T8("!! Secure connection establised")); + + if( iTunnel ) + { + __FLOG_5( + _T8("-> secure tunnel to %S via host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iTunnelHost.DesC(), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + } + else + { + __FLOG_4( + _T8("-> secure connection to host %S, remote port %d (secure : %d, nonpersistent : %d)"), + &iConnectionInfo->Host(), + iConnectionInfo->Port(), + iConnectionInfo->IsSecure(), + iConnectionInfo->IsNonPersistent() + ); + } +#endif + + // Move to Connected state and notify the current request to start. + iState = EConnected; + iCurrentRequest->StartRequest(); + } + +void CHttpConnectionManager::OutputStreamCloseInd(TInt aError) + { + // The output stream is no longer of any use as the connection has closed. + iOutputStream = NULL; + + // Handle the error only if the input stream observer has not done so + // already. + if( iInputStream != NULL ) + { + HandleSocketError(aError); + } + } + +void CHttpConnectionManager::DoResponseCompletion () + { + __ASSERT_DEBUG( iCurrentResponse != NULL, User::Invariant() ); + // We do these operations only for 3xx responses and the response parsing + // hasn't happened completely + if ( iCurrentResponse->NeedCompletion() && !iCurrentResponse->ResponseCompleted () ) + { + // Try to complete the response. Parser may have the data but not processed. + TBool responseCompleted = iCurrentResponse->CompleteResponse ( KNullDesC8() ); + while ( !responseCompleted ) + { + // Read and try complete the response + TPtrC8 data; + __FLOG_0(_T8("!! doing an immediate socket read")); + TInt ret = iInputStream->ImmediateRead ( data ); + if ( ( iPendingResponses.Count() == 0 ) || ret <= KErrNone ) + { + __FLOG_0(_T8("!! Breaking from the loop")); + // no further data is expected or there is a socket error + break; + } + responseCompleted = iCurrentResponse->CompleteResponse ( data ); + } + } + } + +void CHttpConnectionManager::MOutputStreamObserver_Reserved() + { + User::Invariant(); + } + +MHttpResponse* CHttpConnectionManager::CurrentResponse() + { + return iCurrentResponse; + } + +void CHttpConnectionManager::CreateOptimiserL(MHttpDataOptimiser* aDataOptimiser) + { + if( aDataOptimiser ) + { + // The dataOptimiser has been set, it does not matter if it is for session or transaction + iOptimiser = CHttpClientOptimiser::NewL(*iOutputStream, *this); + iOptimiser->BindOptimiser(*aDataOptimiser); + __FLOG_0(_T8("-> HTTP Data Optimiser has been set")); + iInputStream->Bind(*iOptimiser); + iOutputStream = iOptimiser; + iOutputStream->Bind(*this); + } + + else + { + iInputStream->Bind(*this); + iOutputStream->Bind(*this); + } + } + +void CHttpConnectionManager::OnReceiveTimeOut() + { + if(iCurrentResponse) + { + iCurrentResponse->OnResponseReceiveTimeOut(); + } + } + +void CHttpConnectionManager::OnSendTimeOut() + { + if(iCurrentRequest) + { + iCurrentRequest->OnRequestSendTimeOut(); + } + } + +TInt CHttpConnectionManager::SendTimeOutVal() + { + if(iCurrentRequest) + { + return iCurrentRequest->SendTimeOutValue(); + } + return 0; + } + +void CHttpConnectionManager::StartRecvTimer() + { + if(iCurrentResponse) + { + TInt timeoutValue = iCurrentResponse->ReceiveTimeOutValue(); + if(iInputStream) + { + iInputStream->StartReceieveTimer(timeoutValue); + } + } + } + +void CHttpConnectionManager::InsertPipelineFailedHost(const TDesC8& aHost) + { + iPipelineFallback.InsertPipelineFailedHost(aHost); + } + + +CHttpHostElement* CHttpHostElement::New(const TDesC8& aHost) + { + CHttpHostElement* self = new CHttpHostElement; + if(self && self->Construct(aHost)) + { + return self; + } + delete self; + return NULL; + } + +CHttpHostElement::~CHttpHostElement() + { + delete iHost; + } + +TBool CHttpHostElement::Construct(const TDesC8& aHost) + { + iHost = aHost.Alloc(); + return (iHost != NULL); + } + +CHttpPipelineFallback::CHttpPipelineFallback() +: iPipelineFailedHosts(16), // With granularity 16 +iProbablePipelineFailedHosts(CHttpHostElement::LinkOffset()) + { + + } + +CHttpPipelineFallback* CHttpPipelineFallback::NewL() + { + return new(ELeave)CHttpPipelineFallback; + } + +CHttpPipelineFallback::~CHttpPipelineFallback() + { + iPipelineFailedHosts.ResetAndDestroy(); + // Free up the objects pointed by the list + CHttpHostElement* element; + TSglQueIter it(iProbablePipelineFailedHosts); + while((element = it++) != NULL) + { + iProbablePipelineFailedHosts.Remove(*element); + delete element; + } + } + +TBool CHttpPipelineFallback::NeedPipelineFallback(const TDesC8& aHost) + { + TInt count = iPipelineFailedHosts.Count(); + for(TInt i = count - 1; i >= 0; --i) + { + if(aHost.CompareF(*(iPipelineFailedHosts[i])) == 0) + return ETrue; + } + return EFalse; + } + +void CHttpPipelineFallback::InsertPipelineFailedHost(const TDesC8& aHost) + { + // Already failed. no need to check further. + if(NeedPipelineFallback(aHost)) + { + return; + } + CHttpHostElement* element = NULL; + TSglQueIter it(iProbablePipelineFailedHosts); + while((element = it++) != NULL) + { + if(element->Host().CompareF(aHost) == 0) + { + // Remove from the list and push into the pipeline failed array + iProbablePipelineFailedHosts.Remove(*element); + iPipelineFailedHosts.Append(element->AcquireHost()); // No need to check the return code + // as, a failure does not matter here. + delete element; // Delete the host element. + return; + } + } + // A new host has failed on the pipelining. Add into the list + element = CHttpHostElement::New(aHost); + if(element) + { + iProbablePipelineFailedHosts.AddLast(*element); + } + + } + + + + + +