/*++ Copyright (c) 2000 Microsoft Corporation Module Name : workerrequest.cxx Abstract: UL_NATIVE_REQUEST implementation Author: Murali R. Krishnan ( MuraliK ) 23-Oct-1998 Lei Jin ( leijin ) 13-Apr-1999 (Porting) Environment: Win32 - User Mode Project: ULATQ.DLL --*/ #include "precomp.hxx" // // User supplied completion routines // extern PFN_ULATQ_NEW_REQUEST g_pfnNewRequest; extern PFN_ULATQ_IO_COMPLETION g_pfnIoCompletion; // // UL_NATIVE_REQUEST statics // ULONG UL_NATIVE_REQUEST::sm_cRequestsServed; ULONG UL_NATIVE_REQUEST::sm_cRestart; LONG UL_NATIVE_REQUEST::sm_RestartMsgSent; LIST_ENTRY UL_NATIVE_REQUEST::sm_RequestListHead; CRITICAL_SECTION UL_NATIVE_REQUEST::sm_csRequestList; DWORD UL_NATIVE_REQUEST::sm_cRequests; PTRACE_LOG UL_NATIVE_REQUEST::sm_pTraceLog; DWORD UL_NATIVE_REQUEST::sm_cRequestsPending; DWORD UL_NATIVE_REQUEST::sm_cDesiredPendingRequests; BOOL UL_NATIVE_REQUEST::sm_fAddingRequests; ALLOC_CACHE_HANDLER * UL_NATIVE_REQUEST::sm_pachNativeRequests; DWORD UL_NATIVE_REQUEST::sm_cMaxFreeRequests = DEFAULT_MAX_FREE_REQUESTS; DWORD UL_NATIVE_REQUEST::sm_cFreeRequests; #ifdef _WIN64 CSpinLock UL_NATIVE_REQUEST::sm_FreeListLock; SINGLE_LIST_ENTRY UL_NATIVE_REQUEST::sm_FreeList; #else SLIST_HEADER UL_NATIVE_REQUEST::sm_FreeList; #endif HANDLE UL_NATIVE_REQUEST::sm_hPendingRequestsTimer = NULL; UL_NATIVE_REQUEST::UL_NATIVE_REQUEST( VOID ) : _pbBuffer( _achBuffer ), _cbBuffer( sizeof( _achBuffer ) ), _pClientCertInfo( NULL ), _cbAllocateMemoryOffset( 0 ), _cRefs( 1 ) { InitializeListHead( &_ListEntry ); _FreeListEntry.Next = NULL; AddToRequestList(); Reset(); _dwSignature = UL_NATIVE_REQUEST_SIGNATURE; } UL_NATIVE_REQUEST::~UL_NATIVE_REQUEST( VOID ) { DBG_ASSERT( CheckSignature() ); _dwSignature = UL_NATIVE_REQUEST_SIGNATURE_FREE; if ( !IsListEmpty( &_ListEntry ) ) { RemoveFromRequestList(); } DBG_ASSERT( _cRefs == 0 ); Cleanup(); } VOID UL_NATIVE_REQUEST::Cleanup( VOID ) /*++ Routine Description: Private helper method called by destructor and Reset() It assures that resources are cleaned up properly Arguments: None Return Value: None --*/ { if ( _pbBuffer != _achBuffer ) { LocalFree( _pbBuffer ); } _pbBuffer = _achBuffer; _cbBuffer = sizeof( _achBuffer ); // // http.sys returns token in HTTP_SSL_CLIENT_CERT_INFO structure // UL_NATIVE_REQUEST must take care of closing the token to avoid leak // if ( _pClientCertInfo != NULL && _pClientCertInfo->Token != NULL ) { CloseHandle( _pClientCertInfo->Token ); _pClientCertInfo->Token = NULL; } // // Start allocating from the beginning of the buffer // _cbAllocateMemoryOffset = 0; } VOID UL_NATIVE_REQUEST::Reset( VOID ) /*++ Routine Description: Reset a UL_NATIVE_REQUEST for use in the request state machine Arguments: None Return Value: None --*/ { Cleanup(); _ExecState = NREQ_STATE_START; _pvContext = NULL; _cbAsyncIOData = 0; _dwAsyncIOError = NO_ERROR; _dwClientCertFlags = 0; ZeroMemory( &_Overlapped, sizeof(_Overlapped) ); } VOID * UL_NATIVE_REQUEST::AllocateMemory( DWORD cbSize ) /*++ Routine Description: Allocate some memory for use by W3DT.DLL user in our private buffer Arguments: cbSize - Size to allocate Return Value: Pointer to memory (NULL if failed) --*/ { DWORD cbSizeNeeded = 0; VOID * pvRet; ULONG_PTR ulPointer; cbSizeNeeded = _cbAllocateMemoryOffset + cbSize + sizeof( ULONG_PTR ); if ( cbSizeNeeded <= sizeof( _abAllocateMemory ) ) { // // Allocate inline // ulPointer = (ULONG_PTR) _abAllocateMemory + _cbAllocateMemoryOffset; ulPointer = ( ulPointer + 7 ) & ~7; pvRet = (VOID*) ulPointer; _cbAllocateMemoryOffset += cbSize + sizeof( ULONG_PTR ); } else { pvRet = NULL; } return pvRet; } VOID UL_NATIVE_REQUEST::DoWork( DWORD cbData, DWORD dwError, LPOVERLAPPED lpo ) /*++ Routine Description: The driver of the state machine. It is called off async completions and initially (with lpo=NULL) to kick off the state machine. Arguments: cbData - Bytes completed dwError - Win32 Error of completion lpo - Pointer to OVERLAPPED passed in async call Return Value: None --*/ { NREQ_STATUS Status = NSTATUS_NEXT; BOOL fError = FALSE; ReferenceWorkerRequest(); if ( lpo != NULL ) { // // This is an async completion. Dereference the request to match // the reference before posting async operation // DereferenceWorkerRequest(); // // Save away the completion data for state handler use and debugging // purposes // _cbAsyncIOData = cbData; _dwAsyncIOError = dwError; ZeroMemory( &_Overlapped, sizeof( _Overlapped ) ); } // // Keep on executing the state machine until a state handler returns // pending. If so, the IO completion will continue the machine // // We also break out on error (typical case will be shutdown) // while ( !fError && Status == NSTATUS_NEXT ) { switch ( _ExecState ) { case NREQ_STATE_START: Status = DoStateStart(); break; case NREQ_STATE_READ: Status = DoStateRead(); break; case NREQ_STATE_PROCESS: Status = DoStateProcess(); break; case NREQ_STATE_ERROR: fError = TRUE; DereferenceWorkerRequest(); break; case NREQ_STATE_CLIENT_CERT: Status = DoStateClientCertificate(); break; default: fError = TRUE; DBG_ASSERT( FALSE ); break; } } DereferenceWorkerRequest(); } NREQ_STATUS UL_NATIVE_REQUEST::DoStateStart( VOID ) /*++ Routine Description: The NREQ_START state. This state does the initial read for a new HTTP request (it passes a NULL request ID). Continuing the read process ( for example if the buffer is too small) happens in the NREQ_READ state. Arguments: None Return Value: NSTATUS_PENDING if async IO pending, else NSTATUS_NEXT --*/ { ULONG rc; HTTP_REQUEST_ID RequestId; HANDLE hAsync; // // This is the initial read, therefore we don't know the request ID // HTTP_SET_NULL_ID( &RequestId ); // // If we are in shutdown, then just bail with error // if ( g_pwpContext->IsInShutdown() ) { _ExecState = NREQ_STATE_ERROR; return NSTATUS_NEXT; } // // Have we served enough requests? // if ( sm_cRestart && sm_cRequestsServed >= sm_cRestart ) { // // Indicate to WAS such and the error out // if ( NeedToSendRestartMsg() ) { g_pwpContext->SendMsgToAdminProcess( IPM_WP_RESTART_COUNT_REACHED ); } _ExecState = NREQ_STATE_ERROR; return NSTATUS_NEXT; } // // Make an async call to HttpReceiveHttpRequest to get the next request // _ExecState = NREQ_STATE_READ; InterlockedIncrement( (LPLONG) &sm_cRequestsPending ); ReferenceWorkerRequest(); hAsync = g_pwpContext->GetAndLockAsyncHandle(); if ( hAsync != NULL ) { rc = HttpReceiveHttpRequest( hAsync, RequestId, HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, (HTTP_REQUEST *) _pbBuffer, _cbBuffer, NULL, &_Overlapped ); } else { rc = ERROR_INVALID_HANDLE; } DBG_REQUIRE(SUCCEEDED(g_pwpContext->UnlockAsyncHandle())); if ( rc == NO_ERROR ) { rc = ERROR_IO_PENDING; } if ( rc != ERROR_IO_PENDING ) { DBG_ASSERT( rc != NO_ERROR ); InterlockedDecrement( (LPLONG) &sm_cRequestsPending ); _ExecState = NREQ_STATE_ERROR; DereferenceWorkerRequest(); return NSTATUS_NEXT; } else { return NSTATUS_PENDING; } } NREQ_STATUS UL_NATIVE_REQUEST::DoStateRead( VOID ) /*++ Routine Description: The NREQ_READ state. This state is responsible for producing a complete UL_HTTP_REQUEST for consumption by the NREQ_PROCESS state. Note that this may require another UlReceiveHttpRequest() if the initial was not supplied a large enough buffer. Arguments: None Return Value: NSTATUS_PENDING if async IO pending, else NSTATUS_NEXT --*/ { HTTP_REQUEST_ID RequestId; ULONG cbRequired; DWORD cCurrentRequestsPending; HANDLE hAsync; // // The initial read is complete. If the error is ERROR_MORE_DATA, then // our buffer was not large enough. Resize and try again // if ( _dwAsyncIOError == ERROR_MORE_DATA ) { // // Remember the request ID to retry the the UlReceiveHttpRequest // RequestId = QueryRequestId(); DBG_ASSERT( RequestId != HTTP_NULL_ID ); // // We need to allocate a larger buffer :( // if ( _pbBuffer != _achBuffer ) { // // We shouldn't be getting ERROR_MORE_DATA if we already // resized due to an earlier ERROR_MORE_DATA // DBG_ASSERT( FALSE ); LocalFree( _pbBuffer ); _pbBuffer = NULL; } // // The completed bytes tells us the required size of our input // buffer // _cbBuffer = _cbAsyncIOData; _pbBuffer = (UCHAR*) LocalAlloc( LPTR, _cbBuffer ); if ( _pbBuffer == NULL ) { _ExecState = NREQ_STATE_ERROR; return NSTATUS_NEXT; } // // Read the HTTP request again (better to do it sychronously, since // it is all ready now) // hAsync = g_pwpContext->GetAndLockAsyncHandle(); if ( hAsync != NULL ) { _dwAsyncIOError = HttpReceiveHttpRequest( hAsync, RequestId, HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, (HTTP_REQUEST *)_pbBuffer, _cbBuffer, &cbRequired, NULL ); } else { _dwAsyncIOError = ERROR_INVALID_HANDLE; } DBG_REQUIRE(SUCCEEDED(g_pwpContext->UnlockAsyncHandle())); // // No reason this should fail again with ERROR_MORE_DATA // DBG_ASSERT( _dwAsyncIOError != ERROR_MORE_DATA ); } if ( _dwAsyncIOError == NO_ERROR ) { // // We're done. The read was successful and we have a full // UL_HTTP_REQUEST. We can now pass off to the NREQ_PROCESS state // cCurrentRequestsPending = InterlockedDecrement( (LPLONG) &sm_cRequestsPending ); if ( cCurrentRequestsPending < sm_cDesiredPendingRequests / 2 ) { AddPendingRequests( sm_cDesiredPendingRequests - cCurrentRequestsPending ); } _ExecState = NREQ_STATE_PROCESS; } else { // // Some other error. Try again // InterlockedDecrement( (LPLONG) &sm_cRequestsPending ); Reset(); } return NSTATUS_NEXT; } NREQ_STATUS UL_NATIVE_REQUEST::DoStateProcess( VOID ) /*++ Routine Description: The NREQ_PROCESS state. This state actually calls consumer of the NewRequests and IoCompletions. This state calls the set routines in W3CORE.DLL Arguments: None Return Value: NSTATUS_PENDING if async IO pending, else NSTATUS_NEXT --*/ { WP_IDLE_TIMER * pTimer; // // Reset idle tick since we're not idle if we're processing // pTimer = g_pwpContext->QueryIdleTimer(); if ( pTimer != NULL ) { pTimer->ResetCurrentIdleTick(); } ReferenceWorkerRequest(); if ( _pvContext == NULL ) { // // Extra reference here. The consumer must call UlAtqResetContext() // to finally cleanup. This complication is brought on by // scenarios where non-IO-completions are required to finish a request // (example: async ISAPI) // ReferenceWorkerRequest(); DBG_ASSERT( g_pfnNewRequest != NULL ); g_pfnNewRequest( this ); } else { DBG_ASSERT( g_pfnIoCompletion != NULL ); g_pfnIoCompletion( _pvContext, _cbAsyncIOData, _dwAsyncIOError, &_Overlapped ); } DereferenceWorkerRequest(); return NSTATUS_PENDING; } NREQ_STATUS UL_NATIVE_REQUEST::DoStateClientCertificate( VOID ) /*++ Routine Description: Handle a completion for receiving a client certificate Arguments: None Return Value: NSTATUS_PENDING if async IO pending, else NSTATUS_NEXT --*/ { ULONG Status; HTTP_REQUEST * pRequest; HANDLE hAsync; DBG_ASSERT( _ExecState == NREQ_STATE_CLIENT_CERT ); // // Is our buffer too small. If so retry the request synchronously with // a bigger buffer // Note: STATUS_BUFFER_OVERFLOW is translated to WIN32 Error - ERROR_MORE_DATA if ( _dwAsyncIOError == ERROR_MORE_DATA ) { // // If buffer is not big enough, HTTP.sys will return only HTTP_SSL_CLIENT_CERT_INFO // structure that contains the size of the client certificate // The following assert is to assure that HTTP.SYS returns back that // HTTP_SSL_CLIENT_CERT_INFO structure and nothing more DBG_ASSERT( _cbAsyncIOData == sizeof( HTTP_SSL_CLIENT_CERT_INFO ) ); // // We need to allocate enough memory to contain HTTP_SSL_CLIENT_CERT_INFO // and certificate blob // DWORD dwRequiredSize = _pClientCertInfo->CertEncodedSize + sizeof( HTTP_SSL_CLIENT_CERT_INFO ); DBG_ASSERT( dwRequiredSize > _buffClientCertInfo.QuerySize() ); if ( !_buffClientCertInfo.Resize( dwRequiredSize ) ) { // // Funnel the fatal error to the complete // _dwAsyncIOError = GetLastError(); } else { // // Retry the request for client cert synchronously // _pClientCertInfo = reinterpret_cast( _buffClientCertInfo.QueryPtr() ); hAsync = g_pwpContext->GetAndLockAsyncHandle(); if ( hAsync != NULL ) { Status = HttpReceiveClientCertificate( hAsync, QueryConnectionId(), _dwClientCertFlags, _pClientCertInfo, _buffClientCertInfo.QuerySize(), NULL, NULL ); } else { Status = ERROR_INVALID_HANDLE; } DBG_REQUIRE(SUCCEEDED(g_pwpContext->UnlockAsyncHandle())); if ( Status != NO_ERROR ) { DBG_ASSERT( _dwAsyncIOError != ERROR_MORE_DATA ); } _dwAsyncIOError = Status; _cbAsyncIOData = dwRequiredSize; } } if ( _dwAsyncIOError == NO_ERROR ) { pRequest = QueryHttpRequest(); DBG_ASSERT( pRequest->pSslInfo != NULL ); DBG_ASSERT( pRequest->pSslInfo->pClientCertInfo == NULL ); pRequest->pSslInfo->pClientCertInfo = _pClientCertInfo; } // // Regardless of what happened, we are no longer processing a client cert // _ExecState = NREQ_STATE_PROCESS; DBG_ASSERT( g_pfnIoCompletion != NULL ); g_pfnIoCompletion( _pvContext, _cbAsyncIOData, _dwAsyncIOError, &_Overlapped ); return NSTATUS_PENDING; } VOID UL_NATIVE_REQUEST::ResetContext( VOID ) /*++ Routine Description: The implementation of UlAtqResetContext which the consumer of W3DT.DLL calls to cleanup the context. Arguments: None Return Value: None --*/ { DereferenceWorkerRequest(); } VOID UL_NATIVE_REQUEST::ReferenceWorkerRequest( VOID ) /*++ Routine Description: Increment the reference count on the worker request Arguments: None Return Value: None --*/ { LONG cRefs = InterlockedIncrement( &_cRefs ); // // Log the reference ( sm_pTraceLog!=NULL if DBG=1) // if ( sm_pTraceLog != NULL ) { WriteRefTraceLog( sm_pTraceLog, cRefs, this ); } } VOID UL_NATIVE_REQUEST::DereferenceWorkerRequest( VOID ) /*++ Routine Description: Dereference Request. This routine will optionally reset the context for use for reading the next HTTP request. Putting the reset code in one place (here) handles all the cases where we would want to reset. However, there are a few places where we definitely don't want to reset (in the case of error where the context will be going away) In either case, if the ref count goes to 0, we can delete the object. Arguments: None Return Value: None --*/ { LONG cRefs = InterlockedDecrement( &_cRefs ); if ( sm_pTraceLog != NULL ) { WriteRefTraceLog( sm_pTraceLog, cRefs, this ); } if ( cRefs == 0 ) { // // If 0, we can definitely cleanup, regardless. This is the error // case which is used to cleanup contexts on shutdown (on shutdown, // the apppool handle is closed which will error out all pending // UlReceiveHttpRequests // delete this; } else if ( cRefs == 1 && _ExecState != NREQ_STATE_ERROR ) { // // Reset the state machine. Now we can increment our served count // InterlockedIncrement( (PLONG) &sm_cRequestsServed ); // // If we have too many outstanding requests, then don't pend // another receive. Keep the request around ready to go // if ( sm_cRequestsPending > sm_cDesiredPendingRequests * 2 ) { FreeWorkerRequest( this ); } else { Reset(); // // Re-kickoff the state machine // DoWork( 0, 0, NULL ); } } } VOID UL_NATIVE_REQUEST::RemoveFromRequestList( VOID ) /*++ Routine Description: Remove this UL_NATIVE_REQUEST from the static list of requests. Main purpose of the list is for debugging. Arguments: None Return Value: None --*/ { EnterCriticalSection( &sm_csRequestList ); RemoveEntryList( &_ListEntry ); sm_cRequests--; LeaveCriticalSection( &sm_csRequestList ); } VOID UL_NATIVE_REQUEST::AddToRequestList( VOID ) /*++ Routine Description: Add this request to the static list of requests. The main purpose of the list is for debugging. Arguments: None Return Value: None --*/ { EnterCriticalSection( &sm_csRequestList ); sm_cRequests++; InsertTailList( &sm_RequestListHead, &_ListEntry ); LeaveCriticalSection( &sm_csRequestList ); } HRESULT UL_NATIVE_REQUEST::SendResponse( BOOL fAsync, DWORD dwFlags, HTTP_RESPONSE * pResponse, HTTP_CACHE_POLICY * pCachePolicy, DWORD *pcbSent, HTTP_LOG_FIELDS_DATA *pUlLogData ) /*++ Routine Description: Send an HTTP response thru UL. Arguments: fAsync - TRUE if send is async dwFlags - UlSendHttpResponse flags pResponse - Pointer to UL_HTTP_RESPONSE pCachePolicy - Cache policy pcbSent - Receives number of bytes send pULLogData - Logging information Return Value: HRESULT (if pending, the return is NO_ERROR) --*/ { ULONG Status = NO_ERROR; HRESULT hr = NO_ERROR; HANDLE hAsync; if ( pcbSent == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } if ( fAsync ) { ReferenceWorkerRequest(); } hAsync = g_pwpContext->GetAndLockAsyncHandle(); if ( hAsync != NULL ) { Status = HttpSendHttpResponse( hAsync, QueryRequestId(), dwFlags, pResponse, pCachePolicy, fAsync ? NULL : pcbSent, NULL, 0, fAsync ? &(_Overlapped) : NULL , pUlLogData ); } else { Status = ERROR_INVALID_HANDLE; } DBG_REQUIRE(SUCCEEDED(g_pwpContext->UnlockAsyncHandle())); // // If the response is pending, then we return a successful error code. // This frees the caller from the ERROR_IO_PENDING checks in common // case // if ( fAsync ) { if ( Status == NO_ERROR ) { Status = ERROR_IO_PENDING; } DBG_ASSERT( Status != NO_ERROR ); if ( Status != ERROR_IO_PENDING ) { _Overlapped.Internal = Status; DereferenceWorkerRequest(); hr = HRESULT_FROM_WIN32( Status ); } } else if ( Status != NO_ERROR ) { hr = HRESULT_FROM_WIN32( Status ); } return hr; } HRESULT UL_NATIVE_REQUEST::SendEntity( BOOL fAsync, DWORD dwFlags, USHORT cChunks, HTTP_DATA_CHUNK * pChunks, DWORD *pcbSent, HTTP_LOG_FIELDS_DATA *pUlLogData ) /*++ Routine Description: Send an HTTP entity thru UL. Arguments: fAsync - TRUE if send is async dwFlags - UlSendHttpResponse flags cChunks - Number of chunks in response pChunks - Pointer to array of UL_DATA_CHUNKs pcbSent - Receives number of bytes sent pUlLogData - Log information Return Value: HRESULT (if pending, the return is NO_ERROR) --*/ { ULONG Status = NO_ERROR; HRESULT hr = NO_ERROR; HANDLE hAsync; if ( pcbSent == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } if ( fAsync ) { ReferenceWorkerRequest(); } hAsync = g_pwpContext->GetAndLockAsyncHandle(); if ( hAsync != NULL ) { Status = HttpSendResponseEntityBody( hAsync, QueryRequestId(), dwFlags, cChunks, pChunks, fAsync ? NULL : pcbSent, NULL, 0, fAsync ? &(_Overlapped) : NULL, pUlLogData ); } else { Status = ERROR_INVALID_HANDLE; } DBG_REQUIRE(SUCCEEDED(g_pwpContext->UnlockAsyncHandle())); // // If the send is pending, then we return a successful error code. // This frees the caller from the ERROR_IO_PENDING checks in common // case // if ( fAsync ) { if ( Status == NO_ERROR ) { Status = ERROR_IO_PENDING; } DBG_ASSERT( Status != NO_ERROR ); if ( Status != ERROR_IO_PENDING ) { _Overlapped.Internal = Status; DereferenceWorkerRequest(); hr = HRESULT_FROM_WIN32( Status ); } } else if ( Status != NO_ERROR ) { hr = HRESULT_FROM_WIN32( Status ); } return hr; } HRESULT UL_NATIVE_REQUEST::ReceiveEntity( BOOL fAsync, DWORD dwFlags, VOID * pBuffer, DWORD cbBuffer, DWORD * pBytesReceived ) /*++ Routine Description: Receive HTTP entity thru UL. Arguments: fAsync - TRUE if receive is async dwFlags - UlSendHttpResponse flags pBuffer - A buffer to receive the data cbBuffer - The size of the receive buffer pBytesReceived - Upon return, the number of bytes copied to the buffer Return Value: HRESULT (if pending, the return is NO_ERROR) --*/ { ULONG Status = NO_ERROR; HRESULT hr = NO_ERROR; HANDLE hAsync; if ( fAsync ) { ReferenceWorkerRequest(); } hAsync = g_pwpContext->GetAndLockAsyncHandle(); if ( hAsync != NULL ) { Status = HttpReceiveRequestEntityBody( hAsync, QueryRequestId(), dwFlags, pBuffer, cbBuffer, fAsync ? NULL : pBytesReceived, fAsync ? &(_Overlapped) : NULL ); } else { Status = ERROR_INVALID_HANDLE; } DBG_REQUIRE(SUCCEEDED(g_pwpContext->UnlockAsyncHandle())); // // If the receive is pending, then we return a successful error code. // This frees the caller from the ERROR_IO_PENDING checks in common // case // if ( fAsync ) { if ( Status == NO_ERROR ) { Status = ERROR_IO_PENDING; } DBG_ASSERT( Status != NO_ERROR ); if ( Status != ERROR_IO_PENDING ) { _Overlapped.Internal = Status; DereferenceWorkerRequest(); hr = HRESULT_FROM_WIN32( Status ); } } else if ( Status != NO_ERROR ) { hr = HRESULT_FROM_WIN32( Status ); } return hr; } HRESULT UL_NATIVE_REQUEST::ReceiveClientCertificate( BOOL fAsync, BOOL fDoCertMap, HTTP_SSL_CLIENT_CERT_INFO **ppClientCertInfo ) /*++ Routine Description: Receive a client certificate Arguments: fAsync - TRUE if receive should be async fDoCertMap - TRUE if we should map client certificate to token ppClientCertInfo - Set to point to client cert info on success Return Value: HRESULT (if pending, the return is NO_ERROR) --*/ { ULONG Status; HTTP_REQUEST * pHttpRequest; HRESULT hr = NO_ERROR; HANDLE hAsync; if ( ppClientCertInfo == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *ppClientCertInfo = NULL; // // If this request is not SSL enabled, then getting the client cert is // a no-go // pHttpRequest = QueryHttpRequest(); DBG_ASSERT( pHttpRequest != NULL ); if ( pHttpRequest->pSslInfo == NULL ) { return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } // // Do we already have a cert associated with this request? // DBG_ASSERT( pHttpRequest->pSslInfo != NULL ); if ( pHttpRequest->pSslInfo->pClientCertInfo != NULL ) { if ( fAsync ) { // // BUGBUG: Probably should support this case. And if I do, then // need to fake a completion! // DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *ppClientCertInfo = pHttpRequest->pSslInfo->pClientCertInfo; return NO_ERROR; } // // OK. We'll have to ask UL to renegotiate. We must be processing // a request // DBG_ASSERT( _ExecState == NREQ_STATE_PROCESS ); if ( !_buffClientCertInfo.Resize( INITIAL_CERT_INFO_SIZE ) ) { return HRESULT_FROM_WIN32( GetLastError() ); } ZeroMemory( _buffClientCertInfo.QueryPtr(), INITIAL_CERT_INFO_SIZE ); _pClientCertInfo = reinterpret_cast( _buffClientCertInfo.QueryPtr() ); // // Are we cert mapping? // if ( fDoCertMap ) { _dwClientCertFlags = HTTP_RECEIVE_CLIENT_CERT_FLAG_MAP; } else { _dwClientCertFlags = 0; } // // If we're doing this async, then manage state such that // DoStateClientCert gets the completion // if ( fAsync ) { ReferenceWorkerRequest(); _ExecState = NREQ_STATE_CLIENT_CERT; } hAsync = g_pwpContext->GetAndLockAsyncHandle(); if ( hAsync != NULL ) { Status = HttpReceiveClientCertificate( hAsync, QueryConnectionId(), _dwClientCertFlags, _pClientCertInfo, _buffClientCertInfo.QuerySize(), NULL, fAsync ? &_Overlapped : NULL ); } else { Status = ERROR_INVALID_HANDLE; } DBG_REQUIRE(SUCCEEDED(g_pwpContext->UnlockAsyncHandle())); if ( fAsync ) { if ( Status == NO_ERROR ) { Status = ERROR_IO_PENDING; } DBG_ASSERT( Status != NO_ERROR ); if ( Status != ERROR_IO_PENDING ) { _Overlapped.Internal = Status; DereferenceWorkerRequest(); hr = HRESULT_FROM_WIN32( Status ); _ExecState = NREQ_STATE_PROCESS; } } else if ( Status != NO_ERROR ) { hr = HRESULT_FROM_WIN32( Status ); } return hr; } //static HRESULT UL_NATIVE_REQUEST::Initialize( VOID ) /*++ Routine Description: Static initialization of UL_NATIVE_REQUESTs Arguments: None Return Value: HRESULT --*/ { ALLOC_CACHE_CONFIGURATION acConfig; HKEY hKey; BOOL fRet; HRESULT hr; // // Setup allocation lookaside // acConfig.nConcurrency = 1; acConfig.nThreshold = 100; acConfig.cbSize = sizeof( UL_NATIVE_REQUEST ); DBG_ASSERT( sm_pachNativeRequests == NULL ); sm_pachNativeRequests = new ALLOC_CACHE_HANDLER( "UL_NATIVE_REQUEST", &acConfig ); if ( sm_pachNativeRequests == NULL ) { return HRESULT_FROM_WIN32( GetLastError() ); } fRet = InitializeCriticalSectionAndSpinCount( &sm_csRequestList, UL_NATIVE_REQUEST_CS_SPINS ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); delete sm_pachNativeRequests; sm_pachNativeRequests = NULL; return hr; } InitializeListHead( &sm_RequestListHead ); #ifdef _WIN64 sm_FreeList.Next = NULL; #else InitializeSListHead( &sm_FreeList ); #endif sm_cRequestsServed = 0; sm_cRestart = 0; sm_RestartMsgSent = 0; sm_cRequestsPending = 0; // // If the number of pending HttpReceiveHttpRequest is configured, use // that rather than the default value // if (RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGISTRY_KEY_W3SVC_PERFORMANCE_KEY_W, 0, KEY_READ, &hKey ) == ERROR_SUCCESS) { DWORD cbBuffer = sizeof(DWORD); DWORD dwType; if (RegQueryValueEx( hKey, L"ReceiveRequestsPending", NULL, &dwType, (LPBYTE)&sm_cDesiredPendingRequests, &cbBuffer ) != NO_ERROR || dwType != REG_DWORD) { sm_cDesiredPendingRequests = DESIRED_PENDING_REQUESTS; } RegCloseKey( hKey ); } else { sm_cDesiredPendingRequests = DESIRED_PENDING_REQUESTS; } sm_fAddingRequests = FALSE; #if DBG sm_pTraceLog = CreateRefTraceLog( 2000, 0 ); #endif return NO_ERROR; } //static VOID UL_NATIVE_REQUEST::StopListening( VOID ) /*++ Routine Description: Shutdown the apppool in preparation for shutdown Arguments: None Return Value: None --*/ { HANDLE hAsync; hAsync = g_pwpContext->GetAndLockAsyncHandle(); if ( hAsync != NULL ) { // // This will cause us to cancel all pending HttpReceiveHttpRequest calls // and not get any more but other operations will still go on // HttpShutdownAppPool( hAsync ); } DBG_REQUIRE(SUCCEEDED(g_pwpContext->UnlockAsyncHandle())); } //static VOID UL_NATIVE_REQUEST::Terminate( VOID ) /*++ Routine Description: Static termination Arguments: None Return Value: None --*/ { if ( sm_pTraceLog != NULL ) { DestroyRefTraceLog( sm_pTraceLog ); sm_pTraceLog = NULL; } DeleteCriticalSection( &sm_csRequestList ); if ( sm_pachNativeRequests != NULL ) { delete sm_pachNativeRequests; sm_pachNativeRequests = NULL; } } //static HRESULT UL_NATIVE_REQUEST::AddPendingRequests( DWORD cRequests ) /*++ Routine Description: Pools calls to UlReceiveHttpRequest by creating new UL_NATIVE_REQUESTs and kicking off their state machines Arguments: cRequests - Number of requests to read Return Value: HRESULT --*/ { UL_NATIVE_REQUEST * pRequest; if ( sm_fAddingRequests ) { return NO_ERROR; } if ( InterlockedCompareExchange( (LPLONG) &sm_fAddingRequests, TRUE, FALSE ) == FALSE ) { for ( DWORD i = 0; i < cRequests; i++ ) { pRequest = AllocateWorkerRequest(); if ( pRequest == NULL ) { return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } pRequest->DoWork( 0, 0, NULL ); } sm_fAddingRequests = FALSE; } return NO_ERROR; } //static HRESULT UL_NATIVE_REQUEST::ReleaseAllWorkerRequests( VOID ) /*++ Routine Description: Wait for all outstanding UL_NATIVE_REQUESTs to drain Arguments: None Return Value: HRESULT --*/ { UL_NATIVE_REQUEST * pRequest; // // Now, wait for all requests to be destructed // while ( sm_cRequests ) { // // Clear out any of our free list items. Since this list // may be appended to right now, keep on cleaning it // for ( ; ; ) { pRequest = PopFreeList(); if ( pRequest == NULL ) { break; } DBG_ASSERT( pRequest != NULL ); DBG_ASSERT( pRequest->CheckSignature() ); // // Free it up // pRequest->DereferenceWorkerRequest(); } Sleep( 1000 ); DBGPRINTF(( DBG_CONTEXT, "UL_NATIVE_REQUEST::ReleaseAllWorkerRequests waiting for %d requests to drain.\n", sm_cRequests )); } return NO_ERROR; } //static VOID WINAPI UL_NATIVE_REQUEST::PendingRequestsMonitorHandler( PVOID, BOOLEAN ) /*++ Routine Description: Check if pending requests number is 0. If it is 0 then try to add 1 pending request to allow this worker process to start handling http requests again Arguments: None Return Value: NONE --*/ { if ( sm_cRequestsPending == 0 ) { DBGPRINTF((DBG_CONTEXT, "Worker process has run out of pending requests. Trying to add one\n" )); // // If pending requests number is 0 then // it means there must have been errors on // HttpReceiveHttpRequest() call that // caused worker process to run out of pending requests // It is time to intervene by adding one // pending request. That should jump start // the process of handling the requests again // // Note: We don't check the error because there // is nothing we can do about the error. If // we failed then the next time timer fires and // this handler gets called we will simply retry AddPendingRequests( 1 ); } } //static HRESULT UL_NATIVE_REQUEST::StartPendingRequestsMonitor( VOID ) /*++ Routine Description: Start timer to enable monitoring of the pending requests Arguments: None Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; // // create timer to wake up every 5 seconds // // if ( !CreateTimerQueueTimer( &sm_hPendingRequestsTimer, NULL, UL_NATIVE_REQUEST::PendingRequestsMonitorHandler, NULL, 5000, 5000, WT_EXECUTELONGFUNCTION ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); } else { hr = S_OK; } return hr; } //static VOID UL_NATIVE_REQUEST::StopPendingRequestsMonitor( VOID ) /*++ Routine Description: Stop timer for monitoring of the pending requests Arguments: None Return Value: HRESULT --*/ { if ( sm_hPendingRequestsTimer != NULL ) { if ( !DeleteTimerQueueTimer( NULL, sm_hPendingRequestsTimer, INVALID_HANDLE_VALUE /* wait for callbacks to complete */ ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed to delete Ping timer (%d)\n", GetLastError() )); } sm_hPendingRequestsTimer = NULL; } } //static VOID UL_NATIVE_REQUEST::FreeWorkerRequest( UL_NATIVE_REQUEST * pWorkerRequest ) /*++ Routine Description: Free worker request Arguments: pNativeRequest - Worker request to retrieve Return Value: None --*/ { DBG_ASSERT( pWorkerRequest != NULL ); if ( sm_cFreeRequests > sm_cMaxFreeRequests || g_pwpContext->IsInShutdown() ) { // // Don't keep too many around (especially in shutdown ;-)). // Just free this guy by dereferencing one more time // pWorkerRequest->DereferenceWorkerRequest(); } else { pWorkerRequest->Reset(); PushFreeList( pWorkerRequest ); sm_cFreeRequests++; } } //static UL_NATIVE_REQUEST * UL_NATIVE_REQUEST::AllocateWorkerRequest( VOID ) /*++ Routine Description: Allocate a new request Arguments: None Return Value: Pointer to new request or NULL if error --*/ { UL_NATIVE_REQUEST * pRequest; if ( sm_cFreeRequests ) { pRequest = PopFreeList(); if ( pRequest != NULL ) { DBG_ASSERT( pRequest->CheckSignature() ); sm_cFreeRequests--; return pRequest; } } // // If we got to here, we have to allocate a new request // pRequest = new UL_NATIVE_REQUEST; return pRequest; }