applayerprotocols/httptransportfw/Test/t_utils/T_DummyServer.cpp
changeset 0 b16258d2340f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/applayerprotocols/httptransportfw/Test/t_utils/T_DummyServer.cpp	Tue Feb 02 01:09:52 2010 +0200
@@ -0,0 +1,471 @@
+// Copyright (c) 2001-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:
+// defines test classes: CDummyServer
+// 
+//
+
+#include "T_DummyServer.h"
+#include <uri8.h>
+
+
+// Constants used in CDummyServer test
+#ifdef _DEBUG
+	_LIT(KDummyServerPanic, "DUMMYHTTPSVR");
+#endif
+
+const TInt KRecvBufSize = 128;
+const TInt KChunkSize = 10240;
+const TInt KListenQSize = 1;
+_LIT(KTcpProtName, "tcp");
+_LIT(KFileSystemRoot,"Z:\\");
+_LIT8(KCrLf, "\r\n");
+
+
+// Canned responses for each error situation we report
+_LIT8(KErrPageMissingQuery,		"HTTP/1.1 404 Query Not Found\r\nDate: %S\r\nServer: T_DummyServer(EPOC32/6.2)\r\nContent-Type: text/html\r\nContent-Length: 299\r\nConnection: close\r\n\r\n<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n<HTML><HEAD><TITLE>Query Not Found</TITLE></HEAD><BODY><H1>Query Not Found</H1>The requested URL did not include a query portion for validation of request and response.<P><HR><ADDRESS>T_DummyServer at 127.0.0.1 on port %d</ADDRESS></BODY></HTML>");
+_LIT8(KErrRequestFileNotFound,	"HTTP/1.1 404 Request Content File Not Found\r\nDate: %S\r\nServer: T_DummyServer(EPOC32/6.2)\r\nContent-Type: text/html\r\nContent-Length: 330\r\nConnection: close\r\n\r\n<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n<HTML><HEAD><TITLE>Request Content File Not Found</TITLE></HEAD><BODY><H1>Request Content File Not Found</H1>The request filename specified in the URL query portion could not be found.<P><HR><ADDRESS>T_DummyServer at 127.0.0.1 on port %d</ADDRESS></BODY></HTML>");
+_LIT8(KErrResponseFileNotFound, "HTTP/1.1 404 Response Content File Not Found\r\nDate: %S\r\nServer: T_DummyServer(EPOC32/6.2)\r\nContent-Type: text/html\r\nContent-Length: 334\r\nConnection: close\r\n\r\n<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n<HTML><HEAD><TITLE>Response Content File Not Found</TITLE></HEAD><BODY><H1>Response Content File Not Found</H1>The response filename specified in the URL query portion could not be found.<P><HR><ADDRESS>T_DummyServer at 127.0.0.1 on port %d</ADDRESS></BODY></HTML>");
+_LIT8(KErrRequestMismatch,		"HTTP/1.1 400 Invalid Request\r\nDate: %S\r\nServer: T_DummyServer(EPOC32/6.2)\r\nContent-Type: text/html\r\nContent-Length: 337\r\nConnection: close\r\n\r\n<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n<HTML><HEAD><TITLE>Invalid Request</TITLE></HEAD><BODY><H1>Invalid Request</H1>The request differed from that in the validation file.  First difference at byte %3d.  Expected [%S], Received [%S]<P><HR><ADDRESS>T_DummyServer at 127.0.0.1 on port %d</ADDRESS></BODY></HTML>");
+
+
+// format for output of data/time values
+// e.g. Wed, 21 Mar 2001 15:29:19 GMT
+_LIT(KDateFormat,"%F%*E, %D %*N %Y %H%:1%T%:2%S GMT");
+
+
+// CDummyServer-----------------------------------
+
+EXPORT_C
+CDummyServer* CDummyServer::NewL(TInt aPort, TInt aRespChunkSize)
+	{
+	CDummyServer* me = new(ELeave)CDummyServer(aPort, aRespChunkSize, 1);
+	CleanupStack::PushL(me);
+	me->ConstructL();
+	CleanupStack::Pop();
+	return me;
+	}
+
+EXPORT_C
+CDummyServer::~CDummyServer()
+	{
+	// Cancel any outstanding request and remove from active scheduler
+	Cancel();
+	if (IsAdded())
+		Deque();
+
+	// Close handles - individual files and sockets first, then server connections
+	if (iListSockOpen)
+		iListenSockHnd.Close();
+	if (iAccSockOpen)
+		iAcceptSockHnd.Close();
+	if (iReqFileOpen)
+		iRequestFile.Close();
+	if (iRespFileOpen)
+		iResponseFile.Close();
+
+	if (iSockSrvOpen)
+		iSockSrvHnd.Close();
+	if (iFileSrvOpen)
+		iFileSrvHnd.Close();
+
+	// Free memory
+	delete iRecvBuffer;
+	delete iErrorPageData;
+	delete iResponseData;
+	}
+
+EXPORT_C
+void CDummyServer::ListenL()
+	{
+	// Start listening for incoming connections
+	TProtocolDesc desc;
+	User::LeaveIfError(iSockSrvHnd.FindProtocol(KTcpProtName(), desc));
+	iListenSockHnd.Open(iSockSrvHnd, desc.iAddrFamily, desc.iSockType, desc.iProtocol);
+	iListSockOpen = ETrue;
+	TSockAddr addr;
+	addr.SetPort(iPort);
+	iListenSockHnd.Bind(addr);
+	iListenSockHnd.Listen(KListenQSize);
+
+	// Add to the scheduler and set to 'listening' state
+	CActiveScheduler::Add(this);
+	iState = EInitialising;
+
+	// Now go active, ready to accept connections
+	CompleteSelf();
+	}
+
+CDummyServer::CDummyServer(TInt aPort, TInt aRespChunkSize, TInt aSleepPeriodSec):
+	CActive(EPriorityStandard),
+	iPort(aPort),iSleepPeriodSec(aSleepPeriodSec), iRecvBuff_ptr(NULL, 0), iErrorPage_ptr(NULL, 0), iResponse_ptr(NULL, 0),
+	iRespChunkSize(aRespChunkSize) 
+	{
+	// Set the package containing a TCP frame size, based on the desired response chunk size
+	iFrameSize = iRespChunkSize;
+	}
+
+void CDummyServer::ConstructL()
+	{
+	// Make a receive buffer, to hold the data read from the accepting socket
+	iRecvBuffer = HBufC8::NewL(KRecvBufSize);
+	iRecvBuff_ptr.Set(iRecvBuffer->Des());
+	
+	// Make a buffer in which error response pages are assembled before returning to the client
+	iErrorPageData = HBufC8::NewL(KChunkSize);
+	iErrorPage_ptr.Set(iErrorPageData->Des());
+
+	// Make a buffer in which the response chunks are read from file before returning to the client
+	iResponseData = HBufC8::NewL(iRespChunkSize);
+	iResponse_ptr.Set(iResponseData->Des());
+
+	// Make socket server and file server connections
+	User::LeaveIfError(iSockSrvHnd.Connect());
+	iSockSrvOpen = ETrue;
+	User::LeaveIfError(iFileSrvHnd.Connect());
+	iFileSrvOpen = ETrue;
+	}
+
+EXPORT_C
+void CDummyServer::RunL()
+	{
+	switch (iState)
+		{
+	case EInitialising:
+		{
+		// Open the blank accepting socket and await an incoming connection
+		iAcceptSockHnd.Open(iSockSrvHnd);
+		iAccSockOpen = ETrue;
+		iState = EListening;
+		CompleteSelf();
+		} break;
+	case EListening:
+		{
+		iListenSockHnd.Accept(iAcceptSockHnd, iStatus);
+		iState = EAccepted;
+		SetActive();
+		} break;
+
+	case EAccepted:
+		{
+		// Incoming connection is bound - now read from it to get the first part of the client request
+		iAcceptSockHnd.Read(iRecvBuff_ptr, iStatus);
+		iState = EProcessingRequest;
+		SetActive();
+		} break;
+
+	case EProcessingRequest:
+		{
+		// Determine whether there is more data to come, or if we're at the end of the request
+		if (iStatus.Int() == KErrNone)
+			{
+			// Locate the request line
+			TPtrC8 reqLine;
+			TInt crLfPos = iRecvBuffer->Find(KCrLf());
+			__ASSERT_DEBUG(crLfPos > 0, User::Panic(KDummyServerPanic(), KErrGeneral));
+			reqLine.Set(iRecvBuffer->Left(crLfPos - 1));
+
+			// Within the request line, locate the URI. It must follow a space and have a space afterwards
+			TInt spPos1 = reqLine.Locate(TChar(' '));
+			TPtrC8 uri;
+			uri.Set(reqLine.Mid(spPos1 + 1));
+			TInt spPos2 = uri.Locate(TChar(' '));
+			uri.Set(uri.Left(spPos2));
+
+			// Form a URI based on this descriptor
+			TUriParser8 parse;
+			parse.Parse(uri);
+			if (parse.IsPresent(EUriQuery))
+				{
+				TPtrC8 query = parse.Extract(EUriQuery);
+
+				// Extract the request file
+				TInt eqPos1 = query.Locate(TChar('='));
+				TInt ampPos = query.Locate(TChar('&'));
+				if ((eqPos1 == KErrNotFound) || (ampPos == KErrNotFound))
+					User::Leave(EInvalidQuery);
+				TPtrC8 reqFile8 = query.Mid(eqPos1 + 1, ampPos - eqPos1 - 1);
+				TPtr reqFile16_ptr = iReqFileName.Des();
+				reqFile16_ptr.Copy(reqFile8);
+
+				// Extract the response file
+				query.Set(query.Mid(ampPos + 1));
+				TInt eqPos2 = query.Locate(TChar('='));
+				if (eqPos2 == KErrNotFound)
+					User::Leave(EInvalidQuery);
+				TPtrC8 respFile8  = query.Mid(eqPos2 + 1);
+				TPtr respFile16_ptr = iRespFileName.Des();
+				respFile16_ptr.Copy(respFile8);
+
+				// Got the file names - attempt to open both files
+				iFileNameParser.Set(KFileSystemRoot,&iReqFileName,NULL);
+				if (iRequestFile.Open(iFileSrvHnd, iFileNameParser.FullName(), EFileRead) != KErrNone)
+					User::Leave(ERequestFileNotFound);
+				iFileNameParser.Set(KFileSystemRoot,&iRespFileName,NULL);
+				if (iResponseFile.Open(iFileSrvHnd, iFileNameParser.FullName(), EFileRead) != KErrNone)
+					User::Leave(EResponseFileNotFound);
+
+				// Copy this chunk, minus the request line (which will be wrong due to the query portion) into the request buffer,
+				// then copy back (easiest way of chopping off the request line)
+				iErrorPage_ptr.Append(iRecvBuffer->Mid(crLfPos + 2));
+				iRecvBuff_ptr.Copy(iErrorPage_ptr);
+				iFirstDiffPos = 0;
+
+				// Validate what's left from the first read
+				TBool hitEndOfSocketData, hitEndOfFileData;
+				iCharBuffer.Zero();
+				if (!ValidateRequest(hitEndOfSocketData, hitEndOfFileData))
+					User::Leave(ERequestMismatch);
+
+				// Ready to receive more request data and to validate it
+				iAcceptSockHnd.Read(iRecvBuff_ptr, iStatus);
+				iState = EReadingRequest;
+				SetActive();
+				}
+			else
+				User::Leave(EInvalidQuery);
+			}
+		else if (iStatus.Int() == KErrEof)
+			{
+			__DEBUGGER();
+			}
+		} break;
+
+	case EReadingRequest:
+		{
+		// Provided the socket read completed OK, validate what we got
+		TBool hitEndOfSocketData, hitEndOfFileData;
+		TBool valid = ValidateRequest(hitEndOfSocketData, hitEndOfFileData);
+
+		// If the data isn't valid, or either of the socket stream or the validation file has reached EOF but not the other, then
+		// there's a mismatch
+		if ((!valid) || (hitEndOfSocketData != hitEndOfFileData))
+			User::Leave(ERequestMismatch);
+		if (valid)
+			{
+			if (hitEndOfFileData) // or hitEndOfSocketData, they should be the same by here
+				{
+				// Reached end of both the request data and the validation file - there is a match. Send the required response.
+				iState = ESendingBody;
+				CompleteSelf();
+				}
+			else
+				// Provided there's more data available, read from the socket and don't change state
+				{
+				iAcceptSockHnd.RecvOneOrMore(iRecvBuff_ptr, 0, iStatus, iXferLength);
+				SetActive();
+				}
+			}
+		} break;
+
+	case ESendingBody:
+		{
+		// Sleep for a while to allow the client's read operation to complete as soon as possible - in an
+		// attempt to defeat the TCP frame reassembly/segmentation that goes on in the TCP/IP stack
+		//User::After(iSleepPeriodSec*1000000);
+
+		// Notify TCP/IP of the data last sent, immediately, in an attempt to get over the frame reassembly
+		// that appears to go on in the server.
+		TRequestStatus istat;
+		iAcceptSockHnd.Ioctl(KIoctlTcpNotifyDataSent, istat, 0, KSolInetTcp);
+		User::WaitForRequest(istat);
+
+		// Re-use the request buffer to send the validation body back to the client
+		iResponse_ptr.Zero();
+		TInt err = iResponseFile.Read(iResponse_ptr);
+		if (!err && (iResponse_ptr.Length() > 0))
+			{
+			// copy data from file to the socket and stay in the same state
+			iXferLength = iRespChunkSize;;
+			iAcceptSockHnd.Send(iResponse_ptr, 0, iStatus, iXferLength);
+			SetActive();
+			}
+		else
+			{
+			iState = EIdleShuttingDown;
+			CompleteSelf();
+			}
+		} break;
+
+	case ECompleteError:
+		{
+		// Get the date now
+		TBuf<40> dateTimeString;
+		TTime t;
+		t.UniversalTime();
+		t.FormatL(dateTimeString,KDateFormat);
+
+		// Write an error message back to the client, according to the error state. Re-use the request buffer to
+		// formulate the message.
+		switch (iError)
+			{
+		case EInvalidQuery:
+			{
+			iErrorPage_ptr.Format(KErrPageMissingQuery(), &dateTimeString, iPort);
+			} break;
+		case ERequestFileNotFound:
+			{
+			iErrorPage_ptr.Format(KErrRequestFileNotFound(), &dateTimeString, iPort);
+			} break;
+		case EResponseFileNotFound:
+			{
+			iErrorPage_ptr.Format(KErrResponseFileNotFound(), &dateTimeString, iPort);
+			} break;
+		case ERequestMismatch:
+			{
+			//  to send an error page containing useful information
+			// about where the request differed
+			iErrorPage_ptr.Format(KErrRequestMismatch(), &dateTimeString, iFirstDiffPos, &iActualReqSlice, &iExpectedReqSlice, iPort);
+			} break;
+		default:
+			;
+			}
+		iAcceptSockHnd.Write(iErrorPage_ptr, iStatus);
+		iState = EIdleShuttingDown;
+		SetActive();
+		} break;
+
+	case EIdleShuttingDown:
+		{
+		iAcceptSockHnd.Shutdown(RSocket::ENormal, iStatus);
+		iState = EIdleDisconnected;
+		SetActive();
+		} break;
+
+	case EIdleDisconnected:
+		{
+		} break;
+		}
+	}
+	
+EXPORT_C
+TInt CDummyServer::RunError(TInt aErr)
+	{
+	switch (iState)
+		{
+	case EListening:
+		{
+		} break;
+
+	case EAccepted:
+		{
+		} break;
+
+	case EProcessingRequest:
+		{
+		if ((aErr == EInvalidQuery) ||
+			(aErr == ERequestFileNotFound) ||
+			(aErr == EResponseFileNotFound) ||
+			(aErr == ERequestMismatch))
+			{
+			iError = (TError)aErr;
+			iState = ECompleteError;
+			CompleteSelf();
+			}
+		} break;
+
+	case EReadingRequest:
+		{
+		if (aErr == ERequestMismatch)
+			{
+			iError = (TError)aErr;
+			iState = ECompleteError;
+			CompleteSelf();
+			}
+		} break;
+
+	case ESendingBody:
+		{
+		} break;
+
+	case ECompleteError:
+		{
+		} break;
+
+	case EIdleShuttingDown:
+		{
+		} break;
+
+	case EIdleDisconnected:
+		{
+		} break;
+	default:
+		break;
+		}
+	return KErrNone;
+	}
+
+EXPORT_C
+void CDummyServer::DoCancel()
+	{
+	}
+
+void CDummyServer::CompleteSelf()
+	{
+	SetActive();
+	TRequestStatus* pStat = &iStatus;
+	User::RequestComplete(pStat, KErrNone);
+	}
+
+TBool CDummyServer::ValidateRequest(TBool& aHitEndOfFileData, TBool& aHitEndOfSocketData)
+	{
+	aHitEndOfFileData = EFalse;
+	aHitEndOfSocketData = EFalse;
+
+	// Validate the receive buffer content against data read byte by byte from the request file
+	// (slow, but we need an actual byte index for where the two start to differ)
+	TInt bufferSize = iRecvBuff_ptr.Length();
+
+	// Read first char from file in advance - if we've already at the end we don't want to enter the loop
+	// Don't do this if the single char buffer is already populated since that is a carryover from the
+	// previous call to this method
+	if (iCharBuffer.Length() == 0)
+		{
+		(void) iRequestFile.Read(iCharBuffer);
+		aHitEndOfFileData = (iCharBuffer.Length() == 0);
+		}
+
+	// Provided we're not already at EOF, then check the receieve buffer against the file
+	for (TInt ii = 0; ((ii < bufferSize) && !aHitEndOfFileData); ++ii)
+		{
+		++iFirstDiffPos;
+		TChar socketCh = iRecvBuff_ptr[ii];
+		TChar fileCh = iCharBuffer[0];
+		if (socketCh != fileCh)
+			{
+			iActualReqSlice.Copy(iRecvBuff_ptr.Mid(ii, 8));
+			iExpectedReqSlice.Append(fileCh);
+			TBuf8<7> expBuffCh1To7;
+			iRequestFile.Read(expBuffCh1To7);
+			iExpectedReqSlice.Append(expBuffCh1To7);
+			return EFalse;
+			}
+
+		// Read next char, to detect whether we're at EOF yet
+		iCharBuffer.Zero();
+		(void) iRequestFile.Read(iCharBuffer);
+		aHitEndOfFileData = (iCharBuffer.Length() == 0);
+		}
+
+	// OK so far -  Check for EOF/no more data on socket (client closed it) by peeking for one char
+	// NB This code doesn't work - it hangs when no more data is available.  Frig for now so that
+	// both flags get the same value
+	aHitEndOfSocketData = aHitEndOfFileData;
+	/*
+	TRequestStatus peekStat;
+	TBuf8<1> sockChBuffer;
+	iAcceptSockHnd.RecvOneOrMore(sockChBuffer, KSockReadPeek, peekStat, iXferLength);
+	User::WaitForRequest(peekStat);
+	aHitEndOfSocketData = (peekStat.Int() != KErrNone); */	
+	return ETrue;
+	}