diff --git a/index.html b/index.html new file mode 100644 index 00000000..577b4512 --- /dev/null +++ b/index.html @@ -0,0 +1,164 @@ + + + + iisnode-debug + + + +

HTTP response diagnostics for iisnode

+ +

This request

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Processing time [ms]N/A
Named pipe connection retry countN/A
HRESULTN/A
Server DNS nameN/A
w3wp.exe PIDN/A
node.exe PIDN/A
+ +

Memory

+ + Memory consumption chart + +

Counters

+ + + + + + + + + + + + + + + + + + +
Active node.exe processes serving this applicationN/A
Active HTTP requests in this applicationN/A
Active HTTP requests in this node.exe processN/A
Total node.js requests processed by w3wp.exeN/A
+ +

Environment

+ + + + + + + + + + + + + + +
Version of iisnodeN/A
Server full DNS nameN/A
Full node.exe pathN/A
+ +

Actions

+ + Bugs, feedback, questions
+ iisnode project home page
+ + Debug node.js applications hosted in IIS using iisnode +
+ + Use Event Tracing for Windows (ETW) to get more diagnostics information +
+ + Windows Azure node.js developer center + + + + + diff --git a/src/config/iisnode_dev_x64.xml b/src/config/iisnode_dev_x64.xml index cab240cf..5f59502e 100644 --- a/src/config/iisnode_dev_x64.xml +++ b/src/config/iisnode_dev_x64.xml @@ -32,7 +32,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ - + @@ -60,5 +60,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ + + diff --git a/src/config/iisnode_dev_x86_on_x64.xml b/src/config/iisnode_dev_x86_on_x64.xml index aaa0bbc9..8789c487 100644 --- a/src/config/iisnode_dev_x86_on_x64.xml +++ b/src/config/iisnode_dev_x86_on_x64.xml @@ -32,7 +32,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ - + @@ -60,5 +60,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ + + diff --git a/src/config/iisnode_dev_x86_on_x86.xml b/src/config/iisnode_dev_x86_on_x86.xml index 9606a25e..a52700db 100644 --- a/src/config/iisnode_dev_x86_on_x86.xml +++ b/src/config/iisnode_dev_x86_on_x86.xml @@ -32,7 +32,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ - + @@ -60,5 +60,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ + + diff --git a/src/config/iisnode_express_schema.xml b/src/config/iisnode_express_schema.xml index 2ad9eddc..186d9a48 100644 --- a/src/config/iisnode_express_schema.xml +++ b/src/config/iisnode_express_schema.xml @@ -32,7 +32,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ - + @@ -60,5 +60,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ + + diff --git a/src/config/iisnode_express_schema_x64.xml b/src/config/iisnode_express_schema_x64.xml index ddf6368a..c3b05704 100644 --- a/src/config/iisnode_express_schema_x64.xml +++ b/src/config/iisnode_express_schema_x64.xml @@ -32,7 +32,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ - + @@ -60,5 +60,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ + + diff --git a/src/config/iisnode_schema.xml b/src/config/iisnode_schema.xml index 4088c009..e78d76ce 100644 --- a/src/config/iisnode_schema.xml +++ b/src/config/iisnode_schema.xml @@ -32,7 +32,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ - + @@ -60,5 +60,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ + + diff --git a/src/config/iisnode_schema_x64.xml b/src/config/iisnode_schema_x64.xml index 8d454eae..b332a675 100644 --- a/src/config/iisnode_schema_x64.xml +++ b/src/config/iisnode_schema_x64.xml @@ -32,7 +32,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ - + @@ -60,5 +60,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ + " + diff --git a/src/iisnode/casyncmanager.cpp b/src/iisnode/casyncmanager.cpp index e0235e8b..50786340 100644 --- a/src/iisnode/casyncmanager.cpp +++ b/src/iisnode/casyncmanager.cpp @@ -4,14 +4,15 @@ extern RtlNtStatusToDosError pRtlNtStatusToDosError; void ASYNC_CONTEXT::RunSynchronousContinuations() { - while (this->continueSynchronously) + BOOL fCompletionPosted = FALSE; + while (!fCompletionPosted && this->continueSynchronously) { // The continueSynchronously is used to unwind the call stack // to avoid stack overflow in case of a synchronous IO completions this->continueSynchronously = FALSE; DWORD bytesCompleteted = this->bytesCompleteted; this->bytesCompleteted = 0; - this->completionProcessor(S_OK, bytesCompleteted, (LPOVERLAPPED)this); + this->completionProcessor(S_OK, bytesCompleteted, (LPOVERLAPPED)this, &fCompletionPosted); } } @@ -171,19 +172,31 @@ unsigned int WINAPI CAsyncManager::Worker(void* arg) { OVERLAPPED_ENTRY* entry = entries; for (int i = 0; i < entriesRemoved; i++) - { + { + BOOL fCompletionPosted = FALSE; + if (0L == entry->lpCompletionKey && NULL != (ctx = (ASYNC_CONTEXT*)entry->lpOverlapped) && NULL != ctx->completionProcessor) // regular IO completion - invoke custom processor { + error = (entry->lpOverlapped->Internal == STATUS_SUCCESS) ? ERROR_SUCCESS : pRtlNtStatusToDosError(entry->lpOverlapped->Internal); ctx = (ASYNC_CONTEXT*)entry->lpOverlapped; + ctx->completionProcessor( (0 == entry->dwNumberOfBytesTransferred && ERROR_SUCCESS == error) ? ERROR_NO_DATA : error, entry->dwNumberOfBytesTransferred, - (LPOVERLAPPED)ctx); - ctx->RunSynchronousContinuations(); + (LPOVERLAPPED)ctx, + &fCompletionPosted); + + if(!fCompletionPosted) + { + ctx->RunSynchronousContinuations(); + } + + CNodeHttpStoredContext* storedCtx = (CNodeHttpStoredContext*)ctx->data; + storedCtx->DereferenceNodeHttpStoredContext(); } else if (-1L == entry->lpCompletionKey) // shutdown initiated from Terminate { @@ -194,8 +207,14 @@ unsigned int WINAPI CAsyncManager::Worker(void* arg) if (-2L == entry->lpCompletionKey) // completion of an alertable wait state timer initialized from OnTimer { ctx = (ASYNC_CONTEXT*)entry->lpOverlapped; - ctx->completionProcessor(S_OK, 0, (LPOVERLAPPED)ctx); - ctx->RunSynchronousContinuations(); + ctx->completionProcessor(S_OK, 0, (LPOVERLAPPED)ctx, &fCompletionPosted); + if(!fCompletionPosted) + { + ctx->RunSynchronousContinuations(); + } + + CNodeHttpStoredContext* storedCtx = (CNodeHttpStoredContext*)ctx->data; + storedCtx->DereferenceNodeHttpStoredContext(); } else if (-3L == entry->lpCompletionKey) // continuation initiated form PostContinuation { diff --git a/src/iisnode/casyncmanager.h b/src/iisnode/casyncmanager.h index d3b5b044..be4a3ce5 100644 --- a/src/iisnode/casyncmanager.h +++ b/src/iisnode/casyncmanager.h @@ -1,9 +1,18 @@ #ifndef __CASYNCMANAGER_H__ #define __CASYNCMANAGER_H__ +typedef +VOID +(WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE_IISNODE)( + _In_ DWORD dwErrorCode, + _In_ DWORD dwNumberOfBytesTransfered, + _Inout_ LPOVERLAPPED lpOverlapped, + _Inout_ BOOL * fCompletionPosted + ); + typedef struct { OVERLAPPED overlapped; // this member must be first in the struct - LPOVERLAPPED_COMPLETION_ROUTINE completionProcessor; + LPOVERLAPPED_COMPLETION_ROUTINE_IISNODE completionProcessor; BOOL continueSynchronously; void* data; HANDLE timer; diff --git a/src/iisnode/chttpprotocol.cpp b/src/iisnode/chttpprotocol.cpp index 4e70f92c..22e67c18 100644 --- a/src/iisnode/chttpprotocol.cpp +++ b/src/iisnode/chttpprotocol.cpp @@ -364,7 +364,20 @@ HRESULT CHttpProtocol::ParseResponseStatusLine(CNodeHttpStoredContext* context) data[newOffset] = 0; // zero-terminate the reason phrase to reuse it without copying IHttpResponse* response = context->GetHttpContext()->GetResponse(); - response->SetStatus(statusCode, data + offset, subStatusCode); + + if (CModuleConfiguration::GetSkipIISCustomErrors(context->GetHttpContext())) + { + // set fTrySkipCustomErrors so that error responses sent back from the node app through iisnode + // are passed through to the client instead of being intercepted by IIS when httpErrors existingResponse="Auto" + // this allows a mixed solution where custom error pages can be provided via IIS for errors that occur outside + // of iisnode's purview, while also allowing usually-more-helpful error responses from the node application + // to be passed through to the client rather than being intercepted by IIS. + response->SetStatus(statusCode, data + offset, subStatusCode, S_OK, NULL, TRUE); + } + else + { + response->SetStatus(statusCode, data + offset, subStatusCode); + } // adjust buffers diff --git a/src/iisnode/cmoduleconfiguration.cpp b/src/iisnode/cmoduleconfiguration.cpp index 46c0f538..e2d193fc 100644 --- a/src/iisnode/cmoduleconfiguration.cpp +++ b/src/iisnode/cmoduleconfiguration.cpp @@ -87,15 +87,21 @@ CModuleConfiguration::~CModuleConfiguration() this->debuggerPathSegment = NULL; } + if( NULL != this->debugPortRange ) + { + delete [] this->debugPortRange; + this->debugPortRange = NULL; + } + if (NULL != this->node_env) { - delete this->node_env; + delete [] this->node_env; this->node_env = NULL; } if (NULL != this->watchedFiles) { - delete this->watchedFiles; + delete [] this->watchedFiles; this->watchedFiles = NULL; } @@ -187,7 +193,7 @@ HRESULT CModuleConfiguration::CreateNodeEnvironment(IHttpContext* ctx, DWORD deb // allocate memory for new environment variables - tmpSize = 32767 - environmentSize; + tmpSize = 65536; // hard coded for now, change this to auto allocate based on the values. ErrorIf(NULL == (tmpIndex = tmpStart = new char[tmpSize]), ERROR_NOT_ENOUGH_MEMORY); RtlZeroMemory(tmpIndex, tmpSize); @@ -814,6 +820,10 @@ HRESULT CModuleConfiguration::ApplyConfigOverrideKeyValue(IHttpContext* context, { CheckError(GetDWORD(valueStart, &config->maxLogFiles)); } + else if (0 == strcmpi(keyStart, "nodeProcessStickySessions")) + { + CheckError(GetBOOL(valueStart, &config->nodeProcessStickySessions)); + } else if (0 == strcmpi(keyStart, "loggingEnabled")) { CheckError(GetBOOL(valueStart, &config->loggingEnabled)); @@ -888,6 +898,10 @@ HRESULT CModuleConfiguration::ApplyConfigOverrideKeyValue(IHttpContext* context, { CheckError(GetDWORD(valueStart, &config->idlePageOutTimePeriod)); } + else if(0 == stricmp(keyStart, "skipIISCustomErrors")) + { + CheckError(GetBOOL(valueStart, &config->skipIISCustomErrors)); + } return S_OK; Error: @@ -1228,6 +1242,7 @@ HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfigurat CheckError(GetDWORD(section, L"maxTotalLogFileSizeInKB", &c->maxTotalLogFileSizeInKB)); CheckError(GetDWORD(section, L"maxLogFileSizeInKB", &c->maxLogFileSizeInKB)); CheckError(GetDWORD(section, L"maxLogFiles", &c->maxLogFiles)); + CheckError(GetBOOL(section, L"nodeProcessStickySessions", &c->nodeProcessStickySessions, FALSE)); CheckError(GetBOOL(section, L"loggingEnabled", &c->loggingEnabled, TRUE)); CheckError(GetBOOL(section, L"devErrorsEnabled", &c->devErrorsEnabled, TRUE)); CheckError(GetBOOL(section, L"flushResponse", &c->flushResponse, FALSE)); @@ -1236,7 +1251,7 @@ HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfigurat CheckError(GetString(section, L"debuggerExtensionDll", &c->debuggerExtensionDll)); CheckError(GetBOOL(section, L"debugHeaderEnabled", &c->debugHeaderEnabled, FALSE)); CheckError(GetBOOL(section, L"recycleSignalEnabled", &c->recycleSignalEnabled, FALSE)); - CheckError(GetString(section, L"debuggerVirtualDir", &c->debuggerVirtualDir)); + CheckError(GetString(section, L"debuggerVirtualDir", &c->debuggerVirtualDir)); c->debuggerVirtualDirLength = wcslen(c->debuggerVirtualDir); CheckError(GetString(section, L"node_env", &c->node_env)); CheckError(GetString(section, L"debuggerPortRange", &c->debugPortRange)); @@ -1247,6 +1262,7 @@ HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfigurat CheckError(GetString(section, L"nodeProcessCommandLine", &c->nodeProcessCommandLine)); CheckError(GetString(section, L"interceptor", &c->interceptor)); CheckError(GetDWORD(section, L"idlePageOutTimePeriod", &c->idlePageOutTimePeriod)); + CheckError(GetBOOL(section, L"skipIISCustomErrors", &c->skipIISCustomErrors, FALSE)); // debuggerPathSegment @@ -1433,6 +1449,11 @@ BOOL CModuleConfiguration::GetDebuggingEnabled(IHttpContext* ctx) GETCONFIG(debuggingEnabled) } +BOOL CModuleConfiguration::GetProcessStickySessions(IHttpContext* ctx) +{ + GETCONFIG(nodeProcessStickySessions) +} + PWSTR CModuleConfiguration::GetDebuggerExtensionDll(IHttpContext* ctx) { GETCONFIG(debuggerExtensionDll) @@ -1488,6 +1509,11 @@ LPWSTR CModuleConfiguration::GetConfigOverrides(IHttpContext* ctx) GETCONFIG(configOverrides) } +BOOL CModuleConfiguration::GetSkipIISCustomErrors(IHttpContext* ctx) +{ + GETCONFIG(skipIISCustomErrors) +} + HRESULT CModuleConfiguration::GenerateDebuggerConfig(IHttpContext* context, CModuleConfiguration *config) { HRESULT hr = S_OK; @@ -1576,12 +1602,12 @@ HRESULT CModuleConfiguration::GetDebuggerFilesPathSegmentHelper( DWORD *pdwDebuggerFilesPathSegmentSize ) { - HRESULT hr = S_OK; - HCRYPTPROV hProv = 0; + HRESULT hr = S_OK; + HCRYPTPROV hProv = 0; HCRYPTHASH hHash = 0; CHAR rgbDigits[] = "0123456789abcdef"; - BYTE rgbHash[32]; // sha256 ==> 32 bytes. - DWORD cbHash = 0; + BYTE rgbHash[32]; // sha256 ==> 32 bytes. + DWORD cbHash = 0; CHAR shaHash[MAX_HASH_CHAR + 1]; // we will only use first MAX_HASH_CHAR bytes of the sha256 hash ==> 32 hex chars. DWORD dwSHALength = 0; CHAR *pInput = NULL; @@ -1599,27 +1625,27 @@ HRESULT CModuleConfiguration::GetDebuggerFilesPathSegmentHelper( ErrorIf(dwInputSize != WideCharToMultiByte(CP_ACP, 0, pszScriptPath, dwScriptPathLen, pInput, dwInputSize, NULL, NULL), E_FAIL); pInput[dwInputSize] = '\0'; - // Get handle to the crypto provider - ErrorIf(!CryptAcquireContext(&hProv, - NULL, - NULL, - PROV_RSA_AES, - CRYPT_VERIFYCONTEXT), HRESULT_FROM_WIN32(GetLastError())); - + // Get handle to the crypto provider + ErrorIf(!CryptAcquireContext(&hProv, + NULL, + NULL, + PROV_RSA_AES, + CRYPT_VERIFYCONTEXT), HRESULT_FROM_WIN32(GetLastError())); + ErrorIf(!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash), HRESULT_FROM_WIN32(GetLastError())); ErrorIf(!CryptHashData(hHash, (BYTE*) pInput, strnlen_s(pInput, dwInputSize), 0), HRESULT_FROM_WIN32(GetLastError())); // sha256 ==> 32 bytes. - cbHash = 32; - ErrorIf(!CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0), HRESULT_FROM_WIN32(GetLastError())); - - dwIndex = 0; - // convert first (MAX_HASH_CHAR / 2) bytes to hexadecimal form. - for (DWORD i = 0; i < (MAX_HASH_CHAR / 2); i++, dwIndex=dwIndex+2) - { - shaHash[dwIndex] = rgbDigits[rgbHash[i] >> 4]; - shaHash[dwIndex+1] = rgbDigits[rgbHash[i] & 0xf]; + cbHash = 32; + ErrorIf(!CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0), HRESULT_FROM_WIN32(GetLastError())); + + dwIndex = 0; + // convert first (MAX_HASH_CHAR / 2) bytes to hexadecimal form. + for (DWORD i = 0; i < (MAX_HASH_CHAR / 2); i++, dwIndex=dwIndex+2) + { + shaHash[dwIndex] = rgbDigits[rgbHash[i] >> 4]; + shaHash[dwIndex+1] = rgbDigits[rgbHash[i] & 0xf]; } shaHash[dwIndex] = '\0'; diff --git a/src/iisnode/cmoduleconfiguration.h b/src/iisnode/cmoduleconfiguration.h index c951fca3..4d7e1459 100644 --- a/src/iisnode/cmoduleconfiguration.h +++ b/src/iisnode/cmoduleconfiguration.h @@ -52,6 +52,8 @@ class CModuleConfiguration : public IHttpStoredContext static BOOL invalid; SRWLOCK srwlock; LPWSTR configOverrides; + BOOL skipIISCustomErrors; + BOOL nodeProcessStickySessions; static IHttpServer* server; static HTTP_MODULE_ID moduleId; @@ -129,6 +131,8 @@ class CModuleConfiguration : public IHttpStoredContext static BOOL GetEnableXFF(IHttpContext* ctx); static HRESULT GetPromoteServerVars(IHttpContext* ctx, char*** vars, int* count); static LPWSTR GetConfigOverrides(IHttpContext* ctx); + static BOOL GetSkipIISCustomErrors(IHttpContext* ctx); + static BOOL GetProcessStickySessions(IHttpContext* ctx); static HRESULT CreateNodeEnvironment(IHttpContext* ctx, DWORD debugPort, PCH namedPipe, PCH signalPipeName, PCH* env); diff --git a/src/iisnode/cnodeeventprovider.cpp b/src/iisnode/cnodeeventprovider.cpp index 0d5b56f3..97df5d6c 100644 --- a/src/iisnode/cnodeeventprovider.cpp +++ b/src/iisnode/cnodeeventprovider.cpp @@ -107,10 +107,14 @@ HRESULT CNodeEventProvider::Log(IHttpContext *context, PCWSTR message, UCHAR lev } } + /* + commented because there might be a race condition after calling PostCompletion and then using the IHttpContext object. + Uncomment this only after making sure there is no race condition. (IHttpContext cannot be used after calling PostCompletion) if( IsEnabled( context->GetTraceContext(), level ) ) { CheckError( RaiseEvent( context->GetTraceContext(), message, level, activityId ) ); } + */ return S_OK; Error: diff --git a/src/iisnode/cnodehttpmodule.cpp b/src/iisnode/cnodehttpmodule.cpp index 41f8a2ef..a4876b68 100644 --- a/src/iisnode/cnodehttpmodule.cpp +++ b/src/iisnode/cnodehttpmodule.cpp @@ -219,7 +219,9 @@ REQUEST_NOTIFICATION_STATUS CNodeHttpModule::OnAsyncCompletion( bytesCompleted = async->bytesCompleteted; async->bytesCompleteted = 0; } - async->completionProcessor(pCompletionInfo->GetCompletionStatus(), bytesCompleted, ctx->GetOverlapped()); + BOOL fCompletionPosted = FALSE; + async->completionProcessor(pCompletionInfo->GetCompletionStatus(), bytesCompleted, ctx->GetOverlapped(), &fCompletionPosted); + async->RunSynchronousContinuations(); } @@ -237,6 +239,17 @@ REQUEST_NOTIFICATION_STATUS CNodeHttpModule::OnAsyncCompletion( // result = RQ_NOTIFICATION_CONTINUE; } + else + { + if (0 == value) // decreases ref count set to 1 in the ctor of CNodeHttpStoredContext + { + result = ctx->GetRequestNotificationStatus(); + } + else + { + result = RQ_NOTIFICATION_PENDING; + } + } switch (result) { @@ -266,6 +279,8 @@ REQUEST_NOTIFICATION_STATUS CNodeHttpModule::OnAsyncCompletion( break; }; + ctx->DereferenceNodeHttpStoredContext(); + return result; } diff --git a/src/iisnode/cnodehttpstoredcontext.cpp b/src/iisnode/cnodehttpstoredcontext.cpp index 80fc9fef..bb0e1eed 100644 --- a/src/iisnode/cnodehttpstoredcontext.cpp +++ b/src/iisnode/cnodehttpstoredcontext.cpp @@ -6,7 +6,7 @@ CNodeHttpStoredContext::CNodeHttpStoredContext(CNodeApplication* nodeApplication requestNotificationStatus(RQ_NOTIFICATION_PENDING), connectionRetryCount(0), pendingAsyncOperationCount(1), targetUrl(NULL), targetUrlLength(0), childContext(NULL), isConnectionFromPool(FALSE), expectResponseBody(TRUE), closeConnection(FALSE), isUpgrade(FALSE), upgradeContext(NULL), opaqueFlagSet(FALSE), requestPumpStarted(FALSE), - responseChunkBufferSize(0) + responseChunkBufferSize(0), m_cRefs(1) { IHttpTraceContext* tctx; LPCGUID pguid; @@ -75,7 +75,7 @@ CNodeApplication* CNodeHttpStoredContext::GetNodeApplication() return this->nodeApplication; } -void CNodeHttpStoredContext::SetNextProcessor(LPOVERLAPPED_COMPLETION_ROUTINE processor) +void CNodeHttpStoredContext::SetNextProcessor(LPOVERLAPPED_COMPLETION_ROUTINE_IISNODE processor) { this->asyncContext.completionProcessor = processor; this->SetContinueSynchronously(FALSE); @@ -95,7 +95,7 @@ LPOVERLAPPED CNodeHttpStoredContext::InitializeOverlapped() void CNodeHttpStoredContext::CleanupStoredContext() { - delete this; + DereferenceNodeHttpStoredContext(); } CNodeProcess* CNodeHttpStoredContext::GetNodeProcess() diff --git a/src/iisnode/cnodehttpstoredcontext.h b/src/iisnode/cnodehttpstoredcontext.h index ae116f0a..f38875d7 100644 --- a/src/iisnode/cnodehttpstoredcontext.h +++ b/src/iisnode/cnodehttpstoredcontext.h @@ -40,12 +40,35 @@ class CNodeHttpStoredContext : public IHttpStoredContext HTTP_DATA_CHUNK responseChunk; DWORD responseChunkBufferSize; CNodeEventProvider* eventProvider; + ~CNodeHttpStoredContext(); + + mutable LONG m_cRefs; public: // Context is owned by the caller CNodeHttpStoredContext(CNodeApplication* nodeApplication, CNodeEventProvider* eventProvider, IHttpContext* context); - ~CNodeHttpStoredContext(); + + VOID + ReferenceNodeHttpStoredContext( + VOID + ) + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceNodeHttpStoredContext( + VOID + ) + { + _ASSERT(m_cRefs != 0); + + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } IHttpContext* GetHttpContext(); CNodeApplication* GetNodeApplication(); @@ -78,7 +101,7 @@ class CNodeHttpStoredContext : public IHttpStoredContext IHttpContext* GetChildContext(); DWORD GetBytesCompleted(); - void SetNextProcessor(LPOVERLAPPED_COMPLETION_ROUTINE processor); + void SetNextProcessor(LPOVERLAPPED_COMPLETION_ROUTINE_IISNODE processor); void SetNodeProcess(CNodeProcess* process); void SetPipe(HANDLE pipe); void SetConnectionRetryCount(DWORD count); diff --git a/src/iisnode/cnodeprocess.cpp b/src/iisnode/cnodeprocess.cpp index 1ab6683d..f9e0ed06 100644 --- a/src/iisnode/cnodeprocess.cpp +++ b/src/iisnode/cnodeprocess.cpp @@ -20,6 +20,18 @@ CNodeProcess::~CNodeProcess() this->process = NULL; } + if (NULL != this->startupInfo.hStdOutput && INVALID_HANDLE_VALUE != this->startupInfo.hStdOutput) + { + CloseHandle(this->startupInfo.hStdOutput); + this->startupInfo.hStdOutput = INVALID_HANDLE_VALUE; + } + + if (NULL != this->startupInfo.hStdError && INVALID_HANDLE_VALUE != this->startupInfo.hStdError) + { + CloseHandle(this->startupInfo.hStdError); + this->startupInfo.hStdError = INVALID_HANDLE_VALUE; + } + if (NULL != this->processWatcher) { // The following check prevents a dead-lock between process watcher thread calling OnProcessExited diff --git a/src/iisnode/cnodeprocessmanager.cpp b/src/iisnode/cnodeprocessmanager.cpp index cb6df5d6..4d02f263 100644 --- a/src/iisnode/cnodeprocessmanager.cpp +++ b/src/iisnode/cnodeprocessmanager.cpp @@ -2,7 +2,7 @@ CNodeProcessManager::CNodeProcessManager(CNodeApplication* application, IHttpContext* context) : application(application), processes(NULL), currentProcess(0), isClosing(FALSE), - refCount(1), gracefulShutdownProcessCount(0) + refCount(1), gracefulShutdownProcessCount(0), stickySessions(FALSE) { if (this->GetApplication()->IsDebugMode()) { @@ -13,6 +13,8 @@ CNodeProcessManager::CNodeProcessManager(CNodeApplication* application, IHttpCon this->processCount = CModuleConfiguration::GetNodeProcessCountPerApplication(context); } + this->stickySessions = CModuleConfiguration::GetProcessStickySessions(context); + // cache event provider since the application can be disposed prior to CNodeProcessManager this->eventProvider = this->GetApplication()->GetApplicationManager()->GetEventProvider(); @@ -107,10 +109,57 @@ HRESULT CNodeProcessManager::AddProcess(int ordinal, IHttpContext* context) return hr; } +int CNodeProcessManager::ExtractStickySessionsProcess( PCSTR pszCookie ) +{ + const CHAR* pszKey = "iisnode.session.cookie"; + const CHAR* pszDivider = "="; + const CHAR* pszNext = ";"; + CHAR acProcess[10]; // characters needed for MAXDWORD64 + memset(acProcess, 0, sizeof(acProcess)); + + const CHAR* pStart = strstr(pszCookie, pszKey); + const CHAR* pEnd = NULL; + + if( pStart != NULL ) + { + pStart = strstr(pStart, pszDivider); + if( pStart != NULL ) + { + // skip the '='; + pStart++; + if( pStart != NULL ) + { + pEnd = strstr(pStart, pszNext); + if(!pEnd) + { + pEnd = pStart; + while (*pEnd) /* Works because end-of-string and FALSE are identical. */ + { + if((pEnd - pStart) >= sizeof(acProcess)) + { + break; + } + pEnd++; + } + } + + if((pEnd - pStart) < sizeof(acProcess)) + { + memcpy(acProcess, pStart, pEnd - pStart); // copy result + return atoi(acProcess); + } + } + } + } + + return -1; +} + HRESULT CNodeProcessManager::Dispatch(CNodeHttpStoredContext* request) { HRESULT hr; unsigned int tmpProcess, processToUse; + int processInCookie = -1; CheckNull(request); @@ -122,23 +171,56 @@ HRESULT CNodeProcessManager::Dispatch(CNodeHttpStoredContext* request) if (!this->isClosing) { + // + // if sticky sessions is enabled, use the process specified in the cookie else // employ a round robin routing logic to get a "ticket" to use a process with a specific ordinal number - - if (1 == this->processCount) + // + + if(this->stickySessions) // sticky sessions { - processToUse = 0; + PCSTR pszCookieHeader = NULL; + pszCookieHeader = request->GetHttpContext()->GetRequest()->GetHeader(HttpHeaderCookie); + if(pszCookieHeader != NULL) // There might be a sticky session + { + processInCookie = ExtractStickySessionsProcess(pszCookieHeader); + } } - else + + if( processInCookie < 0) { - do + // + // employ a round robin routing logic to get a "ticket" to use a process with a specific ordinal number + // + + if (1 == this->processCount) + { + processToUse = 0; + } + else { - tmpProcess = this->currentProcess; - processToUse = (tmpProcess + 1) % this->processCount; - } while (tmpProcess != InterlockedCompareExchange(&this->currentProcess, processToUse, tmpProcess)); + do + { + tmpProcess = this->currentProcess; + processToUse = (tmpProcess + 1) % this->processCount; + } while (tmpProcess != InterlockedCompareExchange(&this->currentProcess, processToUse, tmpProcess)); + } + } + else + { + // ensure the cookie did not carry a value outside of the possible processes + processToUse = (unsigned int)processInCookie % this->processCount; } - // try dispatch to that process + if(this->stickySessions && (processInCookie < 0)) + { + // Set cookie for sticky session with selected process + CHAR buffer [255]; + int cCount = sprintf (buffer, "iisnode.session.cookie=%d", processToUse); + ErrorIf( cCount < 0, E_OUTOFMEMORY ); + CheckError(request->GetHttpContext()->GetResponse()->SetHeader(HttpHeaderSetCookie, buffer, cCount, FALSE)); + } + // try dispatch to that process if (NULL != this->processes[processToUse]) { CheckError(this->processes[processToUse]->AcceptRequest(request)); @@ -172,7 +254,9 @@ HRESULT CNodeProcessManager::Dispatch(CNodeHttpStoredContext* request) if (request) { this->GetEventProvider()->Log(request->GetHttpContext(), L"iisnode failed to accept a request beacuse the application is recycling", WINEVENT_LEVEL_ERROR, request->GetActivityId()); - CProtocolBridge::SendEmptyResponse(request, 503, CNodeConstants::IISNODE_ERROR_FAILED_ACCEPT_REQUEST_APP_RECYCLE, _T("Service Unavailable"), IISNODE_ERROR_APPLICATION_IS_RECYCLING); + + BOOL fCompletionPosted = FALSE; + CProtocolBridge::SendEmptyResponse(request, 503, CNodeConstants::IISNODE_ERROR_FAILED_ACCEPT_REQUEST_APP_RECYCLE, _T("Service Unavailable"), IISNODE_ERROR_APPLICATION_IS_RECYCLING, &fCompletionPosted); } this->DecRef(); // incremented at the beginning of this method @@ -185,7 +269,8 @@ HRESULT CNodeProcessManager::Dispatch(CNodeHttpStoredContext* request) if (!CProtocolBridge::SendIisnodeError(request, hr)) { - CProtocolBridge::SendEmptyResponse(request, 503, CNodeConstants::IISNODE_ERROR_INIT_PROCESS_REQUEST, _T("Service Unavailable"), hr); + BOOL fCompletionPosted = FALSE; + CProtocolBridge::SendEmptyResponse(request, 503, CNodeConstants::IISNODE_ERROR_INIT_PROCESS_REQUEST, _T("Service Unavailable"), hr, &fCompletionPosted); } this->DecRef(); // incremented at the beginning of this method diff --git a/src/iisnode/cnodeprocessmanager.h b/src/iisnode/cnodeprocessmanager.h index 0c41737e..ac0d741f 100644 --- a/src/iisnode/cnodeprocessmanager.h +++ b/src/iisnode/cnodeprocessmanager.h @@ -22,6 +22,7 @@ class CNodeProcessManager CNodeApplication* application; CNodeProcess** processes; DWORD processCount; + BOOL stickySessions; unsigned int currentProcess; SRWLOCK srwlock; DWORD gracefulShutdownTimeout; @@ -40,6 +41,7 @@ class CNodeProcessManager CNodeProcessManager(CNodeApplication* application, IHttpContext* context); ~CNodeProcessManager(); + int ExtractStickySessionsProcess( PCSTR pszCookie ); HRESULT EmptyWorkingSet(); CNodeApplication* GetApplication(); HRESULT Initialize(IHttpContext* context); diff --git a/src/iisnode/cprotocolbridge.cpp b/src/iisnode/cprotocolbridge.cpp index 726758ff..4f0241ec 100644 --- a/src/iisnode/cprotocolbridge.cpp +++ b/src/iisnode/cprotocolbridge.cpp @@ -7,6 +7,9 @@ HRESULT CProtocolBridge::PostponeProcessing(CNodeHttpStoredContext* context, DWO delay.QuadPart = dueTime; delay.QuadPart *= -10000; // convert from ms to 100ns units + // will be dereferenced in AsynManager::Worker + context->ReferenceNodeHttpStoredContext(); + return async->SetTimer(context->GetAsyncContext(), &delay); } @@ -193,10 +196,12 @@ BOOL CProtocolBridge::SendIisnodeError(CNodeHttpStoredContext* ctx, HRESULT hr) ctx->SetPipe(INVALID_HANDLE_VALUE); } + BOOL fCompletionPosted = FALSE; CProtocolBridge::FinalizeResponseCore( ctx, RQ_NOTIFICATION_FINISH_REQUEST, hr, + &fCompletionPosted, ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider(), L"iisnode posts completion from SendIisnodeError", WINEVENT_LEVEL_VERBOSE); @@ -405,7 +410,7 @@ BOOL CProtocolBridge::SendDevError(CNodeHttpStoredContext* context, return false; } -HRESULT CProtocolBridge::SendEmptyResponse(CNodeHttpStoredContext* context, USHORT status, USHORT subStatus, PCTSTR reason, HRESULT hresult, BOOL disableCache) +HRESULT CProtocolBridge::SendEmptyResponse(CNodeHttpStoredContext* context, USHORT status, USHORT subStatus, PCTSTR reason, HRESULT hresult, BOOL *pfCompletionPosted, BOOL disableCache) { context->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(context->GetHttpContext(), L"iisnode request processing failed for reasons unrecognized by iisnode", WINEVENT_LEVEL_VERBOSE, context->GetActivityId()); @@ -427,6 +432,7 @@ HRESULT CProtocolBridge::SendEmptyResponse(CNodeHttpStoredContext* context, USHO context, RQ_NOTIFICATION_FINISH_REQUEST, hresult, + pfCompletionPosted, context->GetNodeApplication()->GetApplicationManager()->GetEventProvider(), L"iisnode posts completion from SendEmtpyResponse", WINEVENT_LEVEL_VERBOSE); @@ -459,7 +465,11 @@ void CProtocolBridge::SendEmptyResponse(IHttpContext* httpCtx, USHORT status, US if (!httpCtx->GetResponseHeadersSent()) { httpCtx->GetResponse()->Clear(); + + // Internal iisnode errors should probably not set fTrySkipCustomErrors since these are just empty status responses. + // Let IIS capture and replace these responses with more detailed messages depending on the custom error mode. httpCtx->GetResponse()->SetStatus(status, reason, subStatus, hresult); + if (disableCache) { httpCtx->GetResponse()->SetHeader(HttpHeaderCacheControl, "no-cache", 8, TRUE); @@ -474,6 +484,8 @@ HRESULT CProtocolBridge::InitiateRequest(CNodeHttpStoredContext* context) BOOL mainDebuggerPage = FALSE; IHttpContext* child = NULL; BOOL completionExpected; + BOOL fCompletionPosted = FALSE; + BOOL fReference = FALSE; // determine what the target path of the request is @@ -532,17 +544,22 @@ HRESULT CProtocolBridge::InitiateRequest(CNodeHttpStoredContext* context) CheckError(child->GetRequest()->SetUrl(context->GetTargetUrl(), context->GetTargetUrlLength(), FALSE)); context->SetChildContext(child); context->SetNextProcessor(CProtocolBridge::ChildContextCompleted); - + + context->ReferenceNodeHttpStoredContext(); + fReference = TRUE; + CheckError(context->GetHttpContext()->ExecuteRequest(TRUE, child, 0, NULL, &completionExpected)); if (!completionExpected) { - CProtocolBridge::ChildContextCompleted(S_OK, 0, context->GetOverlapped()); + CProtocolBridge::ChildContextCompleted(S_OK, 0, context->GetOverlapped(), &fCompletionPosted); + context->DereferenceNodeHttpStoredContext(); + fReference = FALSE; } } else { context->SetNextProcessor(CProtocolBridge::CreateNamedPipeConnection); - CProtocolBridge::CreateNamedPipeConnection(S_OK, 0, context->InitializeOverlapped()); + CProtocolBridge::CreateNamedPipeConnection(S_OK, 0, context->InitializeOverlapped(), &fCompletionPosted); } return S_OK; @@ -555,10 +572,15 @@ HRESULT CProtocolBridge::InitiateRequest(CNodeHttpStoredContext* context) context->SetChildContext(NULL); } + if(fReference) + { + context->DereferenceNodeHttpStoredContext(); + } + return hr; } -void WINAPI CProtocolBridge::ChildContextCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::ChildContextCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); @@ -584,6 +606,7 @@ void WINAPI CProtocolBridge::ChildContextCompleted(DWORD error, DWORD bytesTrans ctx, RQ_NOTIFICATION_CONTINUE, error, + pfCompletionPosted, ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider(), L"iisnode posts completion from ChildContextCompleted", WINEVENT_LEVEL_VERBOSE); @@ -591,7 +614,7 @@ void WINAPI CProtocolBridge::ChildContextCompleted(DWORD error, DWORD bytesTrans return; } -void WINAPI CProtocolBridge::CreateNamedPipeConnection(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::CreateNamedPipeConnection(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { HRESULT hr; CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); @@ -635,7 +658,7 @@ void WINAPI CProtocolBridge::CreateNamedPipeConnection(DWORD error, DWORD bytesT ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(ctx->GetHttpContext(), L"iisnode created named pipe connection to the node.exe process", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); - CProtocolBridge::SendHttpRequestHeaders(ctx); + CProtocolBridge::SendHttpRequestHeaders(ctx, pfCompletionPosted); return; @@ -649,7 +672,7 @@ void WINAPI CProtocolBridge::CreateNamedPipeConnection(DWORD error, DWORD bytesT { ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(ctx->GetHttpContext(), L"iisnode was unable to establish named pipe connection to the node.exe process because the named pipe server is too busy", WINEVENT_LEVEL_ERROR, ctx->GetActivityId()); - CProtocolBridge::SendEmptyResponse(ctx, 503, CNodeConstants::IISNODE_ERROR_PIPE_CONNECTION_TOO_BUSY, _T("Service Unavailable"), hr); + CProtocolBridge::SendEmptyResponse(ctx, 503, CNodeConstants::IISNODE_ERROR_PIPE_CONNECTION_TOO_BUSY, _T("Service Unavailable"), hr, pfCompletionPosted); } else { @@ -659,7 +682,8 @@ void WINAPI CProtocolBridge::CreateNamedPipeConnection(DWORD error, DWORD bytesT 500, CNodeConstants::IISNODE_ERROR_PIPE_CONNECTION, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); } } else if (ctx->GetNodeProcess()->HasProcessExited()) @@ -673,7 +697,8 @@ void WINAPI CProtocolBridge::CreateNamedPipeConnection(DWORD error, DWORD bytesT 500, CNodeConstants::IISNODE_ERROR_PIPE_CONNECTION_BEFORE_PROCESS_TERMINATED, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); } else { @@ -693,13 +718,14 @@ void WINAPI CProtocolBridge::CreateNamedPipeConnection(DWORD error, DWORD bytesT 500, CNodeConstants::IISNODE_ERROR_CONFIGURE_PIPE_CONNECTION, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); } return; } -void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context) +void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted) { HRESULT hr; DWORD length; @@ -754,11 +780,15 @@ void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context) CheckError(CHttpProtocol::SerializeRequestHeaders(context, context->GetBufferRef(), context->GetBufferSizeRef(), &length)); context->SetNextProcessor(CProtocolBridge::SendHttpRequestHeadersCompleted); + + context->ReferenceNodeHttpStoredContext(); if (WriteFile(context->GetPipe(), context->GetBuffer(), length, NULL, context->InitializeOverlapped())) { // completed synchronously + context->DereferenceNodeHttpStoredContext(); + etw->Log(context->GetHttpContext(), L"iisnode initiated sending http request headers to the node.exe process and completed synchronously", WINEVENT_LEVEL_VERBOSE, &activityId); @@ -768,16 +798,18 @@ void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context) // - see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365683(v=vs.85).aspx // and http://msdn.microsoft.com/en-us/library/windows/desktop/aa365538(v=vs.85).aspx - CProtocolBridge::SendHttpRequestHeadersCompleted(S_OK, 0, context->GetOverlapped()); + CProtocolBridge::SendHttpRequestHeadersCompleted(S_OK, 0, context->GetOverlapped(), pfCompletionPosted); } else { hr = GetLastError(); if (ERROR_IO_PENDING == hr) { + // async WriteFile - will be dereferenced in AsyncManager::Worker } else { + context->DereferenceNodeHttpStoredContext(); // error if (context->GetIsConnectionFromPool()) @@ -786,7 +818,7 @@ void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context) // try to create a brand new connection instead context->SetConnectionRetryCount(1); - CProtocolBridge::CreateNamedPipeConnection(S_OK, 0, context->GetOverlapped()); + CProtocolBridge::CreateNamedPipeConnection(S_OK, 0, context->GetOverlapped(), pfCompletionPosted); } else { @@ -798,7 +830,8 @@ void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context) 500, CNodeConstants::IISNODE_ERROR_FAILED_INIT_SEND_HTTP_HEADERS, _T("Internal Server Error"), - hr ); + hr , + pfCompletionPosted); } } } @@ -815,12 +848,13 @@ void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context) 500, CNodeConstants::IISNODE_ERROR_FAILED_SERIALIZE_HTTP_HEADERS, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); return; } -void WINAPI CProtocolBridge::SendHttpRequestHeadersCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::SendHttpRequestHeadersCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { HRESULT hr; CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); @@ -828,7 +862,7 @@ void WINAPI CProtocolBridge::SendHttpRequestHeadersCompleted(DWORD error, DWORD CheckError(error); ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(ctx->GetHttpContext(), L"iisnode finished sending http request headers to the node.exe process", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); - CProtocolBridge::ReadRequestBody(ctx); + CProtocolBridge::ReadRequestBody(ctx, pfCompletionPosted); return; Error: @@ -839,7 +873,7 @@ void WINAPI CProtocolBridge::SendHttpRequestHeadersCompleted(DWORD error, DWORD // try to create a brand new connection instead ctx->SetConnectionRetryCount(1); - CProtocolBridge::CreateNamedPipeConnection(S_OK, 0, ctx->GetOverlapped()); + CProtocolBridge::CreateNamedPipeConnection(S_OK, 0, ctx->GetOverlapped(), pfCompletionPosted); } else { @@ -849,22 +883,28 @@ void WINAPI CProtocolBridge::SendHttpRequestHeadersCompleted(DWORD error, DWORD 500, CNodeConstants::IISNODE_ERROR_FAILED_SEND_HTTP_HEADERS, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); } return; } -void CProtocolBridge::ReadRequestBody(CNodeHttpStoredContext* context) +void CProtocolBridge::ReadRequestBody(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted) { HRESULT hr; DWORD bytesReceived = 0; BOOL completionPending = FALSE; BOOL continueSynchronouslyNow = TRUE; + BOOL fReferenced = FALSE; if (0 < context->GetHttpContext()->GetRequest()->GetRemainingEntityBytes() || context->GetIsUpgrade()) { context->SetNextProcessor(CProtocolBridge::ReadRequestBodyCompleted); + + // if async, will be dereferenced in OnAsyncCompletion + context->ReferenceNodeHttpStoredContext(); + fReferenced = TRUE; if (context->GetIsChunked()) { @@ -877,6 +917,8 @@ void CProtocolBridge::ReadRequestBody(CNodeHttpStoredContext* context) if (!completionPending) { + context->DereferenceNodeHttpStoredContext(); + context->SetContinueSynchronously(TRUE); continueSynchronouslyNow = FALSE; context->SetBytesCompleted(bytesReceived); @@ -891,7 +933,7 @@ void CProtocolBridge::ReadRequestBody(CNodeHttpStoredContext* context) context->SetBytesCompleted(bytesReceived); if (continueSynchronouslyNow) { - CProtocolBridge::ReadRequestBodyCompleted(S_OK, 0, context->GetOverlapped()); + CProtocolBridge::ReadRequestBodyCompleted(S_OK, 0, context->GetOverlapped(), pfCompletionPosted); } } else @@ -910,17 +952,16 @@ void CProtocolBridge::ReadRequestBody(CNodeHttpStoredContext* context) if (context->GetIsUpgrade()) { - CProtocolBridge::FinalizeUpgradeResponse(context, S_OK); + CProtocolBridge::FinalizeUpgradeResponse(context, S_OK, pfCompletionPosted); } else if (context->GetIsChunked() && !context->GetIsLastChunk()) { // send the terminating zero-length chunk - - CProtocolBridge::ReadRequestBodyCompleted(S_OK, 0, context->GetOverlapped()); + CProtocolBridge::ReadRequestBodyCompleted(S_OK, 0, context->GetOverlapped(), pfCompletionPosted); } else { - CProtocolBridge::StartReadResponse(context); + CProtocolBridge::StartReadResponse(context, pfCompletionPosted); } } else @@ -930,7 +971,7 @@ void CProtocolBridge::ReadRequestBody(CNodeHttpStoredContext* context) if (context->GetIsUpgrade()) { - CProtocolBridge::FinalizeUpgradeResponse(context, HRESULT_FROM_WIN32(hr)); + CProtocolBridge::FinalizeUpgradeResponse(context, HRESULT_FROM_WIN32(hr), pfCompletionPosted); } else { @@ -938,14 +979,20 @@ void CProtocolBridge::ReadRequestBody(CNodeHttpStoredContext* context) 500, CNodeConstants::IISNODE_ERROR_FAILED_READ_REQ_BODY, _T("Internal Server Error"), - HRESULT_FROM_WIN32(hr) ); + HRESULT_FROM_WIN32(hr), + pfCompletionPosted); } } + if (fReferenced) + { + context->DereferenceNodeHttpStoredContext(); + } + return; } -void WINAPI CProtocolBridge::ReadRequestBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::ReadRequestBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); @@ -953,7 +1000,7 @@ void WINAPI CProtocolBridge::ReadRequestBodyCompleted(DWORD error, DWORD bytesTr { ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(ctx->GetHttpContext(), L"iisnode read a chunk of http request body", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); - CProtocolBridge::SendRequestBody(ctx, bytesTransfered); + CProtocolBridge::SendRequestBody(ctx, bytesTransfered, pfCompletionPosted); } else if (ERROR_HANDLE_EOF == error || 0 == bytesTransfered) { @@ -962,17 +1009,17 @@ void WINAPI CProtocolBridge::ReadRequestBodyCompleted(DWORD error, DWORD bytesTr if (ctx->GetIsUpgrade()) { - CProtocolBridge::FinalizeUpgradeResponse(ctx, S_OK); + CProtocolBridge::FinalizeUpgradeResponse(ctx, S_OK, pfCompletionPosted); } else if (ctx->GetIsChunked() && !ctx->GetIsLastChunk()) { // send the zero-length last chunk to indicate the end of a chunked entity body - CProtocolBridge::SendRequestBody(ctx, 0); + CProtocolBridge::SendRequestBody(ctx, 0, pfCompletionPosted); } else { - CProtocolBridge::StartReadResponse(ctx); + CProtocolBridge::StartReadResponse(ctx, pfCompletionPosted); } } else @@ -982,7 +1029,7 @@ void WINAPI CProtocolBridge::ReadRequestBodyCompleted(DWORD error, DWORD bytesTr if (ctx->GetIsUpgrade()) { - CProtocolBridge::FinalizeUpgradeResponse(ctx, error); + CProtocolBridge::FinalizeUpgradeResponse(ctx, error, pfCompletionPosted); } else { @@ -990,12 +1037,13 @@ void WINAPI CProtocolBridge::ReadRequestBodyCompleted(DWORD error, DWORD bytesTr 500, CNodeConstants::IISNODE_ERROR_FAILED_READ_REQ_BODY_COMPLETED, _T("Internal Server Error"), - error ); + error, + pfCompletionPosted); } } } -void CProtocolBridge::SendRequestBody(CNodeHttpStoredContext* context, DWORD chunkLength) +void CProtocolBridge::SendRequestBody(CNodeHttpStoredContext* context, DWORD chunkLength, BOOL *pfCompletionPosted) { // capture ETW provider since after a successful call to WriteFile the context may be asynchronously deleted @@ -1058,10 +1106,15 @@ void CProtocolBridge::SendRequestBody(CNodeHttpStoredContext* context, DWORD chu context->SetNextProcessor(CProtocolBridge::SendRequestBodyCompleted); + // will derefence in AsyncManager::Worker + context->ReferenceNodeHttpStoredContext(); + if (WriteFile(context->GetPipe(), (void*)buffer, length, NULL, context->InitializeOverlapped())) { // completed synchronously + context->DereferenceNodeHttpStoredContext(); + etw->Log(context->GetHttpContext(), L"iisnode initiated sending http request body chunk to the node.exe process and completed synchronously", WINEVENT_LEVEL_VERBOSE, &activityId); @@ -1071,7 +1124,7 @@ void CProtocolBridge::SendRequestBody(CNodeHttpStoredContext* context, DWORD chu // - see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365683(v=vs.85).aspx // and http://msdn.microsoft.com/en-us/library/windows/desktop/aa365538(v=vs.85).aspx - CProtocolBridge::SendRequestBodyCompleted(S_OK, 0, context->GetOverlapped()); + CProtocolBridge::SendRequestBodyCompleted(S_OK, 0, context->GetOverlapped(), pfCompletionPosted); } else { @@ -1087,7 +1140,7 @@ void CProtocolBridge::SendRequestBody(CNodeHttpStoredContext* context, DWORD chu } else if (ERROR_NO_DATA == hr) { - // Node.exe has closed the named pipe. This means it does not expect any more request data, but it does not mean there is no response. + context->DereferenceNodeHttpStoredContext(); // Node.exe has closed the named pipe. This means it does not expect any more request data, but it does not mean there is no response. // This may happen even for POST requests if the node.js application does not register event handlers for the 'data' or 'end' request events. // Ignore the write error and attempt to read the response instead (which might have been written by node.exe before the named pipe connection // was closed). This may also happen for WebSocket traffic. @@ -1098,16 +1151,17 @@ void CProtocolBridge::SendRequestBody(CNodeHttpStoredContext* context, DWORD chu if (context->GetIsUpgrade()) { - CProtocolBridge::FinalizeUpgradeResponse(context, S_OK); + CProtocolBridge::FinalizeUpgradeResponse(context, S_OK, pfCompletionPosted); } else { - CProtocolBridge::StartReadResponse(context); + CProtocolBridge::StartReadResponse(context, pfCompletionPosted); } } else { // error + context->DereferenceNodeHttpStoredContext(); etw->Log(context->GetHttpContext(), L"iisnode failed to initiate sending http request body chunk to the node.exe process", WINEVENT_LEVEL_ERROR, @@ -1115,7 +1169,7 @@ void CProtocolBridge::SendRequestBody(CNodeHttpStoredContext* context, DWORD chu if (context->GetIsUpgrade()) { - CProtocolBridge::FinalizeUpgradeResponse(context, hr); + CProtocolBridge::FinalizeUpgradeResponse(context, hr, pfCompletionPosted); } else { @@ -1123,7 +1177,8 @@ void CProtocolBridge::SendRequestBody(CNodeHttpStoredContext* context, DWORD chu 500, CNodeConstants::IISNODE_ERROR_FAILED_INIT_SEND_REQ_BODY, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted ); } } } @@ -1131,7 +1186,7 @@ void CProtocolBridge::SendRequestBody(CNodeHttpStoredContext* context, DWORD chu return; } -void WINAPI CProtocolBridge::SendRequestBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::SendRequestBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); @@ -1139,7 +1194,7 @@ void WINAPI CProtocolBridge::SendRequestBodyCompleted(DWORD error, DWORD bytesTr { ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(ctx->GetHttpContext(), L"iisnode finished sending http request body chunk to the node.exe process", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); - CProtocolBridge::ReadRequestBody(ctx); + CProtocolBridge::ReadRequestBody(ctx, pfCompletionPosted); } else { @@ -1148,7 +1203,7 @@ void WINAPI CProtocolBridge::SendRequestBodyCompleted(DWORD error, DWORD bytesTr if (ctx->GetIsUpgrade()) { - CProtocolBridge::FinalizeUpgradeResponse(ctx, error); + CProtocolBridge::FinalizeUpgradeResponse(ctx, error, pfCompletionPosted); } else { @@ -1156,19 +1211,20 @@ void WINAPI CProtocolBridge::SendRequestBodyCompleted(DWORD error, DWORD bytesTr 500, CNodeConstants::IISNODE_ERROR_FAILED_SEND_REQ_BODY, _T("Internal Server Error"), - error ); + error, + pfCompletionPosted); } } } -void CProtocolBridge::StartReadResponse(CNodeHttpStoredContext* context) +void CProtocolBridge::StartReadResponse(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted) { context->SetDataSize(0); context->SetParsingOffset(0); context->SetNextProcessor(CProtocolBridge::ProcessResponseStatusLine); context->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(context->GetHttpContext(), L"iisnode starting to read http response", WINEVENT_LEVEL_VERBOSE, context->GetActivityId()); - CProtocolBridge::ContinueReadResponse(context); + CProtocolBridge::ContinueReadResponse(context, pfCompletionPosted); } HRESULT CProtocolBridge::EnsureBuffer(CNodeHttpStoredContext* context) @@ -1222,7 +1278,7 @@ HRESULT CProtocolBridge::EnsureBuffer(CNodeHttpStoredContext* context) } -void CProtocolBridge::ContinueReadResponse(CNodeHttpStoredContext* context) +void CProtocolBridge::ContinueReadResponse(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted) { HRESULT hr; DWORD bytesRead = 0; @@ -1235,6 +1291,9 @@ void CProtocolBridge::ContinueReadResponse(CNodeHttpStoredContext* context) CheckError(CProtocolBridge::EnsureBuffer(context)); + // will dereference in AsyncManager::Worker + context->ReferenceNodeHttpStoredContext(); + if (ReadFile( context->GetPipe(), (char*)context->GetBuffer() + context->GetDataSize(), @@ -1243,6 +1302,7 @@ void CProtocolBridge::ContinueReadResponse(CNodeHttpStoredContext* context) context->InitializeOverlapped())) { // read completed synchronously + context->DereferenceNodeHttpStoredContext(); etw->Log(context->GetHttpContext(), L"iisnode initiated reading http response chunk and completed synchronously", WINEVENT_LEVEL_VERBOSE, @@ -1253,7 +1313,7 @@ void CProtocolBridge::ContinueReadResponse(CNodeHttpStoredContext* context) // - see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365683(v=vs.85).aspx // and http://msdn.microsoft.com/en-us/library/windows/desktop/aa365538(v=vs.85).aspx - context->GetAsyncContext()->completionProcessor(S_OK, bytesRead, context->GetOverlapped()); + context->GetAsyncContext()->completionProcessor(S_OK, bytesRead, context->GetOverlapped(), pfCompletionPosted); } else if (ERROR_IO_PENDING == (hr = GetLastError())) { @@ -1265,13 +1325,15 @@ void CProtocolBridge::ContinueReadResponse(CNodeHttpStoredContext* context) } else if (ERROR_BROKEN_PIPE == hr && context->GetCloseConnection()) { + context->DereferenceNodeHttpStoredContext(); // Termination of a connection indicates the end of the response body if Connection: close response header was present - CProtocolBridge::FinalizeResponse(context); + CProtocolBridge::FinalizeResponse(context, pfCompletionPosted); } else { // error + context->DereferenceNodeHttpStoredContext(); etw->Log(context->GetHttpContext(), L"iisnode failed to initialize reading of http response chunk", WINEVENT_LEVEL_ERROR, @@ -1281,7 +1343,8 @@ void CProtocolBridge::ContinueReadResponse(CNodeHttpStoredContext* context) 500, CNodeConstants::IISNODE_ERROR_FAILED_INIT_READ_RESPONSE, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); } return; @@ -1294,12 +1357,13 @@ void CProtocolBridge::ContinueReadResponse(CNodeHttpStoredContext* context) 500, CNodeConstants::IISNODE_ERROR_FAILED_ALLOC_MEM_READ_RESPONSE, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); return; } -void WINAPI CProtocolBridge::ProcessResponseStatusLine(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::ProcessResponseStatusLine(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { HRESULT hr; CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); @@ -1315,14 +1379,14 @@ void WINAPI CProtocolBridge::ProcessResponseStatusLine(DWORD error, DWORD bytesT L"iisnode finished processing http response status line", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); ctx->SetNextProcessor(CProtocolBridge::ProcessResponseHeaders); - CProtocolBridge::ProcessResponseHeaders(S_OK, 0, ctx->GetOverlapped()); + CProtocolBridge::ProcessResponseHeaders(S_OK, 0, ctx->GetOverlapped(), pfCompletionPosted); return; Error: if (ERROR_MORE_DATA == hr) { - CProtocolBridge::ContinueReadResponse(ctx); + CProtocolBridge::ContinueReadResponse(ctx, pfCompletionPosted); } else { @@ -1332,7 +1396,8 @@ void WINAPI CProtocolBridge::ProcessResponseStatusLine(DWORD error, DWORD bytesT 500, CNodeConstants::IISNODE_ERROR_FAILED_PROCESS_HTTP_STATUS_LINE, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); } return; @@ -1450,7 +1515,7 @@ HRESULT CProtocolBridge::AddDebugHeader(CNodeHttpStoredContext* context) return hr; } -void WINAPI CProtocolBridge::ProcessResponseHeaders(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::ProcessResponseHeaders(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { HRESULT hr; CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); @@ -1474,7 +1539,7 @@ void WINAPI CProtocolBridge::ProcessResponseHeaders(DWORD error, DWORD bytesTran ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(ctx->GetHttpContext(), L"iisnode determined the HTTP response does not have entity body", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); - CProtocolBridge::FinalizeResponse(ctx); + CProtocolBridge::FinalizeResponse(ctx, pfCompletionPosted); } else { @@ -1523,7 +1588,7 @@ void WINAPI CProtocolBridge::ProcessResponseHeaders(DWORD error, DWORD bytesTran ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(ctx->GetHttpContext(), L"iisnode finished processing http response headers", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); - ctx->GetAsyncContext()->completionProcessor(S_OK, 0, ctx->GetOverlapped()); + ctx->GetAsyncContext()->completionProcessor(S_OK, 0, ctx->GetOverlapped(), pfCompletionPosted); } return; @@ -1531,7 +1596,7 @@ void WINAPI CProtocolBridge::ProcessResponseHeaders(DWORD error, DWORD bytesTran if (ERROR_MORE_DATA == hr) { - CProtocolBridge::ContinueReadResponse(ctx); + CProtocolBridge::ContinueReadResponse(ctx, pfCompletionPosted); } else { @@ -1541,13 +1606,14 @@ void WINAPI CProtocolBridge::ProcessResponseHeaders(DWORD error, DWORD bytesTran 500, CNodeConstants::IISNODE_ERROR_FAILED_PROCESS_HTTP_HEADERS, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); } return; } -void WINAPI CProtocolBridge::ProcessChunkHeader(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::ProcessChunkHeader(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { HRESULT hr; CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); @@ -1564,7 +1630,8 @@ void WINAPI CProtocolBridge::ProcessChunkHeader(DWORD error, DWORD bytesTransfer L"iisnode finished processing http response body chunk header", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); ctx->SetNextProcessor(CProtocolBridge::ProcessResponseBody); - CProtocolBridge::ProcessResponseBody(S_OK, 0, ctx->GetOverlapped()); + + CProtocolBridge::ProcessResponseBody(S_OK, 0, ctx->GetOverlapped(), pfCompletionPosted); return; @@ -1572,7 +1639,7 @@ void WINAPI CProtocolBridge::ProcessChunkHeader(DWORD error, DWORD bytesTransfer if (ERROR_MORE_DATA == hr) { - CProtocolBridge::ContinueReadResponse(ctx); + CProtocolBridge::ContinueReadResponse(ctx, pfCompletionPosted); } else { @@ -1582,13 +1649,14 @@ void WINAPI CProtocolBridge::ProcessChunkHeader(DWORD error, DWORD bytesTransfer 500, CNodeConstants::IISNODE_ERROR_FAILED_PROCESS_RESPONSE_BODY_CHUNK_HEADER, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); } return; } -void CProtocolBridge::EnsureRequestPumpStarted(CNodeHttpStoredContext* context) +void CProtocolBridge::EnsureRequestPumpStarted(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted) { if (context->GetOpaqueFlagSet() && !context->GetRequestPumpStarted()) { @@ -1596,19 +1664,20 @@ void CProtocolBridge::EnsureRequestPumpStarted(CNodeHttpStoredContext* context) // only after the 101 Switching Protocols response had been sent. context->SetRequestPumpStarted(); - CProtocolBridge::ReadRequestBody(context->GetUpgradeContext()); + CProtocolBridge::ReadRequestBody(context->GetUpgradeContext(), pfCompletionPosted); ASYNC_CONTEXT* async = context->GetUpgradeContext()->GetAsyncContext(); async->RunSynchronousContinuations(); } } -void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { HRESULT hr; CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); HTTP_DATA_CHUNK* chunk; DWORD bytesSent; BOOL completionExpected; + BOOL fReferenced = FALSE; ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(ctx->GetHttpContext(), L"iisnode starting to process http response body", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); @@ -1646,6 +1715,10 @@ void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfe ctx->SetNextProcessor(CProtocolBridge::SendResponseBodyCompleted); ctx->SetBytesCompleted(bytesToSend); + // will be dereferenced in OnAsyncCompletion + ctx->ReferenceNodeHttpStoredContext(); + fReferenced = TRUE; + CheckError(ctx->GetHttpContext()->GetResponse()->WriteEntityChunks( chunk, 1, @@ -1659,6 +1732,7 @@ void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfe if (!completionExpected) { + ctx->DereferenceNodeHttpStoredContext(); ctx->SetContinueSynchronously(TRUE); } } @@ -1667,7 +1741,7 @@ void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfe // process next chunk of the chunked encoding ctx->SetNextProcessor(CProtocolBridge::ProcessChunkHeader); - CProtocolBridge::ProcessChunkHeader(S_OK, 0, ctx->GetOverlapped()); + CProtocolBridge::ProcessChunkHeader(S_OK, 0, ctx->GetOverlapped(), pfCompletionPosted); } else { @@ -1680,7 +1754,7 @@ void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfe { // read more body data - CProtocolBridge::ContinueReadResponse(ctx); + CProtocolBridge::ContinueReadResponse(ctx, pfCompletionPosted); } else { @@ -1688,11 +1762,11 @@ void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfe if (ctx->GetIsUpgrade()) { - CProtocolBridge::FinalizeUpgradeResponse(ctx, S_OK); + CProtocolBridge::FinalizeUpgradeResponse(ctx, S_OK, pfCompletionPosted); } else { - CProtocolBridge::FinalizeResponse(ctx); + CProtocolBridge::FinalizeResponse(ctx, pfCompletionPosted); } } @@ -1703,13 +1777,13 @@ void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfe { // Termination of a connection indicates the end of the upgraded request - CProtocolBridge::FinalizeUpgradeResponse(ctx, S_OK); + CProtocolBridge::FinalizeUpgradeResponse(ctx, S_OK, pfCompletionPosted); } else if (ERROR_BROKEN_PIPE == hr && ctx->GetCloseConnection()) { // Termination of a connection indicates the end of the response body if Connection: close response header was present - CProtocolBridge::FinalizeResponse(ctx); + CProtocolBridge::FinalizeResponse(ctx, pfCompletionPosted); } else { @@ -1718,7 +1792,7 @@ void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfe if (ctx->GetIsUpgrade()) { - CProtocolBridge::FinalizeUpgradeResponse(ctx, hr); + CProtocolBridge::FinalizeUpgradeResponse(ctx, hr, pfCompletionPosted); } else { @@ -1726,19 +1800,26 @@ void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfe 500, CNodeConstants::IISNODE_ERROR_FAILED_SEND_RESPONSE_BODY_CHUNK, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); } } + if (fReferenced) + { + ctx->DereferenceNodeHttpStoredContext(); + } + return; } -void WINAPI CProtocolBridge::SendResponseBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::SendResponseBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { HRESULT hr; CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); DWORD bytesSent; BOOL completionExpected = FALSE; + BOOL fReference = FALSE; CheckError(error); @@ -1754,7 +1835,7 @@ void WINAPI CProtocolBridge::SendResponseBodyCompleted(DWORD error, DWORD bytesT if (ctx->GetIsLastChunk() && ctx->GetChunkLength() == ctx->GetChunkTransmitted()) { - CProtocolBridge::FinalizeResponse(ctx); + CProtocolBridge::FinalizeResponse(ctx, pfCompletionPosted); } else { @@ -1765,12 +1846,18 @@ void WINAPI CProtocolBridge::SendResponseBodyCompleted(DWORD error, DWORD bytesT ctx->SetNextProcessor(CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush); ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(ctx->GetHttpContext(), L"iisnode initiated flushing http response body chunk", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); + ctx->ReferenceNodeHttpStoredContext(); + fReference = TRUE; ctx->GetHttpContext()->GetResponse()->Flush(TRUE, TRUE, &bytesSent, &completionExpected); } if (!completionExpected) { - CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush(S_OK, 0, ctx->GetOverlapped()); + CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush(S_OK, 0, ctx->GetOverlapped(), pfCompletionPosted); + if(fReference) + { + ctx->DereferenceNodeHttpStoredContext(); + } } } @@ -1782,7 +1869,7 @@ void WINAPI CProtocolBridge::SendResponseBodyCompleted(DWORD error, DWORD bytesT if (ctx->GetIsUpgrade()) { - CProtocolBridge::FinalizeUpgradeResponse(ctx, hr); + CProtocolBridge::FinalizeUpgradeResponse(ctx, hr, pfCompletionPosted); } else { @@ -1790,31 +1877,34 @@ void WINAPI CProtocolBridge::SendResponseBodyCompleted(DWORD error, DWORD bytesT 500, CNodeConstants::IISNODE_ERROR_FAILED_FLUSH_RESPONSE_BODY, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); } return; } -void WINAPI CProtocolBridge::ProcessUpgradeResponse(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::ProcessUpgradeResponse(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { - BOOL completionExpected; + BOOL completionExpected = FALSE; DWORD bytesSent; CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); ctx->SetNextProcessor(CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush); ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(ctx->GetHttpContext(), L"iisnode initiated flushing http upgrade response headers", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId()); + ctx->ReferenceNodeHttpStoredContext(); ctx->GetHttpContext()->GetResponse()->Flush(TRUE, TRUE, &bytesSent, &completionExpected); if (!completionExpected) { - CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush(S_OK, 0, ctx->GetOverlapped()); + CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush(S_OK, 0, ctx->GetOverlapped(), pfCompletionPosted); + ctx->DereferenceNodeHttpStoredContext(); } } -void WINAPI CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped) +void WINAPI CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * pfCompletionPosted) { HRESULT hr; CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped); @@ -1822,11 +1912,11 @@ void WINAPI CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush(DWORD CheckError(error); // Start reading the request bytes if the request was an accepted HTTP Upgrade - CProtocolBridge::EnsureRequestPumpStarted(ctx); + CProtocolBridge::EnsureRequestPumpStarted(ctx, pfCompletionPosted); // Continue on to reading the response body ctx->SetNextProcessor(CProtocolBridge::ProcessResponseBody); - CProtocolBridge::ProcessResponseBody(S_OK, 0, ctx->GetOverlapped()); + CProtocolBridge::ProcessResponseBody(S_OK, 0, ctx->GetOverlapped(), pfCompletionPosted); return; Error: @@ -1837,12 +1927,13 @@ void WINAPI CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush(DWORD 500, CNodeConstants::IISNODE_ERROR_FAILED_FLUSH_RESPONSE_BODY_PARTIAL_FLUSH, _T("Internal Server Error"), - hr ); + hr, + pfCompletionPosted); return; } -void CProtocolBridge::FinalizeUpgradeResponse(CNodeHttpStoredContext* context, HRESULT hresult) +void CProtocolBridge::FinalizeUpgradeResponse(CNodeHttpStoredContext* context, HRESULT hresult, BOOL *pfCompletionPosted) { context->SetNextProcessor(NULL); context->SetHresult(hresult); @@ -1858,7 +1949,12 @@ void CProtocolBridge::FinalizeUpgradeResponse(CNodeHttpStoredContext* context, H CloseHandle(context->GetPipe()); context->SetPipe(INVALID_HANDLE_VALUE); context->GetHttpContext()->GetResponse()->SetNeedDisconnect(); + + // will dereference in OnAsyncCompletion + context->ReferenceNodeHttpStoredContext(); + context->GetHttpContext()->PostCompletion(0); + *pfCompletionPosted = TRUE; } else { @@ -1869,7 +1965,7 @@ void CProtocolBridge::FinalizeUpgradeResponse(CNodeHttpStoredContext* context, H } } -void CProtocolBridge::FinalizeResponse(CNodeHttpStoredContext* context) +void CProtocolBridge::FinalizeResponse(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted) { context->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(context->GetHttpContext(), @@ -1889,12 +1985,13 @@ void CProtocolBridge::FinalizeResponse(CNodeHttpStoredContext* context) context, RQ_NOTIFICATION_CONTINUE, S_OK, + pfCompletionPosted, context->GetNodeApplication()->GetApplicationManager()->GetEventProvider(), L"iisnode posts completion from FinalizeResponse", WINEVENT_LEVEL_VERBOSE); } -HRESULT CProtocolBridge::FinalizeResponseCore(CNodeHttpStoredContext* context, REQUEST_NOTIFICATION_STATUS status, HRESULT error, CNodeEventProvider* log, PCWSTR etw, UCHAR level) +HRESULT CProtocolBridge::FinalizeResponseCore(CNodeHttpStoredContext* context, REQUEST_NOTIFICATION_STATUS status, HRESULT error, BOOL *pfCompletionPosted, CNodeEventProvider* log, PCWSTR etw, UCHAR level) { context->SetRequestNotificationStatus(status); context->SetNextProcessor(NULL); @@ -1910,8 +2007,12 @@ HRESULT CProtocolBridge::FinalizeResponseCore(CNodeHttpStoredContext* context, R if (0 == context->DecreasePendingAsyncOperationCount()) // decreases ref count increased in the ctor of CNodeApplication::Dispatch { + // will be dereferenced in OnAsyncCompletion + context->ReferenceNodeHttpStoredContext(); + log->Log(etw, level, context->GetActivityId()); context->GetHttpContext()->PostCompletion(0); + *pfCompletionPosted = TRUE; } return S_OK; @@ -1937,10 +2038,14 @@ HRESULT CProtocolBridge::SendDebugRedirect(CNodeHttpStoredContext* context, CNod response->SetStatus(301, "Moved Permanently"); CheckError(context->GetHttpContext()->GetResponse()->Redirect(path, FALSE, TRUE)); + + BOOL fCompletionPosted = FALSE; + CProtocolBridge::FinalizeResponseCore( context, RQ_NOTIFICATION_FINISH_REQUEST, S_OK, + &fCompletionPosted, log, L"iisnode redirected debugging request", WINEVENT_LEVEL_VERBOSE); diff --git a/src/iisnode/cprotocolbridge.h b/src/iisnode/cprotocolbridge.h index 5ce4ab80..054f5e59 100644 --- a/src/iisnode/cprotocolbridge.h +++ b/src/iisnode/cprotocolbridge.h @@ -11,48 +11,48 @@ class CProtocolBridge // utility static HRESULT PostponeProcessing(CNodeHttpStoredContext* context, DWORD dueTime); static HRESULT EnsureBuffer(CNodeHttpStoredContext* context); - static HRESULT FinalizeResponseCore(CNodeHttpStoredContext * context, REQUEST_NOTIFICATION_STATUS status, HRESULT error, CNodeEventProvider* log, PCWSTR etw, UCHAR level); + static HRESULT FinalizeResponseCore(CNodeHttpStoredContext * context, REQUEST_NOTIFICATION_STATUS status, HRESULT error, BOOL *pfCompletionPosted, CNodeEventProvider* log, PCWSTR etw, UCHAR level); static BOOL IsLocalCall(IHttpContext* ctx); static BOOL SendDevError(CNodeHttpStoredContext* context, USHORT status, USHORT subStatus, PCTSTR reason, HRESULT hresult, BOOL disableCache = FALSE); static HRESULT AddDebugHeader(CNodeHttpStoredContext* context); // processing stages - static void WINAPI ChildContextCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); - static void WINAPI CreateNamedPipeConnection(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); + static void WINAPI ChildContextCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); + static void WINAPI CreateNamedPipeConnection(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); - static void SendHttpRequestHeaders(CNodeHttpStoredContext* context); - static void WINAPI SendHttpRequestHeadersCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); + static void SendHttpRequestHeaders(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted); + static void WINAPI SendHttpRequestHeadersCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); - static void ReadRequestBody(CNodeHttpStoredContext* context); - static void WINAPI ReadRequestBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); + static void ReadRequestBody(CNodeHttpStoredContext* context, BOOL * fCompletionPosted); + static void WINAPI ReadRequestBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); - static void SendRequestBody(CNodeHttpStoredContext* context, DWORD chunkLength); - static void WINAPI SendRequestBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); + static void SendRequestBody(CNodeHttpStoredContext* context, DWORD chunkLength, BOOL *pfCompletionPosted); + static void WINAPI SendRequestBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); - static void StartReadResponse(CNodeHttpStoredContext* context); - static void ContinueReadResponse(CNodeHttpStoredContext* context); - static void WINAPI ProcessResponseStatusLine(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); - static void WINAPI ProcessResponseHeaders(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); + static void StartReadResponse(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted); + static void ContinueReadResponse(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted); + static void WINAPI ProcessResponseStatusLine(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); + static void WINAPI ProcessResponseHeaders(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); - static void WINAPI ProcessChunkHeader(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); - static void WINAPI ProcessResponseBody(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); + static void WINAPI ProcessChunkHeader(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); + static void WINAPI ProcessResponseBody(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); - static void WINAPI ProcessUpgradeResponse(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); - static void WINAPI ContinueProcessResponseBodyAfterPartialFlush(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); + static void WINAPI ProcessUpgradeResponse(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); + static void WINAPI ContinueProcessResponseBodyAfterPartialFlush(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); - static void EnsureRequestPumpStarted(CNodeHttpStoredContext* context); + static void EnsureRequestPumpStarted(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted); - static void FinalizeResponse(CNodeHttpStoredContext* context); - static void FinalizeUpgradeResponse(CNodeHttpStoredContext* context, HRESULT hresult); + static void FinalizeResponse(CNodeHttpStoredContext* context, BOOL *pfCompletionPosted); + static void FinalizeUpgradeResponse(CNodeHttpStoredContext* context, HRESULT hresult, BOOL *pfCompletionPosted); public: - static void WINAPI SendResponseBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped); + static void WINAPI SendResponseBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped, BOOL * fCompletionPosted); static HRESULT InitiateRequest(CNodeHttpStoredContext* context); static BOOL SendIisnodeError(IHttpContext* httpCtx, HRESULT hr); static BOOL SendIisnodeError(CNodeHttpStoredContext* ctx, HRESULT hr); - static HRESULT SendEmptyResponse(CNodeHttpStoredContext* context, USHORT status, USHORT subStatus, PCTSTR reason, HRESULT hresult, BOOL disableCache = FALSE); + static HRESULT SendEmptyResponse(CNodeHttpStoredContext* context, USHORT status, USHORT subStatus, PCTSTR reason, HRESULT hresult, BOOL *pfCompletionPosted, BOOL disableCache = FALSE); static HRESULT SendSyncResponse(IHttpContext* httpCtx, USHORT status, PCTSTR reason, HRESULT hresult, BOOL disableCache, PCSTR htmlBody); static void SendEmptyResponse(IHttpContext* httpCtx, USHORT status, USHORT subStatus, PCTSTR reason, HRESULT hresult, BOOL disableCache = FALSE); static HRESULT SendDebugRedirect(CNodeHttpStoredContext* context, CNodeEventProvider* log); diff --git a/src/samples/configuration/iisnode.yml b/src/samples/configuration/iisnode.yml index 415b0bf9..76e1be42 100644 --- a/src/samples/configuration/iisnode.yml +++ b/src/samples/configuration/iisnode.yml @@ -141,4 +141,7 @@ enableXFF: false # HTTP request headers; for a list of IIS server variables available see # http://msdn.microsoft.com/en-us/library/ms524602(v=vs.90).aspx; for example "AUTH_USER,AUTH_TYPE" -promoteServerVars: \ No newline at end of file +promoteServerVars: + +# skipIISCustomErrors - controls whether iisnode will try to skip IIS custom error handling when existingResponse="Auto" +skipIISCustomErrors: false \ No newline at end of file diff --git a/src/samples/configuration/readme.htm b/src/samples/configuration/readme.htm index 7dacba7b..1408ec6e 100644 --- a/src/samples/configuration/readme.htm +++ b/src/samples/configuration/readme.htm @@ -145,6 +145,8 @@

