diff --git a/src/config/iisnode_schema.xml b/src/config/iisnode_schema.xml index 3ff8109..578cea4 100644 --- a/src/config/iisnode_schema.xml +++ b/src/config/iisnode_schema.xml @@ -50,8 +50,9 @@ 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 99a641e..87b4578 100644 --- a/src/config/iisnode_schema_x64.xml +++ b/src/config/iisnode_schema_x64.xml @@ -50,8 +50,9 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/ - + + diff --git a/src/iisnode/cfilewatcher.cpp b/src/iisnode/cfilewatcher.cpp index d2723b2..51ab3d2 100644 --- a/src/iisnode/cfilewatcher.cpp +++ b/src/iisnode/cfilewatcher.cpp @@ -1,7 +1,8 @@ #include "precomp.h" CFileWatcher::CFileWatcher() - : completionPort(NULL), worker(NULL), directories(NULL), uncFileSharePollingInterval(0) + : completionPort(NULL), worker(NULL), directories(NULL), uncFileSharePollingInterval(0), + configOverridesFileName(NULL), configOverridesFileNameLength(0) { InitializeCriticalSection(&this->syncRoot); } @@ -38,6 +39,12 @@ CFileWatcher::~CFileWatcher() delete currentDirectory; } + if (NULL != this->configOverridesFileName) + { + delete [] this->configOverridesFileName; + this->configOverridesFileName = NULL; + } + DeleteCriticalSection(&this->syncRoot); } @@ -48,6 +55,14 @@ HRESULT CFileWatcher::Initialize(IHttpContext* context) ErrorIf(NULL == (this->completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1)), GetLastError()); this->uncFileSharePollingInterval = CModuleConfiguration::GetUNCFileChangesPollingInterval(context); + LPWSTR overrides = CModuleConfiguration::GetConfigOverrides(context); + if (overrides && *overrides != L'\0') + { + this->configOverridesFileNameLength = wcslen(overrides); + ErrorIf(NULL == (this->configOverridesFileName = new WCHAR[this->configOverridesFileNameLength + 1]), ERROR_NOT_ENOUGH_MEMORY); + wcscpy(this->configOverridesFileName, overrides); + } + return S_OK; Error: @@ -271,6 +286,13 @@ HRESULT CFileWatcher::WatchFile(PCWSTR directoryName, DWORD directoryNameLength, file->wildcard = wildcard; this->GetWatchedFileTimestamp(file, &file->lastWrite); + // determine if this is the yaml config file + + if (this->configOverridesFileNameLength == (endFileName - startFileName)) + { + file->yamlConfig = 0 == memcmp(this->configOverridesFileName, startFileName, this->configOverridesFileNameLength * sizeof WCHAR); + } + // find matching directory watcher entry directory = this->directories; @@ -531,6 +553,14 @@ BOOL CFileWatcher::ScanDirectory(WatchedDirectory* directory, BOOL unc) if (S_OK == CFileWatcher::GetWatchedFileTimestamp(file, ×tamp) && 0 != memcmp(×tamp, &file->lastWrite, sizeof FILETIME)) { + if (file->yamlConfig) + { + // the node.config file has changed + // invalidate the configuration such that on next message it will be re-created + + CModuleConfiguration::Invalidate(); + } + memcpy(&file->lastWrite, ×tamp, sizeof FILETIME); file->callback(file->manager, file->application); return TRUE; diff --git a/src/iisnode/cfilewatcher.h b/src/iisnode/cfilewatcher.h index 515de02..92801c8 100644 --- a/src/iisnode/cfilewatcher.h +++ b/src/iisnode/cfilewatcher.h @@ -18,6 +18,7 @@ private: BOOL unc; BOOL wildcard; FILETIME lastWrite; + BOOL yamlConfig; struct _WatchedFile* next; } WatchedFile; @@ -35,6 +36,8 @@ private: WatchedDirectory* directories; DWORD uncFileSharePollingInterval; CRITICAL_SECTION syncRoot; + LPWSTR configOverridesFileName; + DWORD configOverridesFileNameLength; static unsigned int WINAPI Worker(void* arg); BOOL ScanDirectory(WatchedDirectory* directory, BOOL unc); diff --git a/src/iisnode/cmoduleconfiguration.cpp b/src/iisnode/cmoduleconfiguration.cpp index 7637ee4..a93737b 100644 --- a/src/iisnode/cmoduleconfiguration.cpp +++ b/src/iisnode/cmoduleconfiguration.cpp @@ -4,11 +4,15 @@ IHttpServer* CModuleConfiguration::server = NULL; HTTP_MODULE_ID CModuleConfiguration::moduleId = NULL; +BOOL CModuleConfiguration::invalid = FALSE; + CModuleConfiguration::CModuleConfiguration() : nodeProcessCommandLine(NULL), logDirectoryNameSuffix(NULL), debuggerPathSegment(NULL), debugPortRange(NULL), debugPortStart(0), debugPortEnd(0), node_env(NULL), watchedFiles(NULL), - enableXFF(FALSE), promoteServerVars(NULL) + enableXFF(FALSE), promoteServerVars(NULL), promoteServerVarsRaw(NULL), configOverridesFileName(NULL), + configOverrides(NULL) { + InitializeSRWLock(&this->srwlock); } CModuleConfiguration::~CModuleConfiguration() @@ -56,6 +60,29 @@ CModuleConfiguration::~CModuleConfiguration() delete [] this->promoteServerVars; this->promoteServerVars = NULL; } + + if (NULL != this->promoteServerVarsRaw) + { + delete [] this->promoteServerVarsRaw; + this->promoteServerVarsRaw = NULL; + } + + if (NULL != this->configOverridesFileName) + { + delete [] this->configOverridesFileName; + this->configOverridesFileName = NULL; + } + + if (NULL != this->configOverrides) + { + delete [] this->configOverrides; + this->configOverrides = NULL; + } +} + +void CModuleConfiguration::Invalidate() +{ + CModuleConfiguration::invalid = TRUE; } HRESULT CModuleConfiguration::Initialize(IHttpServer* server, HTTP_MODULE_ID moduleId) @@ -437,6 +464,509 @@ Error: return hr; } +HRESULT CModuleConfiguration::GetDWORD(char* str, DWORD* value) +{ + HRESULT hr; + + if (str) + { + long v = atol(str); + ErrorIf((v == LONG_MAX || v == LONG_MIN) && errno == ERANGE, E_FAIL); + *value = (DWORD)v; + } + + return S_OK; +Error: + return hr; +} + +HRESULT CModuleConfiguration::GetBOOL(char* str, BOOL* value) +{ + if (!str) + { + *value = FALSE; + } + else if (0 == strcmpi(str, "false") || 0 == strcmpi(str, "0") || 0 == strcmpi(str, "no")) + { + *value = FALSE; + } + else if (0 == strcmpi(str, "true") || 0 == strcmpi(str, "1") || 0 == strcmpi(str, "yes")) + { + *value = TRUE; + } + else + { + return E_FAIL; + } + + return S_OK; +} + +HRESULT CModuleConfiguration::GetString(char* str, LPWSTR* value) +{ + HRESULT hr; + int wcharSize, bytesConverted; + + if (*value) + { + delete [] *value; + *value = NULL; + } + + if (!str) + { + str = ""; + } + + ErrorIf(0 == (wcharSize = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0)), GetLastError()); + ErrorIf(NULL == (*value = new WCHAR[wcharSize]), ERROR_NOT_ENOUGH_MEMORY); + ErrorIf(wcharSize != MultiByteToWideChar(CP_ACP, 0, str, -1, *value, wcharSize), GetLastError()); + + return S_OK; +Error: + return hr; +} + +HRESULT CModuleConfiguration::ApplyConfigOverrideKeyValue(IHttpContext* context, CModuleConfiguration* config, char* keyStart, char* keyEnd, char* valueStart, char* valueEnd) +{ + HRESULT hr; + + keyEnd++; + *keyEnd = 0; + + if (valueEnd) + { + valueEnd++; + *valueEnd = 0; + } + + if (0 == strcmpi(keyStart, "asyncCompletionThreadCount")) + { + CheckError(GetDWORD(valueStart, &config->asyncCompletionThreadCount)); + } + else if (0 == strcmpi(keyStart, "nodeProcessCountPerApplication")) + { + CheckError(GetDWORD(valueStart, &config->nodeProcessCountPerApplication)); + } + else if (0 == strcmpi(keyStart, "maxConcurrentRequestsPerProcess")) + { + CheckError(GetDWORD(valueStart, &config->maxConcurrentRequestsPerProcess)); + } + else if (0 == strcmpi(keyStart, "maxNamedPipeConnectionRetry")) + { + CheckError(GetDWORD(valueStart, &config->maxNamedPipeConnectionRetry)); + } + else if (0 == strcmpi(keyStart, "namedPipeConnectionRetryDelay")) + { + CheckError(GetDWORD(valueStart, &config->namedPipeConnectionRetryDelay)); + } + else if (0 == strcmpi(keyStart, "maxNamedPipeConnectionPoolSize")) + { + CheckError(GetDWORD(valueStart, &config->maxNamedPipeConnectionPoolSize)); + } + else if (0 == strcmpi(keyStart, "maxNamedPipePooledConnectionAge")) + { + CheckError(GetDWORD(valueStart, &config->maxNamedPipePooledConnectionAge)); + } + else if (0 == strcmpi(keyStart, "initialRequestBufferSize")) + { + CheckError(GetDWORD(valueStart, &config->initialRequestBufferSize)); + } + else if (0 == strcmpi(keyStart, "maxRequestBufferSize")) + { + CheckError(GetDWORD(valueStart, &config->maxRequestBufferSize)); + } + else if (0 == strcmpi(keyStart, "uncFileChangesPollingInterval")) + { + CheckError(GetDWORD(valueStart, &config->uncFileChangesPollingInterval)); + } + else if (0 == strcmpi(keyStart, "gracefulShutdownTimeout")) + { + CheckError(GetDWORD(valueStart, &config->gracefulShutdownTimeout)); + } + else if (0 == strcmpi(keyStart, "logFileFlushInterval")) + { + CheckError(GetDWORD(valueStart, &config->logFileFlushInterval)); + } + else if (0 == strcmpi(keyStart, "maxLogFileSizeInKB")) + { + CheckError(GetDWORD(valueStart, &config->maxLogFileSizeInKB)); + } + else if (0 == strcmpi(keyStart, "loggingEnabled")) + { + CheckError(GetBOOL(valueStart, &config->loggingEnabled)); + } + else if (0 == strcmpi(keyStart, "appendToExistingLog")) + { + CheckError(GetBOOL(valueStart, &config->appendToExistingLog)); + } + else if (0 == strcmpi(keyStart, "devErrorsEnabled")) + { + CheckError(GetBOOL(valueStart, &config->devErrorsEnabled)); + } + else if (0 == strcmpi(keyStart, "flushResponse")) + { + CheckError(GetBOOL(valueStart, &config->flushResponse)); + } + else if (0 == strcmpi(keyStart, "debuggingEnabled")) + { + CheckError(GetBOOL(valueStart, &config->debuggingEnabled)); + } + else if (0 == strcmpi(keyStart, "enableXFF")) + { + CheckError(GetBOOL(valueStart, &config->enableXFF)); + } + else if (0 == strcmpi(keyStart, "logDirectoryNameSuffix")) + { + CheckError(GetString(valueStart, &config->logDirectoryNameSuffix)); + } + else if (0 == strcmpi(keyStart, "node_env")) + { + CheckError(GetString(valueStart, &config->node_env)); + } + else if (0 == strcmpi(keyStart, "debugPortRange")) + { + CheckError(GetString(valueStart, &config->debugPortRange)); + } + else if (0 == strcmpi(keyStart, "watchedFiles")) + { + CheckError(GetString(valueStart, &config->watchedFiles)); + } + else if (0 == strcmpi(keyStart, "promoteServerVars")) + { + CheckError(GetString(valueStart, &config->promoteServerVarsRaw)); + } + else if (0 == strcmpi(keyStart, "debuggerPathSegment")) + { + CheckError(GetString(valueStart, &config->debuggerPathSegment)); + config->debuggerPathSegmentLength = wcslen(config->debuggerPathSegment); + } + else if (0 == strcmpi(keyStart, "nodeProcessCommandLine")) + { + if (config->nodeProcessCommandLine) + { + delete [] config->nodeProcessCommandLine; + config->nodeProcessCommandLine = NULL; + } + + ErrorIf(NULL == (config->nodeProcessCommandLine = new char[MAX_PATH]), ERROR_NOT_ENOUGH_MEMORY); + if (valueStart) + { + strcpy(config->nodeProcessCommandLine, valueStart); + } + else + { + strcpy(config->nodeProcessCommandLine, ""); + } + } + + + return S_OK; +Error: + return hr; +} + +HRESULT CModuleConfiguration::ApplyYamlConfigOverrides(IHttpContext* context, CModuleConfiguration* config) +{ + HRESULT hr; + PCWSTR scriptTranslated; + DWORD scriptTranslatedLength; + HANDLE overridesHandle = INVALID_HANDLE_VALUE; + DWORD fileSize; + DWORD bytesRead; + char* content = NULL; + char* lineStart; + char* lineEnd; + char* colon; + char* comment; + char *keyStart, *keyEnd; + char *valueStart, *valueEnd; + + if (config->configOverrides == L'\0') + { + // no file name with config overrides specified, return success + + return S_OK; + } + + // construct absolute file name by replacing the script name in the script translated path with the config override file name + + if (!config->configOverridesFileName) + { + scriptTranslated = context->GetScriptTranslated(&scriptTranslatedLength); + ErrorIf(NULL == (config->configOverridesFileName = new WCHAR[scriptTranslatedLength + wcslen(config->configOverrides) + 1]), ERROR_NOT_ENOUGH_MEMORY); + wcscpy(config->configOverridesFileName, scriptTranslated); + while (scriptTranslatedLength > 0 && config->configOverridesFileName[scriptTranslatedLength] != L'\\') + scriptTranslatedLength--; + wcscpy(config->configOverridesFileName + scriptTranslatedLength + 1, config->configOverrides); + } + + // open configuration override file if it exists + + if (INVALID_HANDLE_VALUE == (overridesHandle = CreateFileW( + config->configOverridesFileName, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + 0, + NULL))) { + hr = GetLastError(); + + // if file does not exist, clean up and return success (i.e. no config overrides specified) + ErrorIf(ERROR_FILE_NOT_FOUND == hr, S_OK); + + // if other error occurred, return the error + ErrorIf(TRUE, hr); + } + + // read file content + + ErrorIf(INVALID_FILE_SIZE == (fileSize = GetFileSize(overridesHandle, NULL)), GetLastError()); + ErrorIf(0 == fileSize, S_OK); // empty file, clean up and return success + ErrorIf(NULL == (content = new char[fileSize + 1]), ERROR_NOT_ENOUGH_MEMORY); + ErrorIf(!ReadFile(overridesHandle, content, fileSize, &bytesRead, NULL), GetLastError()); + ErrorIf(fileSize != bytesRead, E_FAIL); + content[bytesRead] = 0; + CloseHandle(overridesHandle); + overridesHandle = INVALID_HANDLE_VALUE; + + // parse file + + lineStart = lineEnd = content; + while (*lineStart) + { + // determine the placement of a comment and colon as well as the end of the line + colon = comment = NULL; + while (*lineEnd && *lineEnd != '\r' && *lineEnd != '\n') + { + if (*lineEnd == ':') + { + if (!colon) + colon = lineEnd; + } + else if (*lineEnd == '#') + { + if (!comment) + comment = lineEnd; + } + + lineEnd++; + } + + // comment will be the sentinel of the end of this line + if (!comment) + comment = lineEnd; + + // skip whitespace at the end of this line and beginning of next + while (*lineEnd == ' ' || *lineEnd == '\r' || *lineEnd == '\n') + lineEnd++; + + // skip whitespace at the beginning of line + while (lineStart < comment && *lineStart == ' ') + lineStart++; + + if (lineStart < comment) + { + // there is a non-whitespace character before the end of the line or comment on that line + // assume : [#] syntax of the line + keyStart = lineStart; + ErrorIf(!colon, E_FAIL); // there is no colon on the line + ErrorIf(keyStart == colon, E_FAIL); // colon is the first non-whitespace character on the line + + // find end of key name + while (lineStart < colon && *lineStart != ' ') + lineStart++; + keyEnd = lineStart - 1; + + // skip whitespace between end of key name and colon + while (lineStart < colon && *lineStart == ' ') + lineStart++; + ErrorIf(lineStart != colon, E_FAIL); // non-whitespace character found + + // skip whitespace before value + lineStart++; + while (lineStart < comment && *lineStart == ' ') + lineStart++; + + if (lineStart == comment) + { + // empty value + valueStart = valueEnd = NULL; + } + else + { + valueStart = lineStart; + + // find end of value as a last non-whitespace character before comment + valueEnd = comment - 1; + while (valueEnd > valueStart && *valueEnd == ' ') + valueEnd--; + + CheckError(CModuleConfiguration::ApplyConfigOverrideKeyValue(context, config, keyStart, keyEnd, valueStart, valueEnd)); + } + + } + + // move on to the next line + lineStart = lineEnd; + } + + hr = S_OK; // fall through to cleanup in the Error section +Error: + + if (overridesHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(overridesHandle); + overridesHandle = INVALID_HANDLE_VALUE; + } + + if (content) + { + delete [] content; + content = NULL; + } + + return S_OK == hr ? S_OK : IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION_OVERRIDE; +} + +HRESULT CModuleConfiguration::TokenizePromoteServerVars(CModuleConfiguration* c) +{ + HRESULT hr; + size_t i; + size_t varLength; + LPWSTR start, end; + wchar_t terminator; + + if (c->promoteServerVarsRaw) + { + if (NULL != c->promoteServerVars) + { + for (int i = 0; i < c->promoteServerVarsCount; i++) + { + if (c->promoteServerVars[i]) + { + delete [] c->promoteServerVars[i]; + } + } + + delete [] c->promoteServerVars; + c->promoteServerVars = NULL; + } + + if (*c->promoteServerVarsRaw == L'\0') + { + c->promoteServerVarsCount = 0; + } + else + { + // determine number of server variables + + c->promoteServerVarsCount = 1; + start = c->promoteServerVarsRaw; + while (*start) + { + if (L',' == *start) + { + c->promoteServerVarsCount++; + } + + start++; + } + + // tokenize server variable names (comma delimited list) + + ErrorIf(NULL == (c->promoteServerVars = new char*[c->promoteServerVarsCount]), ERROR_NOT_ENOUGH_MEMORY); + RtlZeroMemory(c->promoteServerVars, c->promoteServerVarsCount * sizeof(char*)); + + i = 0; + end = c->promoteServerVarsRaw; + while (*end) + { + start = end; + while (*end && L',' != *end) + { + end++; + } + + if (start != end) + { + terminator = *end; + *end = L'\0'; + ErrorIf(0 != wcstombs_s(&varLength, NULL, 0, start, _TRUNCATE), ERROR_CAN_NOT_COMPLETE); + ErrorIf(NULL == (c->promoteServerVars[i] = new char[varLength]), ERROR_NOT_ENOUGH_MEMORY); + ErrorIf(0 != wcstombs_s(&varLength, c->promoteServerVars[i], varLength, start, _TRUNCATE), ERROR_CAN_NOT_COMPLETE); + i++; + *end = terminator; + } + + if (*end) + { + end++; + } + } + } + + delete [] c->promoteServerVarsRaw; + c->promoteServerVarsRaw = NULL; + } + + return S_OK; +Error: + return hr; +} + +HRESULT CModuleConfiguration::ApplyDefaults(CModuleConfiguration* c) +{ + if (0 == c->asyncCompletionThreadCount) + { + // default number of async completion threads is the number of processors + + SYSTEM_INFO info; + + GetSystemInfo(&info); + c->asyncCompletionThreadCount = 0 == info.dwNumberOfProcessors ? 4 : info.dwNumberOfProcessors; + } + + if (0 == c->nodeProcessCountPerApplication) + { + // default number of node.exe processes to create per node.js application + + SYSTEM_INFO info; + + GetSystemInfo(&info); + c->nodeProcessCountPerApplication = 0 == info.dwNumberOfProcessors ? 1 : info.dwNumberOfProcessors; + } + + return S_OK; +} + +HRESULT CModuleConfiguration::EnsureCurrent(IHttpContext* context, CModuleConfiguration* config) +{ + HRESULT hr; + + if (config && CModuleConfiguration::invalid) + { + // yaml config has changed and needs to be re-read; this condition was set by the CFileWatcher + + ENTER_SRW_EXCLUSIVE(config->srwlock) + + if (CModuleConfiguration::invalid) + { + CheckError(CModuleConfiguration::ApplyYamlConfigOverrides(context, config)); + CheckError(CModuleConfiguration::TokenizePromoteServerVars(config)); + CheckError(CModuleConfiguration::ApplyDefaults(config)); + CModuleConfiguration::invalid = FALSE; + } + + LEAVE_SRW_EXCLUSIVE(config->srwlock) + } + + return S_OK; +Error: + return hr; +} + HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfiguration** config) { HRESULT hr; @@ -444,14 +974,12 @@ HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfigurat IAppHostElement* section = NULL; LPWSTR commandLine = NULL; size_t i; - size_t varLength; - LPWSTR serverVars = NULL; - LPWSTR start, end; - wchar_t terminator; CheckNull(config); *config = (CModuleConfiguration*)context->GetMetadata()->GetModuleContextContainer()->GetModuleContext(moduleId); + CheckError(CModuleConfiguration::EnsureCurrent(context, *config)); + if (NULL == *config) { ErrorIf(NULL == (c = new CModuleConfiguration()), ERROR_NOT_ENOUGH_MEMORY); @@ -480,6 +1008,8 @@ HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfigurat CheckError(GetString(section, L"debuggerPortRange", &c->debugPortRange)); CheckError(GetString(section, L"watchedFiles", &c->watchedFiles)); CheckError(GetBOOL(section, L"enableXFF", &c->enableXFF)); + CheckError(GetString(section, L"promoteServerVars", &c->promoteServerVarsRaw)); + CheckError(GetString(section, L"configOverrides", &c->configOverrides)); // debuggerPathSegment @@ -494,89 +1024,22 @@ HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfigurat delete [] commandLine; commandLine = NULL; - // promoteServerVars + // apply config setting overrides from the optional YAML configuration file - CheckError(GetString(section, L"promoteServerVars", &serverVars)); - if (*serverVars == L'\0') - { - c->promoteServerVarsCount = 0; - } - else - { - // determine number of server variables + CheckError(CModuleConfiguration::ApplyYamlConfigOverrides(context, c)); - c->promoteServerVarsCount = 1; - start = serverVars; - while (*start) - { - if (L',' == *start) - { - c->promoteServerVarsCount++; - } + // tokenize promoteServerVars - start++; - } + CheckError(CModuleConfiguration::TokenizePromoteServerVars(c)); - // tokenize server variable names (comma delimited list) - - ErrorIf(NULL == (c->promoteServerVars = new char*[c->promoteServerVarsCount]), ERROR_NOT_ENOUGH_MEMORY); - RtlZeroMemory(c->promoteServerVars, c->promoteServerVarsCount * sizeof(char*)); - - i = 0; - end = serverVars; - while (*end) - { - start = end; - while (*end && L',' != *end) - { - end++; - } - - if (start != end) - { - terminator = *end; - *end = L'\0'; - ErrorIf(0 != wcstombs_s(&varLength, NULL, 0, start, _TRUNCATE), ERROR_CAN_NOT_COMPLETE); - ErrorIf(NULL == (c->promoteServerVars[i] = new char[varLength]), ERROR_NOT_ENOUGH_MEMORY); - ErrorIf(0 != wcstombs_s(&varLength, c->promoteServerVars[i], varLength, start, _TRUNCATE), ERROR_CAN_NOT_COMPLETE); - i++; - *end = terminator; - } - - if (*end) - { - end++; - } - } - } - - delete [] serverVars; - serverVars = NULL; + // done with section section->Release(); section = NULL; // apply defaults - if (0 == c->asyncCompletionThreadCount) - { - // default number of async completion threads is the number of processors - - SYSTEM_INFO info; - - GetSystemInfo(&info); - c->asyncCompletionThreadCount = 0 == info.dwNumberOfProcessors ? 4 : info.dwNumberOfProcessors; - } - - if (0 == c->nodeProcessCountPerApplication) - { - // default number of node.exe processes to create per node.js application - - SYSTEM_INFO info; - - GetSystemInfo(&info); - c->nodeProcessCountPerApplication = 0 == info.dwNumberOfProcessors ? 1 : info.dwNumberOfProcessors; - } + CheckError(CModuleConfiguration::ApplyDefaults(c)); // CR: check for ERROR_ALREADY_ASSIGNED to detect a race in creation of this section // CR: refcounting may be needed if synchronous code paths proove too long (race with config changes) @@ -600,12 +1063,6 @@ Error: commandLine = NULL; } - if (NULL != serverVars) - { - delete [] serverVars; - serverVars = NULL; - } - if (NULL != c) { delete c; @@ -750,6 +1207,11 @@ BOOL CModuleConfiguration::GetEnableXFF(IHttpContext* ctx) GETCONFIG(enableXFF) } +LPWSTR CModuleConfiguration::GetConfigOverrides(IHttpContext* ctx) +{ + GETCONFIG(configOverrides) +} + HRESULT CModuleConfiguration::GetDebugPortRange(IHttpContext* ctx, DWORD* start, DWORD* end) { HRESULT hr; diff --git a/src/iisnode/cmoduleconfiguration.h b/src/iisnode/cmoduleconfiguration.h index 6b62c0b..99390d3 100644 --- a/src/iisnode/cmoduleconfiguration.h +++ b/src/iisnode/cmoduleconfiguration.h @@ -37,13 +37,26 @@ private: BOOL enableXFF; char** promoteServerVars; int promoteServerVarsCount; + LPWSTR promoteServerVarsRaw; + LPWSTR configOverridesFileName; + static BOOL invalid; + SRWLOCK srwlock; + LPWSTR configOverrides; static IHttpServer* server; static HTTP_MODULE_ID moduleId; static HRESULT GetConfigSection(IHttpContext* context, IAppHostElement** section, OLECHAR* configElement = L"system.webServer/iisnode"); static HRESULT GetString(IAppHostElement* section, LPCWSTR propertyName, LPWSTR* value); static HRESULT GetBOOL(IAppHostElement* section, LPCWSTR propertyName, BOOL* value); + static HRESULT GetDWORD(char* str, DWORD* value); + static HRESULT GetBOOL(char* str, BOOL* value); + static HRESULT GetString(char* str, LPWSTR* value); static HRESULT GetDWORD(IAppHostElement* section, LPCWSTR propertyName, DWORD* value); + static HRESULT ApplyConfigOverrideKeyValue(IHttpContext* context, CModuleConfiguration* config, char* keyStart, char* keyEnd, char* valueStart, char* valueEnd); + static HRESULT ApplyYamlConfigOverrides(IHttpContext* context, CModuleConfiguration* config); + static HRESULT TokenizePromoteServerVars(CModuleConfiguration* c); + static HRESULT ApplyDefaults(CModuleConfiguration* c); + static HRESULT EnsureCurrent(IHttpContext* context, CModuleConfiguration* config); CModuleConfiguration(); ~CModuleConfiguration(); @@ -81,9 +94,12 @@ public: static DWORD GetMaxNamedPipePooledConnectionAge(IHttpContext* ctx); static BOOL GetEnableXFF(IHttpContext* ctx); static HRESULT GetPromoteServerVars(IHttpContext* ctx, char*** vars, int* count); + static LPWSTR GetConfigOverrides(IHttpContext* ctx); static HRESULT CreateNodeEnvironment(IHttpContext* ctx, DWORD debugPort, PCH namedPipe, PCH* env); + static void Invalidate(); + virtual void CleanupStoredContext(); }; diff --git a/src/iisnode/cnodeapplicationmanager.cpp b/src/iisnode/cnodeapplicationmanager.cpp index 1b9bfb0..ebd75b9 100644 --- a/src/iisnode/cnodeapplicationmanager.cpp +++ b/src/iisnode/cnodeapplicationmanager.cpp @@ -13,23 +13,20 @@ HRESULT CNodeApplicationManager::Initialize(IHttpContext* context) HRESULT hr = S_OK; CModuleConfiguration *config; - if (!this->initialized) + if (S_OK != (hr = CModuleConfiguration::GetConfig(context, &config))) + { + hr = IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION_OVERRIDE == hr ? hr : IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION; + } + else if (!this->initialized) { - if (S_OK != CModuleConfiguration::GetConfig(context, &config)) - { - hr = IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION; - } - else - { - ENTER_SRW_EXCLUSIVE(this->srwlock) + ENTER_SRW_EXCLUSIVE(this->srwlock) - if (!this->initialized) - { - hr = this->InitializeCore(context); - } - - LEAVE_SRW_EXCLUSIVE(this->srwlock) + if (!this->initialized) + { + hr = this->InitializeCore(context); } + + LEAVE_SRW_EXCLUSIVE(this->srwlock) } return hr; diff --git a/src/iisnode/cprotocolbridge.cpp b/src/iisnode/cprotocolbridge.cpp index 4c72724..00ba33b 100644 --- a/src/iisnode/cprotocolbridge.cpp +++ b/src/iisnode/cprotocolbridge.cpp @@ -45,20 +45,41 @@ BOOL CProtocolBridge::IsLocalCall(IHttpContext* ctx) BOOL CProtocolBridge::SendIisnodeError(IHttpContext* httpCtx, HRESULT hr) { - if (IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION == hr) + if (IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION == hr || IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION_OVERRIDE == hr) { if (CProtocolBridge::IsLocalCall(httpCtx)) { - CProtocolBridge::SendSyncResponse( - httpCtx, - 200, - "OK", - hr, - TRUE, - "iisnode was unable to read the configuration file. Make sure the web.config file syntax is correct. In particular, verify the " - " " - "iisnode configuration section matches the expected schema. The schema of the iisnode section that your version of iisnode requires is stored in the " - "%systemroot%\\system32\\inetsrv\\config\\schema\\iisnode_schema.xml file."); + switch (hr) { + + default: + return FALSE; + + case IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION: + CProtocolBridge::SendSyncResponse( + httpCtx, + 200, + "OK", + hr, + TRUE, + "iisnode was unable to read the configuration file. Make sure the web.config file syntax is correct. In particular, verify the " + " " + "iisnode configuration section matches the expected schema. The schema of the iisnode section that your version of iisnode requires is stored in the " + "%systemroot%\\system32\\inetsrv\\config\\schema\\iisnode_schema.xml file."); + break; + + case IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION_OVERRIDE: + CProtocolBridge::SendSyncResponse( + httpCtx, + 200, + "OK", + hr, + TRUE, + "iisnode was unable to read the configuration file node.config. Make sure the node.config file syntax is correct. For reference, check " + " " + "the sample node.config file. The property names recognized in the node.config file of your version of iisnode are stored in the " + "%systemroot%\\system32\\inetsrv\\config\\schema\\iisnode_schema.xml file."); + break; + }; return TRUE; } diff --git a/src/iisnode/errors.h b/src/iisnode/errors.h index 9b69c21..c60e708 100644 --- a/src/iisnode/errors.h +++ b/src/iisnode/errors.h @@ -9,5 +9,6 @@ #define IISNODE_ERROR_UNABLE_TO_CREATE_LOG_FILE 1029L #define IISNODE_ERROR_UNABLE_TO_CREATE_DEBUGGER_FILES 1030L #define IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION 1031L +#define IISNODE_ERROR_UNABLE_TO_READ_CONFIGURATION_OVERRIDE 1032L #endif \ No newline at end of file diff --git a/src/iisnode/iisnode.vcxproj b/src/iisnode/iisnode.vcxproj index 277852f..0d809af 100644 --- a/src/iisnode/iisnode.vcxproj +++ b/src/iisnode/iisnode.vcxproj @@ -257,6 +257,8 @@ copy /y $(ProjectDir)\..\config\* $(ProjectDir)\..\..\build\$(Configuration)\$(P + + @@ -316,6 +318,12 @@ copy /y $(ProjectDir)\..\config\* $(ProjectDir)\..\..\build\$(Configuration)\$(P + + + + + + @@ -326,6 +334,7 @@ copy /y $(ProjectDir)\..\config\* $(ProjectDir)\..\..\build\$(Configuration)\$(P + true diff --git a/src/iisnode/iisnode.vcxproj.filters b/src/iisnode/iisnode.vcxproj.filters index 43dbded..e00cec8 100644 --- a/src/iisnode/iisnode.vcxproj.filters +++ b/src/iisnode/iisnode.vcxproj.filters @@ -138,6 +138,12 @@ {18d24dd3-6582-41ad-856d-949ed0626792} + + {ebde3d11-88f9-44b3-ba68-1752293b7729} + + + {e8b955a6-1062-48cf-8799-e5481a1c759b} + @@ -600,6 +606,33 @@ Tests\functional\tests + + Samples\configuration + + + Tests\functional\www\124_node_config_override + + + Tests\functional\www\124_node_config_override + + + Tests\functional\www\124_node_config_override + + + Tests\functional\www\125_node_config_autoupdate + + + Tests\functional\www\125_node_config_autoupdate + + + Tests\functional\www\125_node_config_autoupdate + + + Tests\functional\tests + + + Tests\functional\tests + diff --git a/src/samples/configuration/node.config b/src/samples/configuration/node.config new file mode 100644 index 0000000..3716a6d --- /dev/null +++ b/src/samples/configuration/node.config @@ -0,0 +1,138 @@ +# The optional node.config file provides overrides of the iisnode configuration settings specified in web.config. + +# node_env - determines the environment (production, development, staging, ...) in which +# child node processes run; if nonempty, is propagated to the child node processes as their NODE_ENV +# environment variable; the default is the value of the IIS worker process'es NODE_ENV +# environment variable + +node_env: %node_env% + +# nodeProcessCommandLine - command line starting the node executable; in shared +# hosting environments this setting would typically be locked at the machine scope. + +# nodeProcessCommandLine: "%programfiles%\nodejs\node.exe" + +# nodeProcessCountPerApplication - number of node.exe processes that IIS will start per application; +# setting this value to 0 results in creating one node.exe process per each processor on the machine + +nodeProcessCountPerApplication: 1 + +# maxConcurrentRequestsPerProcess - maximum number of reqeusts one node process can +# handle at a time + +maxConcurrentRequestsPerProcess: 1024 + +# maxNamedPipeConnectionRetry - number of times IIS will retry to establish a named pipe connection with a +# node process in order to send a new HTTP request + +maxNamedPipeConnectionRetry: 24 + +# namedPipeConnectionRetryDelay - delay in milliseconds between connection retries + +namedPipeConnectionRetryDelay: 250 + +# maxNamedPipeConnectionPoolSize - maximum number of named pipe connections that will be kept in a connection pool; +# connection pooling helps improve the performance of applications that process a large number of short lived HTTP requests + +maxNamedPipeConnectionPoolSize: 512 + +# maxNamedPipePooledConnectionAge - age of a pooled connection in milliseconds after which the connection is not reused for +# subsequent requests + +maxNamedPipePooledConnectionAge: 30000 + +# asyncCompletionThreadCount - size of the IO thread pool maintained by the IIS module to process asynchronous IO; setting it +# to 0 (default) results in creating one thread per each processor on the machine + +asyncCompletionThreadCount: 0 + +# initialRequestBufferSize - initial size in bytes of a memory buffer allocated for a new HTTP request + +initialRequestBufferSize: 4096 + +# maxRequestBufferSize - maximum size in bytes of a memory buffer allocated per request; this is a hard limit of +# the serialized form of HTTP request or response headers block + +maxRequestBufferSize: 65536 + +# watchedFiles - semi-colon separated list of files that will be watched for changes; a change to a file causes the application to recycle; +# each entry consists of an optional directory name plus required file name which are relative to the directory where the main application entry point +# is located; wild cards are allowed in the file name portion only; for example: "*.js;node_modules\foo\lib\options.json;app_data\*.config.json" + +watchedFiles: *.js;node.config + +# uncFileChangesPollingInterval - applications are recycled when the underlying *.js file is modified; if the file resides +# on a UNC share, the only reliable way to detect such modifications is to periodically poll for them; this setting +# controls the polling interval + +uncFileChangesPollingInterval: 5000 + +# gracefulShutdownTimeout - when a node.js file is modified, all node processes handling running this application are recycled; +# this setting controls the time (in milliseconds) given for currently active requests to gracefully finish before the +# process is terminated; during this time, all new requests are already dispatched to a new node process based on the fresh version +# of the application + +gracefulShutdownTimeout: 60000 + +# loggingEnabled - controls whether stdout and stderr streams from node processes are captured and made available over HTTP + +loggingEnabled: true + +# logDirectoryNameSuffix - suffix of the directory name that will store files with stdout and stderr captures; directly name is created +# by appending this suffix to the file name of the node.js application; individual log files are stored in that directory, one per node +# process (in files of the form x.txt, where x is between 0 and nodeProcessCountPerApplication - 1); given a node.js application at +# http://localhost/node/hello.js, log files would by default be stored at http://localhost/node/hello.js.logs/0.txt (thrugh 3.txt); +# SECURITY NOTE: if log files contain sensitive information, this setting should be modified to contain enough entropy to be considered +# cryptographically secure; in most situations, a GUID is sufficient + +logDirectoryNameSuffix: logs + +# debuggingEnabled - controls whether the built-in debugger is available + +debuggingEnabled: true + +# debuggerPortRange - range of TCP ports that can be used for communication between the node-inspector debugger and the debugee; iisnode +# will round robin through this port range for subsequent debugging sessions and pick the next available (free) port to use from the range + +debuggerPortRange: 5058-6058 + +# debuggerPathSegment - URL path segment used to access the built-in node-inspector debugger; given a node.js application at +http://foo.com/bar/baz.js, the debugger can be accessed at http://foo.com/bar/baz.js/{debuggerPathSegment}, by default +http://foo.com/bar/baz.js/debug + +debuggerPathSegment: debug + +# maxLogFileSizeInKB - maximum size of a log file in KB; once a log file exceeds this limit it is truncated back to empty + +maxLogFileSizeInKB: 128 + +# appendToExistingLog - determines whether pre-existing log files are appended to or created fresh when a node process with a given ordinal +# number starts; appending may be useful to diagnose unorderly node process terminations or recycling + +appendToExistingLog: false + +# logFileFlushInterval - interval in milliseconds for flushing logs to the log files + +logFileFlushInterval: 5000 + +# devErrorsEnabled - controls how much information is sent back in the HTTP response to the browser when an error occurrs in iisnode; +# when true, error conditions in iisnode result in HTTP 200 response with the body containing error details; when false, +# iisnode will return generic HTTP 5xx responses + +devErrorsEnabled: true + +# flushResponse - controls whether each HTTP response body chunk is immediately flushed by iisnode; flushing each body chunk incurs +# CPU cost but may improve latency in streaming scenarios + +flushResponse: false + +# enableXFF - controls whether iisnode adds or modifies the X-Forwarded-For request HTTP header with the IP address of the remote host + +enableXFF: false + +# promoteServerVars - comma delimited list of IIS server variables that will be propagated to the node.exe process in the form of +# x-iisnode- +# 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 diff --git a/src/samples/configuration/readme.htm b/src/samples/configuration/readme.htm index dda8ab2..5eeb021 100644 --- a/src/samples/configuration/readme.htm +++ b/src/samples/configuration/readme.htm @@ -13,12 +13,13 @@ configuration

