/*++ Copyright (c) 1999-1999 Microsoft Corporation Module Name: tover.c Abstract: Minimal test server to test UL<-->WP overhead Heavily copied from tperf.c Author: Keith Moore (keithmo) 13-Sep-1999 Revision History: --*/ #include "precomp.h" // // Private constants. // #define PENDING_IO_COUNT 8 // // Private types. // typedef ULONG (* PCOMPLETION_ROUTINE)( IN struct _MY_OVERLAPPED *pMyOverlapped, IN ULONG ErrorCode, IN ULONG BytesTransferred ); typedef struct _GLOBALS { HANDLE ControlChannel; HANDLE AppPool; HTTP_CONFIG_GROUP_ID ConfigGroup; BOOL SendRawResponse; BOOL EnableLogging; BYTE * FileBuffer; DWORD FileSize; HTTP_DATA_CHUNK FileContentChunk; HTTP_RESPONSE StaticResponse; HTTP_DATA_CHUNK StaticRawResponse; HTTP_LOG_FIELDS_DATA LogFields; HANDLE CompletionPort; BOOL DoShutdown; } GLOBALS, *PGLOBALS; typedef struct _MY_OVERLAPPED { OVERLAPPED Overlapped; struct _IO_CONTEXT *pIoContext; PCOMPLETION_ROUTINE pCompletionRoutine; } MY_OVERLAPPED, *PMY_OVERLAPPED; typedef struct _IO_CONTEXT { MY_OVERLAPPED Read; MY_OVERLAPPED Send; PHTTP_REQUEST pRequestBuffer; DWORD RequestBufferLength; UCHAR StaticRequestBuffer[REQUEST_LENGTH]; } IO_CONTEXT, *PIO_CONTEXT; // // Private prototypes. // ULONG LocalInitialize( DWORD NumberOfThreads, WCHAR * FilePath, BOOL EnableLogging, BOOL DoRawResponse ); VOID LocalTerminate( IN BOOLEAN TerminateUl ); PIO_CONTEXT AllocateIoContext( VOID ); VOID InitializeIoContext( IN PIO_CONTEXT pIoContext ); VOID FreeIoContext( IN PIO_CONTEXT pIoContext ); VOID CleanupIoContext( IN PIO_CONTEXT pIoContext ); ULONG GrowRequestBuffer( IN PIO_CONTEXT pIoContext, IN ULONG RequestBufferLength ); ULONG ReadRequest( IN PIO_CONTEXT pIoContext, IN HTTP_REQUEST_ID RequestId ); ULONG ReadRequestComplete( IN PMY_OVERLAPPED pOverlapped, IN ULONG ErrorCode, IN ULONG BytesTransferred ); ULONG SendResponse( IN PIO_CONTEXT pIoContext ); ULONG SendResponseComplete( IN PMY_OVERLAPPED pOverlapped, IN ULONG ErrorCode, IN ULONG BytesTransferred ); DWORD CALLBACK WorkerThreadRoutine( PVOID Arg ); // // Private globals. // DEFINE_COMMON_GLOBALS(); GLOBALS Globals; INT __cdecl wmain( INT argc, PWSTR argv[] ) { ULONG result; ULONG i; PIO_CONTEXT pIoContext; DWORD NumberOfThreads; WCHAR * FilePath; BOOL EnableLogging; BOOL DoRawResponse; DWORD PendingIoCount; // // Initialize. // result = CommonInit(); if (result != NO_ERROR) { wprintf( L"CommonInit() failed, error %lu\n", result ); return 1; } // // Parse command line // if ( argc != 6 ) { wprintf( L"Usage: tover.exe \n" ); return 1; } NumberOfThreads = _wtoi( argv[ 1 ] ); FilePath = argv[ 2 ]; EnableLogging = _wtoi( argv[ 3 ] ) ? TRUE : FALSE; DoRawResponse = _wtoi( argv[ 4 ] ) ? TRUE : FALSE; PendingIoCount = _wtoi( argv[ 5 ] ); result = LocalInitialize( NumberOfThreads, FilePath, EnableLogging, DoRawResponse ); if (result != NO_ERROR) { wprintf( L"LocalInitialize() failed, error %lu\n", result ); return 1; } // // Fire off the initial I/O requests. // for (i = 0 ; i < PendingIoCount ; i++) { pIoContext = AllocateIoContext(); if (pIoContext == NULL) { wprintf( L"Cannot allocate IO_CONTEXT\n" ); return 1; } result = ReadRequest( pIoContext, HTTP_NULL_ID ); if (result != NO_ERROR && result != ERROR_IO_PENDING) { wprintf( L"ReadRequest() failed, error %lu\n", result ); return 1; } } // // Wait forever... // Sleep( INFINITE ); // // BUGBUG: Need some way to get past the sleep... // LocalTerminate( TRUE ); return 0; } // wmain // // Private functions. // ULONG LocalInitialize( DWORD NumberOfThreads, WCHAR * FilePath, BOOL EnableLogging, BOOL DoRawResponse ) { ULONG result; ULONG i; HTTP_CONFIG_GROUP_APP_POOL configAppPool; HTTP_CONFIG_GROUP_STATE configState; HTTP_ENABLED_STATE controlState; BOOLEAN initDone; SECURITY_ATTRIBUTES securityAttributes; HANDLE FileHandle = INVALID_HANDLE_VALUE; HANDLE Thread; DWORD BytesRead; DWORD RawSize; CHAR * RawBuffer; DWORD RawResponseSize; // // Setup globals & locals so we know how to cleanup on exit. // Globals.ControlChannel = NULL; Globals.AppPool = NULL; Globals.ConfigGroup = HTTP_NULL_ID; initDone = FALSE; // // Get UL started. // result = InitUlStuff( &Globals.ControlChannel, &Globals.AppPool, NULL, // FilterChannel &Globals.ConfigGroup, TRUE, // AllowSystem TRUE, // AllowAdmin FALSE, // AllowCurrentUser FALSE, // AllowWorld HTTP_OPTION_OVERLAPPED, FALSE, // EnableSsl FALSE // EnableRawFilters ); if (result != NO_ERROR) { wprintf( L"InitUlStuff() failed, error %lu\n", result ); goto fatal; } initDone = TRUE; // // Open the file and generate the response // FileHandle = CreateFileW( FilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if ( FileHandle == INVALID_HANDLE_VALUE ) { result = GetLastError(); wprintf( L"Could not open content file %ws because of %d\n", FilePath, result ); goto fatal; } Globals.FileSize = GetFileSize( FileHandle, NULL ); if ( Globals.FileSize == 0 ) { result = ERROR_INVALID_DATA; wprintf( L"File has not content\n" ); goto fatal; } Globals.FileBuffer = LocalAlloc( LPTR, Globals.FileSize + 1 ); if ( Globals.FileBuffer == NULL ) { result = GetLastError(); wprintf( L"Could not allocate content buffer\n" ); goto fatal; } if ( !ReadFile( FileHandle, Globals.FileBuffer, Globals.FileSize, &BytesRead, NULL ) ) { result = GetLastError(); wprintf( L"Could not read file content\n" ); goto fatal; } CloseHandle( FileHandle ); FileHandle = INVALID_HANDLE_VALUE; // // Generate the appropriate response // if ( DoRawResponse ) { // // Generate the entire raw response // RawSize = Globals.FileSize + 512; RawBuffer = LocalAlloc( LPTR, RawSize ); if ( RawBuffer == NULL ) { result = GetLastError(); wprintf( L"Could not allocate raw response buffer\n" ); goto fatal; } wsprintfA( RawBuffer, "HTTP/1.1 200 OK\r\n" "Server: Microsoft-IIS/6.0\r\n" "Date: Thu, 30 Nov 2000 01:01:07 GMT\r\n" "Content-Length: %d\r\n\r\n", Globals.FileSize ); RawResponseSize = strlen( RawBuffer ); memcpy( RawBuffer + RawResponseSize, Globals.FileBuffer, Globals.FileSize ); Globals.StaticRawResponse.DataChunkType = HttpDataChunkFromMemory; Globals.StaticRawResponse.FromMemory.pBuffer = RawBuffer; Globals.StaticRawResponse.FromMemory.BufferLength = RawResponseSize + Globals.FileSize; LocalFree( Globals.FileBuffer ); Globals.FileBuffer = RawBuffer; Globals.SendRawResponse = TRUE; } else { // // Create an HTTP_RESPONSE // Globals.FileContentChunk.DataChunkType = HttpDataChunkFromMemory; Globals.FileContentChunk.FromMemory.pBuffer = Globals.FileBuffer; Globals.FileContentChunk.FromMemory.BufferLength = Globals.FileSize; Globals.StaticResponse.Flags = 0; Globals.StaticResponse.StatusCode = 200; Globals.StaticResponse.pReason = "OK"; Globals.StaticResponse.ReasonLength = 2; Globals.StaticResponse.Headers.UnknownHeaderCount = 0; Globals.StaticResponse.EntityChunkCount = 1; Globals.StaticResponse.pEntityChunks = &Globals.FileContentChunk; } // // Are we logging or not? // if ( EnableLogging ) { // // Generate a logging record // Globals.LogFields.UserNameLength = 0; Globals.LogFields.UserName = L""; Globals.LogFields.UriStemLength = 5 * 2; Globals.LogFields.UriStem = L"/Hell"; Globals.LogFields.ClientIpLength = 15; Globals.LogFields.ClientIp = "111.111.111.111"; Globals.LogFields.ServerNameLength = 9; Globals.LogFields.ServerName = "localhost"; Globals.LogFields.ServiceNameLength = 0; Globals.LogFields.ServiceName = ""; Globals.LogFields.ServerIpLength = 15; Globals.LogFields.ServerIp = "111.111.111.111"; Globals.LogFields.MethodLength = 3; Globals.LogFields.Method = "GET"; Globals.LogFields.UriQueryLength = 0; Globals.LogFields.UriQuery = ""; Globals.LogFields.HostLength = 9; Globals.LogFields.Host = "localhost"; Globals.LogFields.UserAgentLength = 0; Globals.LogFields.UserAgent = ""; Globals.LogFields.CookieLength = 0; Globals.LogFields.Cookie = ""; Globals.LogFields.ReferrerLength = 0; Globals.LogFields.Referrer = ""; Globals.LogFields.ServerPort = 80; Globals.LogFields.ProtocolStatus = 200; Globals.LogFields.Win32Status = 0; Globals.EnableLogging = TRUE; } // // Create IO completion port // Globals.CompletionPort = CreateIoCompletionPort( Globals.AppPool, NULL, (ULONG_PTR) 0, 0 ); if ( Globals.CompletionPort == NULL ) { result = GetLastError(); wprintf( L"Error creating completion port. Error = %d\n", result ); goto fatal; } // // Create worker threads // for ( i = 0; i < NumberOfThreads; i++ ) { Thread = CreateThread( NULL, 0, WorkerThreadRoutine, NULL, CREATE_SUSPENDED, NULL ); if ( Thread == NULL ) { result = GetLastError(); wprintf( L"Error creating thread. Error = %d\n", result ); goto fatal; } // // Do some affinitization stuff? // ResumeThread( Thread ); CloseHandle( Thread ); } // // Success! // return NO_ERROR; fatal: if ( FileHandle != INVALID_HANDLE_VALUE ) { CloseHandle( FileHandle ); } LocalTerminate( initDone ); return result; } // LocalInitialize VOID LocalTerminate( IN BOOLEAN TerminateUl ) { ULONG result; ULONG i; if (Globals.ConfigGroup != HTTP_NULL_ID) { result = HttpDeleteConfigGroup( Globals.ControlChannel, Globals.ConfigGroup ); if (result != NO_ERROR) { wprintf( L"HttpDeleteConfigGroup() failed, error %lu\n", result ); } } if (Globals.AppPool != NULL) { CloseHandle( Globals.AppPool ); } if (Globals.ControlChannel != NULL) { CloseHandle( Globals.ControlChannel ); } if (TerminateUl) { HttpTerminate(); } } // LocalTerminate PIO_CONTEXT AllocateIoContext( VOID ) { PIO_CONTEXT pIoContext; pIoContext = ALLOC( sizeof(*pIoContext) ); if (pIoContext != NULL) { InitializeIoContext( pIoContext ); } return pIoContext; } // AllocateIoContext VOID InitializeIoContext( IN PIO_CONTEXT pIoContext ) { ZeroMemory( pIoContext, sizeof(*pIoContext) ); pIoContext->pRequestBuffer = (PHTTP_REQUEST)pIoContext->StaticRequestBuffer; pIoContext->RequestBufferLength = sizeof(pIoContext->StaticRequestBuffer); pIoContext->Read.pIoContext = pIoContext; pIoContext->Read.pCompletionRoutine = &ReadRequestComplete; pIoContext->Send.pIoContext = pIoContext; pIoContext->Send.pCompletionRoutine = &SendResponseComplete; } // InitializeIoContext VOID FreeIoContext( IN PIO_CONTEXT pIoContext ) { CleanupIoContext( pIoContext ); FREE( pIoContext ); } // FreeIoContext VOID CleanupIoContext( IN PIO_CONTEXT pIoContext ) { if (pIoContext->pRequestBuffer != (PHTTP_REQUEST)pIoContext->StaticRequestBuffer) { FREE( pIoContext->pRequestBuffer ); pIoContext->pRequestBuffer = (PHTTP_REQUEST)pIoContext->StaticRequestBuffer; } } // CleanupIoContext ULONG GrowRequestBuffer( IN PIO_CONTEXT pIoContext, IN ULONG RequestBufferLength ) { PHTTP_REQUEST pNewBuffer; if (RequestBufferLength <= pIoContext->RequestBufferLength) { return NO_ERROR; } pNewBuffer = ALLOC( RequestBufferLength ); if (pNewBuffer != NULL) { if (pIoContext->pRequestBuffer != (PHTTP_REQUEST)pIoContext->StaticRequestBuffer) { FREE( pIoContext->pRequestBuffer ); } pIoContext->pRequestBuffer = pNewBuffer; return NO_ERROR; } return ERROR_NOT_ENOUGH_MEMORY; } // GrowRequestBuffer ULONG ReadRequest( IN PIO_CONTEXT pIoContext, IN HTTP_REQUEST_ID RequestId ) { ULONG result; ULONG bytesRead; // // Read a request. // result = HttpReceiveHttpRequest( Globals.AppPool, RequestId, 0, pIoContext->pRequestBuffer, pIoContext->RequestBufferLength, &bytesRead, &pIoContext->Read.Overlapped ); if (result != ERROR_IO_PENDING) { result = (pIoContext->Read.pCompletionRoutine)( &pIoContext->Read, result, bytesRead ); } return result; } // ReadRequest ULONG ReadRequestComplete( IN PMY_OVERLAPPED pOverlapped, IN ULONG ErrorCode, IN ULONG BytesTransferred ) { PIO_CONTEXT pIoContext; pIoContext = pOverlapped->pIoContext; if (ErrorCode == ERROR_MORE_DATA) { // // Request buffer too small; reallocate & try again. // ErrorCode = GrowRequestBuffer( pIoContext, BytesTransferred ); if (ErrorCode == NO_ERROR) { ErrorCode = ReadRequest( pIoContext, pIoContext->pRequestBuffer->RequestId ); } } ErrorCode = SendResponse( pIoContext ); return ErrorCode; } // ReadRequestComplete ULONG SendResponse( IN PIO_CONTEXT pIoContext ) { DWORD bytesSent; ULONG result; if ( Globals.SendRawResponse ) { result = HttpSendEntityBody( Globals.AppPool, pIoContext->pRequestBuffer->RequestId, HTTP_SEND_RESPONSE_FLAG_RAW_HEADER, 1, &Globals.StaticRawResponse, &bytesSent, &pIoContext->Send.Overlapped, Globals.EnableLogging ? &Globals.LogFields : NULL ); } else { result = HttpSendHttpResponse( Globals.AppPool, pIoContext->pRequestBuffer->RequestId, 0, &Globals.StaticResponse, NULL, &bytesSent, &pIoContext->Send.Overlapped, Globals.EnableLogging ? &Globals.LogFields : NULL ); } return result; } // SendResponse ULONG SendResponseComplete( IN PMY_OVERLAPPED pOverlapped, IN ULONG ErrorCode, IN ULONG BytesTransferred ) { PIO_CONTEXT pIoContext; pIoContext = pOverlapped->pIoContext; ErrorCode = ReadRequest( pIoContext, HTTP_NULL_ID ); return ErrorCode; } // SendResponseComplete DWORD CALLBACK WorkerThreadRoutine( VOID * Arg ) { BOOL Complete; OVERLAPPED * pOverlapped; DWORD Bytes; ULONG_PTR CompletionKey; DWORD ErrorStatus; PMY_OVERLAPPED pMyOverlapped; PIO_CONTEXT pIoContext; for ( ; !Globals.DoShutdown ; ) { Complete = GetQueuedCompletionStatus( Globals.CompletionPort, &Bytes, &CompletionKey, &pOverlapped, INFINITE ); ErrorStatus = Complete ? ERROR_SUCCESS : GetLastError(); pMyOverlapped = CONTAINING_RECORD( pOverlapped, MY_OVERLAPPED, Overlapped ); pMyOverlapped->pCompletionRoutine( pMyOverlapped, ErrorStatus, Bytes ); } return NO_ERROR; } VOID CALLBACK IoCompletionRoutine( IN ULONG ErrorCode, IN ULONG BytesTransferred, IN LPOVERLAPPED pOverlapped ) { PMY_OVERLAPPED pMyOverlapped; PIO_CONTEXT pIoContext; pMyOverlapped = CONTAINING_RECORD( pOverlapped, MY_OVERLAPPED, Overlapped ); (pMyOverlapped->pCompletionRoutine)( pMyOverlapped, ErrorCode, BytesTransferred ); } // IoCompletionRoutine