maxRequestBufferSize: 8192 # increasing from the default # maxConcurrentRequestsPerProcess: 512 - commented out setting + * skipIISCustomErrors - controls whether iisnode will try to skip IIS >httpErrors< custom error handling when existingResponse="Auto" + --> <iisnode @@ -175,6 +177,7 @@

enableXFF="false" promoteServerVars="" configOverrides="node.conf" + skipIISCustomErrors="false" /> <!-- @@ -335,6 +338,10 @@

# HTTP request headers; for a list of IIS server variables available see # http://msdn.microsoft.com/en-us/library/ms524602(v=vs.90).aspx; for example "AUTH_USER,AUTH_TYPE" -promoteServerVars: +promoteServerVars: + +# skipIISCustomErrors - controls whether iisnode will try to skip IIS >httpErrors< custom error handling when existingResponse="Auto" +skipIISCustomErrors: false + diff --git a/src/samples/configuration/web.config b/src/samples/configuration/web.config index fed9441e..51fb003a 100644 --- a/src/samples/configuration/web.config +++ b/src/samples/configuration/web.config @@ -109,6 +109,8 @@ nodeProcessCountPerApplication: 2 maxRequestBufferSize: 8192 # increasing from the default # maxConcurrentRequestsPerProcess: 512 - commented out setting + + * skipIISCustomErrors - controls whether iisnode will try to skip IIS custom error handling when existingResponse="Auto" --> @@ -140,6 +142,7 @@ enableXFF="false" promoteServerVars="" configOverrides="iisnode.yml" + skipIISCustomErrors="false" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file