There are several configuration options that can be controlled from the system.webServer/iisnode - section of the configurtion file. Review web.config below for detailed description + section of the web.config configurtion file or the node.config file. Review web.config or node.config below for detailed description of them.

visit the node.js endpoint at hello.js
visit the logs at logs (only available after you first visit the endpoint)
- debug the hello.js endpoint at hello.js/debug (requires WebKit enabled browser)
+ debug the hello.js endpoint at hello.js/debug + (requires WebKit enabled browser)

code

var http = require('http');
@@ -130,6 +131,14 @@ console.log('Application started at location ' + process.env.PORT);
x-iisnode-<server_variable_name> 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" + * configOverrides - optional file name containing overrides of configuration settings of the iisnode section of web.config; + the format of the file is a small subset of YAML: each setting is represented as a <key>: <value> on a separate line + and comments start with # until the end of the line, e.g. + # This is a sample node.config file + nodeProcessCountPerApplication: 2 + maxRequestBufferSize: 8192 # increasing from the default + # maxConcurrentRequestsPerProcess: 512 - commented out setting + --> <iisnode @@ -143,7 +152,7 @@ console.log('Application started at location ' + process.env.PORT); asyncCompletionThreadCount="0" initialRequestBufferSize="4096" maxRequestBufferSize="65536" - watchedFiles="*.js" + watchedFiles="*.js;node.conf" uncFileChangesPollingInterval="5000" gracefulShutdownTimeout="60000" loggingEnabled="true" @@ -157,6 +166,7 @@ console.log('Application started at location ' + process.env.PORT); flushResponse="false" enableXFF="false" promoteServerVars="" + configOverrides="node.conf" /> <!-- @@ -171,5 +181,145 @@ console.log('Application started at location ' + process.env.PORT); </system.webServer> </configuration> +

