--- /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;
+ }