+ node.config

+
# The optional node.config file provides overrides of the iisnode configuration settings specified in web.config.
+
+# node_env - determines the environment (production, development, staging, ...) in which
+# child node processes run; if nonempty, is propagated to the child node processes as their NODE_ENV
+# environment variable; the default is the value of the IIS worker process'es NODE_ENV
+# environment variable
+
+node_env: %node_env%
+
+# nodeProcessCommandLine - command line starting the node executable; in shared
+# hosting environments this setting would typically be locked at the machine scope.
+
+# nodeProcessCommandLine: "%programfiles%\nodejs\node.exe"
+
+# nodeProcessCountPerApplication - number of node.exe processes that IIS will start per application;
+# setting this value to 0 results in creating one node.exe process per each processor on the machine
+
+nodeProcessCountPerApplication: 1
+
+# maxConcurrentRequestsPerProcess - maximum number of reqeusts one node process can
+# handle at a time
+
+maxConcurrentRequestsPerProcess: 1024
+
+# maxNamedPipeConnectionRetry - number of times IIS will retry to establish a named pipe connection with a
+# node process in order to send a new HTTP request
+
+maxNamedPipeConnectionRetry: 24
+
+# namedPipeConnectionRetryDelay - delay in milliseconds between connection retries
+
+namedPipeConnectionRetryDelay: 250
+
+# maxNamedPipeConnectionPoolSize - maximum number of named pipe connections that will be kept in a connection pool;
+# connection pooling helps improve the performance of applications that process a large number of short lived HTTP requests
+
+maxNamedPipeConnectionPoolSize: 512
+
+# maxNamedPipePooledConnectionAge - age of a pooled connection in milliseconds after which the connection is not reused for
+# subsequent requests
+
+maxNamedPipePooledConnectionAge: 30000
+
+# asyncCompletionThreadCount - size of the IO thread pool maintained by the IIS module to process asynchronous IO; setting it
+# to 0 (default) results in creating one thread per each processor on the machine
+
+asyncCompletionThreadCount: 0
+
+# initialRequestBufferSize - initial size in bytes of a memory buffer allocated for a new HTTP request
+
+initialRequestBufferSize: 4096
+
+# maxRequestBufferSize - maximum size in bytes of a memory buffer allocated per request; this is a hard limit of
+# the serialized form of HTTP request or response headers block
+
+maxRequestBufferSize: 65536
+
+# watchedFiles - semi-colon separated list of files that will be watched for changes; a change to a file causes the application to recycle;
+# each entry consists of an optional directory name plus required file name which are relative to the directory where the main application entry point
+# is located; wild cards are allowed in the file name portion only; for example: "*.js;node_modules\foo\lib\options.json;app_data\*.config.json"
+
+watchedFiles: *.js;node.config
+
+# uncFileChangesPollingInterval - applications are recycled when the underlying *.js file is modified; if the file resides
+# on a UNC share, the only reliable way to detect such modifications is to periodically poll for them; this setting
+# controls the polling interval
+
+uncFileChangesPollingInterval: 5000
+
+# gracefulShutdownTimeout - when a node.js file is modified, all node processes handling running this application are recycled;
+# this setting controls the time (in milliseconds) given for currently active requests to gracefully finish before the
+# process is terminated; during this time, all new requests are already dispatched to a new node process based on the fresh version
+# of the application
+
+gracefulShutdownTimeout: 60000
+
+# loggingEnabled - controls whether stdout and stderr streams from node processes are captured and made available over HTTP
+
+loggingEnabled: true
+
+# logDirectoryNameSuffix - suffix of the directory name that will store files with stdout and stderr captures; directly name is created
+# by appending this suffix to the file name of the node.js application; individual log files are stored in that directory, one per node
+# process (in files of the form x.txt, where x is between 0 and nodeProcessCountPerApplication - 1); given a node.js application at
+# http://localhost/node/hello.js, log files would by default be stored at http://localhost/node/hello.js.logs/0.txt (thrugh 3.txt);
+# SECURITY NOTE: if log files contain sensitive information, this setting should be modified to contain enough entropy to be considered
+# cryptographically secure; in most situations, a GUID is sufficient
+
+logDirectoryNameSuffix: logs
+
+# debuggingEnabled - controls whether the built-in debugger is available
+
+debuggingEnabled: true
+
+# debuggerPortRange - range of TCP ports that can be used for communication between the node-inspector debugger and the debugee; iisnode
+# will round robin through this port range for subsequent debugging sessions and pick the next available (free) port to use from the range
+
+debuggerPortRange: 5058-6058
+
+# debuggerPathSegment - URL path segment used to access the built-in node-inspector debugger; given a node.js application at
+http://foo.com/bar/baz.js, the debugger can be accessed at http://foo.com/bar/baz.js/{debuggerPathSegment}, by default
+http://foo.com/bar/baz.js/debug
+
+debuggerPathSegment: debug
+
+# maxLogFileSizeInKB - maximum size of a log file in KB; once a log file exceeds this limit it is truncated back to empty
+
+maxLogFileSizeInKB: 128
+
+# appendToExistingLog - determines whether pre-existing log files are appended to or created fresh when a node process with a given ordinal
+# number starts; appending may be useful to diagnose unorderly node process terminations or recycling
+
+appendToExistingLog: false
+
+# logFileFlushInterval - interval in milliseconds for flushing logs to the log files
+
+logFileFlushInterval: 5000
+
+# devErrorsEnabled - controls how much information is sent back in the HTTP response to the browser when an error occurrs in iisnode;
+# when true, error conditions in iisnode result in HTTP 200 response with the body containing error details; when false,
+# iisnode will return generic HTTP 5xx responses
+
+devErrorsEnabled: true
+
+# flushResponse - controls whether each HTTP response body chunk is immediately flushed by iisnode; flushing each body chunk incurs
+# CPU cost but may improve latency in streaming scenarios
+
+flushResponse: false
+
+# enableXFF - controls whether iisnode adds or modifies the X-Forwarded-For request HTTP header with the IP address of the remote host
+
+enableXFF: false
+
+# promoteServerVars - comma delimited list of IIS server variables that will be propagated to the node.exe process in the form of
+# x-iisnode-<server_variable_name>
+# 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:
diff --git a/src/samples/configuration/web.config b/src/samples/configuration/web.config index 501335b..6041289 100644 --- a/src/samples/configuration/web.config +++ b/src/samples/configuration/web.config @@ -96,6 +96,14 @@ x-iisnode- 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" + * configOverrides - optional file name containing overrides of configuration settings of the iisnode section of web.config; + the format of the file is a small subset of YAML: each setting is represented as a : on a separate line + and comments start with # until the end of the line, e.g. + # This is a sample node.config file + nodeProcessCountPerApplication: 2 + maxRequestBufferSize: 8192 # increasing from the default + # maxConcurrentRequestsPerProcess: 512 - commented out setting + -->