diff --git a/IISIntegration.sln b/IISIntegration.sln index 41411cfd76..d93cac5243 100644 --- a/IISIntegration.sln +++ b/IISIntegration.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.2026 +VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 15.0.26730.03 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04B1EDB6-E967-4D25-89B9-E6F8304038CD}" ProjectSection(SolutionItems) = preProject @@ -44,8 +44,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeIISSample", "samples\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IISTestSite", "test\IISTestSite\IISTestSite.csproj", "{679FA2A2-898B-4320-884E-C2D294A97CE1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IISIntegration.IISServerFunctionalTests", "test\IISIntegration.IISServerFunctionalTests\IISIntegration.IISServerFunctionalTests.csproj", "{C745BBFD-7888-4038-B41B-6D1832D13878}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AspNetCore", "src\AspNetCore\AspNetCore.vcxproj", "{439824F9-1455-4CC4-BD79-B44FA0A16552}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IISLib", "src\IISLib\IISLib.vcxproj", "{4787A64F-9A3E-4867-A55A-70CB4B2B2FFE}" @@ -150,17 +148,6 @@ Global {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x64.Build.0 = Release|Any CPU {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x86.ActiveCfg = Release|Any CPU {679FA2A2-898B-4320-884E-C2D294A97CE1}.Release|x86.Build.0 = Release|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Debug|x64.ActiveCfg = Debug|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Debug|x64.Build.0 = Debug|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Debug|x86.ActiveCfg = Debug|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Debug|x86.Build.0 = Debug|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Release|Any CPU.Build.0 = Release|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Release|x64.ActiveCfg = Release|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Release|x64.Build.0 = Release|Any CPU - {C745BBFD-7888-4038-B41B-6D1832D13878}.Release|x86.ActiveCfg = Release|Any CPU {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|Any CPU.ActiveCfg = Debug|Win32 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.ActiveCfg = Debug|x64 {439824F9-1455-4CC4-BD79-B44FA0A16552}.Debug|x64.Build.0 = Debug|x64 @@ -225,7 +212,6 @@ Global {4E3E1F5C-CD52-4CC0-A35F-D1FA1685D2FA} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {9BC4AFCB-325D-4C81-8228-8CF301CE2F97} = {C74B8F36-FD2F-45C9-9B8A-00E7CF0126A9} {679FA2A2-898B-4320-884E-C2D294A97CE1} = {EF30B533-D715-421A-92B7-92FEF460AC9C} - {C745BBFD-7888-4038-B41B-6D1832D13878} = {EF30B533-D715-421A-92B7-92FEF460AC9C} {439824F9-1455-4CC4-BD79-B44FA0A16552} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} {4787A64F-9A3E-4867-A55A-70CB4B2B2FFE} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} {55494E58-E061-4C4C-A0A8-837008E72F85} = {04B1EDB6-E967-4D25-89B9-E6F8304038CD} diff --git a/samples/IISSample/Startup.cs b/samples/IISSample/Startup.cs index 4174c4b45f..138f993d9e 100644 --- a/samples/IISSample/Startup.cs +++ b/samples/IISSample/Startup.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.IISIntegration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -71,7 +72,16 @@ namespace IISSample var value = vars[key]; await context.Response.WriteAsync(key + ": " + value + Environment.NewLine); } + await context.Response.WriteAsync(Environment.NewLine); + if (context.Features.Get() != null) + { + await context.Response.WriteAsync("Websocket feature is enabled."); + } + else + { + await context.Response.WriteAsync("Websocket feature is disabled."); + } }); } diff --git a/samples/IISSample/web.config b/samples/IISSample/web.config index afc7a862f0..1ee540eb0f 100644 --- a/samples/IISSample/web.config +++ b/samples/IISSample/web.config @@ -18,4 +18,4 @@ --> - \ No newline at end of file + diff --git a/samples/NativeIISSample/Startup.cs b/samples/NativeIISSample/Startup.cs index 4010839be8..1a41f5a848 100644 --- a/samples/NativeIISSample/Startup.cs +++ b/samples/NativeIISSample/Startup.cs @@ -70,6 +70,16 @@ namespace NativeIISSample { await context.Response.WriteAsync(varName + ": " + context.GetIISServerVariable(varName) + Environment.NewLine); } + + await context.Response.WriteAsync(Environment.NewLine); + if (context.Features.Get() != null) + { + await context.Response.WriteAsync("Websocket feature is enabled."); + } + else + { + await context.Response.WriteAsync("Websocket feature is disabled."); + } }); } @@ -83,6 +93,7 @@ namespace NativeIISSample "REMOTE_PORT", "REMOTE_USER", "REQUEST_METHOD", + "WEBSOCKET_VERSION" }; public static void Main(string[] args) diff --git a/src/CommonLib/environmentvariablehash.h b/src/CommonLib/environmentvariablehash.h index dace338f5d..8fa054f2a9 100644 --- a/src/CommonLib/environmentvariablehash.h +++ b/src/CommonLib/environmentvariablehash.h @@ -7,6 +7,7 @@ #define HOSTING_STARTUP_ASSEMBLIES_NAME L"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=" #define HOSTING_STARTUP_ASSEMBLIES_VALUE L"Microsoft.AspNetCore.Server.IISIntegration" #define ASPNETCORE_IIS_AUTH_ENV_STR L"ASPNETCORE_IIS_HTTPAUTH=" +#define ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR L"ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED=" #define ASPNETCORE_IIS_AUTH_WINDOWS L"windows;" #define ASPNETCORE_IIS_AUTH_BASIC L"basic;" #define ASPNETCORE_IIS_AUTH_ANONYMOUS L"anonymous;" @@ -123,349 +124,6 @@ public: pEntry->Dereference(); } - static - VOID - CopyToMultiSz( - ENVIRONMENT_VAR_ENTRY * pEntry, - PVOID pvData - ) - { - STRU strTemp; - MULTISZ *pMultiSz = static_cast(pvData); - DBG_ASSERT(pMultiSz); - DBG_ASSERT(pEntry); - strTemp.Copy(pEntry->QueryName()); - strTemp.Append(pEntry->QueryValue()); - pMultiSz->Append(strTemp.QueryStr()); - } - - static - VOID - CopyToTable( - ENVIRONMENT_VAR_ENTRY * pEntry, - PVOID pvData - ) - { - // best effort copy, ignore the failure - ENVIRONMENT_VAR_ENTRY * pNewEntry = new ENVIRONMENT_VAR_ENTRY(); - if (pNewEntry != NULL) - { - pNewEntry->Initialize(pEntry->QueryName(), pEntry->QueryValue()); - ENVIRONMENT_VAR_HASH *pHash = static_cast(pvData); - DBG_ASSERT(pHash); - pHash->InsertRecord(pNewEntry); - // Need to dereference as InsertRecord references it now - pNewEntry->Dereference(); - } - } - - static - VOID - AppendEnvironmentVariables - ( - ENVIRONMENT_VAR_ENTRY * pEntry, - PVOID pvData - ) - { - UNREFERENCED_PARAMETER(pvData); - - HRESULT hr = S_OK; - DWORD dwResult = 0; - DWORD dwError = 0; - STRU struNameBuffer; - STACK_STRU(struValueBuffer, 300); - BOOL fFound = FALSE; - - // pEntry->QueryName includes the trailing =, remove it before calling stru - if (FAILED(hr = struNameBuffer.Copy(pEntry->QueryName()))) - { - goto Finished; - } - dwResult = struNameBuffer.LastIndexOf(L'='); - if (dwResult != -1) - { - struNameBuffer.QueryStr()[dwResult] = L'\0'; - if (FAILED(hr = struNameBuffer.SyncWithBuffer())) - { - goto Finished; - } - } - - dwResult = GetEnvironmentVariable(struNameBuffer.QueryStr(), struValueBuffer.QueryStr(), struValueBuffer.QuerySizeCCH()); - if (dwResult == 0) - { - dwError = GetLastError(); - // Windows API (e.g., CreateProcess) allows variable with empty string value - // in such case dwResult will be 0 and dwError will also be 0 - // As UI and CMD does not allow empty value, ignore this environment var - if (dwError != ERROR_ENVVAR_NOT_FOUND && dwError != ERROR_SUCCESS) - { - hr = HRESULT_FROM_WIN32(dwError); - goto Finished; - } - } - else if (dwResult > struValueBuffer.QuerySizeCCH()) - { - // have to increase the buffer and try get environment var again - struValueBuffer.Reset(); - struValueBuffer.Resize(dwResult + (DWORD)wcslen(pEntry->QueryValue()) + 2); // for null char and semicolon - dwResult = GetEnvironmentVariable(struNameBuffer.QueryStr(), - struValueBuffer.QueryStr(), - struValueBuffer.QuerySizeCCH()); - if (struValueBuffer.IsEmpty()) - { - hr = E_UNEXPECTED; - goto Finished; - } - fFound = TRUE; - } - else - { - fFound = TRUE; - } - - if (FAILED(hr = struValueBuffer.SyncWithBuffer())) - { - goto Finished; - } - - if (fFound) - { - if (FAILED(hr = struValueBuffer.Append(L";"))) - { - goto Finished; - } - } - if (FAILED(hr = struValueBuffer.Append(pEntry->QueryValue()))) - { - goto Finished; - } - - if (FAILED(hr = pEntry->Initialize(pEntry->QueryName(), struValueBuffer.QueryStr()))) - { - goto Finished; - } - - Finished: - // TODO besides calling SetLastError, is there anyway to propagate failures to set the environment variable? - return; - } - - static - VOID - SetEnvironmentVariables - ( - ENVIRONMENT_VAR_ENTRY * pEntry, - PVOID pvData - ) - { - UNREFERENCED_PARAMETER(pvData); - HRESULT hr = S_OK; - DWORD dwResult = 0; - STRU struNameBuffer; - - // pEntry->QueryName includes the trailing =, remove it before calling SetEnvironmentVariable. - if (FAILED(hr = struNameBuffer.Copy(pEntry->QueryName()))) - { - goto Finished; - } - dwResult = struNameBuffer.LastIndexOf(L'='); - if (dwResult != -1) - { - struNameBuffer.QueryStr()[dwResult] = L'\0'; - if (FAILED(hr = struNameBuffer.SyncWithBuffer())) - { - goto Finished; - } - } - - dwResult = SetEnvironmentVariable(struNameBuffer.QueryStr(), pEntry->QueryValue()); - if (dwResult == 0) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - - Finished: - return; - } - - static - HRESULT - InitEnvironmentVariablesTable - ( - _In_ ENVIRONMENT_VAR_HASH* pInEnvironmentVarTable, - BOOL fWindowsAuthEnabled, - BOOL fBasicAuthEnabled, - BOOL fAnonymousAuthEnabled, - ENVIRONMENT_VAR_HASH** ppEnvironmentVarTable - ) - { - HRESULT hr = S_OK; - BOOL fFound = FALSE; - DWORD dwResult, dwError; - STRU strIisAuthEnvValue; - STACK_STRU(strStartupAssemblyEnv, 1024); - ENVIRONMENT_VAR_ENTRY* pHostingEntry = NULL; - ENVIRONMENT_VAR_ENTRY* pIISAuthEntry = NULL; - ENVIRONMENT_VAR_HASH* pEnvironmentVarTable = NULL; - - pEnvironmentVarTable = new ENVIRONMENT_VAR_HASH(); - if (pEnvironmentVarTable == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - // - // few environment variables expected, small bucket size for hash table - // - if (FAILED(hr = pEnvironmentVarTable->Initialize(37 /*prime*/))) - { - goto Finished; - } - - // copy the envirable hash table (from configuration) to a temp one as we may need to remove elements - pInEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToTable, pEnvironmentVarTable); - if (pEnvironmentVarTable->Count() != pInEnvironmentVarTable->Count()) - { - // hash table copy failed - hr = E_UNEXPECTED; - goto Finished; - } - - pEnvironmentVarTable->FindKey((PWSTR)ASPNETCORE_IIS_AUTH_ENV_STR, &pIISAuthEntry); - if (pIISAuthEntry != NULL) - { - // user defined ASPNETCORE_IIS_HTTPAUTH in configuration, wipe it off - pIISAuthEntry->Dereference(); - pEnvironmentVarTable->DeleteKey((PWSTR)ASPNETCORE_IIS_AUTH_ENV_STR); - } - - if (fWindowsAuthEnabled) - { - strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_WINDOWS); - } - - if (fBasicAuthEnabled) - { - strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_BASIC); - } - - if (fAnonymousAuthEnabled) - { - strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_ANONYMOUS); - } - - if (strIisAuthEnvValue.IsEmpty()) - { - strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_NONE); - } - - pIISAuthEntry = new ENVIRONMENT_VAR_ENTRY(); - if (pIISAuthEntry == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - if (FAILED(hr = pIISAuthEntry->Initialize(ASPNETCORE_IIS_AUTH_ENV_STR, strIisAuthEnvValue.QueryStr())) || - FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISAuthEntry))) - { - goto Finished; - } - - // Compiler is complaining about conversion between PCWSTR and PWSTR here. - // Explictly casting. - pEnvironmentVarTable->FindKey((PWSTR)HOSTING_STARTUP_ASSEMBLIES_NAME, &pHostingEntry); - if (pHostingEntry != NULL) - { - // user defined ASPNETCORE_HOSTINGSTARTUPASSEMBLIES in configuration - // the value will be used in OutputEnvironmentVariables. Do nothing here - pHostingEntry->Dereference(); - pHostingEntry = NULL; - goto Skipped; - } - - //check whether ASPNETCORE_HOSTINGSTARTUPASSEMBLIES is defined in system - dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, - strStartupAssemblyEnv.QueryStr(), - strStartupAssemblyEnv.QuerySizeCCH()); - if (dwResult == 0) - { - dwError = GetLastError(); - // Windows API (e.g., CreateProcess) allows variable with empty string value - // in such case dwResult will be 0 and dwError will also be 0 - // As UI and CMD does not allow empty value, ignore this environment var - if (dwError != ERROR_ENVVAR_NOT_FOUND && dwError != ERROR_SUCCESS) - { - hr = HRESULT_FROM_WIN32(dwError); - goto Finished; - } - } - else if (dwResult > strStartupAssemblyEnv.QuerySizeCCH()) - { - // have to increase the buffer and try get environment var again - strStartupAssemblyEnv.Reset(); - strStartupAssemblyEnv.Resize(dwResult + (DWORD)wcslen(HOSTING_STARTUP_ASSEMBLIES_VALUE) + 1); - dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, - strStartupAssemblyEnv.QueryStr(), - strStartupAssemblyEnv.QuerySizeCCH()); - if (strStartupAssemblyEnv.IsEmpty()) - { - hr = E_UNEXPECTED; - goto Finished; - } - fFound = TRUE; - } - else - { - fFound = TRUE; - } - - strStartupAssemblyEnv.SyncWithBuffer(); - if (fFound) - { - strStartupAssemblyEnv.Append(L";"); - } - strStartupAssemblyEnv.Append(HOSTING_STARTUP_ASSEMBLIES_VALUE); - - // the environment variable was not defined, create it and add to hashtable - pHostingEntry = new ENVIRONMENT_VAR_ENTRY(); - if (pHostingEntry == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - if (FAILED(hr = pHostingEntry->Initialize(HOSTING_STARTUP_ASSEMBLIES_NAME, strStartupAssemblyEnv.QueryStr())) || - FAILED(hr = pEnvironmentVarTable->InsertRecord(pHostingEntry))) - { - goto Finished; - } - - Skipped: - *ppEnvironmentVarTable = pEnvironmentVarTable; - pEnvironmentVarTable = NULL; - - Finished: - if (pHostingEntry != NULL) - { - pHostingEntry->Dereference(); - pHostingEntry = NULL; - } - - if (pIISAuthEntry != NULL) - { - pIISAuthEntry->Dereference(); - pIISAuthEntry = NULL; - } - - if (pEnvironmentVarTable != NULL) - { - pEnvironmentVarTable->Clear(); - delete pEnvironmentVarTable; - pEnvironmentVarTable = NULL; - } - return hr; - } private: ENVIRONMENT_VAR_HASH(const ENVIRONMENT_VAR_HASH &); diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs index e536519707..013b15adfc 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs @@ -28,8 +28,26 @@ namespace Microsoft.AspNetCore.Server.IISIntegration private readonly ILogger _logger; private readonly string _pairingToken; private readonly IApplicationLifetime _applicationLifetime; + private readonly bool _isWebsocketsSupported; - public IISMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions options, string pairingToken, IAuthenticationSchemeProvider authentication, IApplicationLifetime applicationLifetime) + // Can't break public API, so creating a second constructor to propagate the isWebsocketsSupported flag. + public IISMiddleware(RequestDelegate next, + ILoggerFactory loggerFactory, + IOptions options, + string pairingToken, + IAuthenticationSchemeProvider authentication, + IApplicationLifetime applicationLifetime) + : this(next, loggerFactory, options, pairingToken, isWebsocketsSupported: true, authentication, applicationLifetime) + { + } + + public IISMiddleware(RequestDelegate next, + ILoggerFactory loggerFactory, + IOptions options, + string pairingToken, + bool isWebsocketsSupported, + IAuthenticationSchemeProvider authentication, + IApplicationLifetime applicationLifetime) { if (next == null) { @@ -63,6 +81,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration _pairingToken = pairingToken; _applicationLifetime = applicationLifetime; _logger = loggerFactory.CreateLogger(); + _isWebsocketsSupported = isWebsocketsSupported; } public async Task Invoke(HttpContext httpContext) @@ -118,6 +137,13 @@ namespace Microsoft.AspNetCore.Server.IISIntegration } } + // Remove the upgrade feature if websockets are not supported by ANCM. + // The feature must be removed on a per request basis as the Upgrade feature exists per request. + if (!_isWebsocketsSupported) + { + httpContext.Features.Set(null); + } + await _next(httpContext); } } diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IISSetupFilter.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IISSetupFilter.cs index 6c2aa54b95..cb2d97f0f7 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/IISSetupFilter.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/IISSetupFilter.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -12,11 +12,13 @@ namespace Microsoft.AspNetCore.Server.IISIntegration { private readonly string _pairingToken; private readonly PathString _pathBase; + private readonly bool _isWebsocketsSupported; - internal IISSetupFilter(string pairingToken, PathString pathBase) + internal IISSetupFilter(string pairingToken, PathString pathBase, bool isWebsocketsSupported) { _pairingToken = pairingToken; _pathBase = pathBase; + _isWebsocketsSupported = isWebsocketsSupported; } public Action Configure(Action next) @@ -25,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration { app.UsePathBase(_pathBase); app.UseForwardedHeaders(); - app.UseMiddleware(_pairingToken); + app.UseMiddleware(_pairingToken, _isWebsocketsSupported); next(app); }; } diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs index 7f1efba984..4a1ecd5f27 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs @@ -183,7 +183,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration bool IHttpResponseFeature.HasStarted => HasResponseStarted; - bool IHttpUpgradeFeature.IsUpgradableRequest => UpgradeAvailable; + // The UpgradeAvailable Feature is set on the first request to the server. + bool IHttpUpgradeFeature.IsUpgradableRequest => _websocketAvailability == WebsocketAvailabilityStatus.Available; bool IFeatureCollection.IsReadOnly => false; diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs index dc02f53a4b..f6cbe57255 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs @@ -28,7 +28,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration private const int PauseWriterThreshold = 65536; private const int ResumeWriterTheshold = PauseWriterThreshold / 2; - private static bool UpgradeAvailable = (Environment.OSVersion.Version >= new Version(6, 2)); + // TODO make this static again. + private static WebsocketAvailabilityStatus _websocketAvailability = WebsocketAvailabilityStatus.Uninitialized; protected readonly IntPtr _pInProcessHandler; @@ -59,6 +60,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration private const string NtlmString = "NTLM"; private const string NegotiateString = "Negotiate"; private const string BasicString = "Basic"; + private const string WebSocketVersionString = "WEBSOCKET_VERSION"; internal unsafe IISHttpContext(MemoryPool memoryPool, IntPtr pInProcessHandler, IISOptions options, IISHttpServer server) : base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.http_get_raw_request(pInProcessHandler)) @@ -121,7 +123,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration StatusCode = 200; RequestHeaders = new RequestHeaders(this); - HttpResponseHeaders = new HeaderCollection(); // TODO Optimize for known headers + HttpResponseHeaders = new HeaderCollection(); ResponseHeaders = HttpResponseHeaders; if (options.ForwardWindowsAuthentication) @@ -134,6 +136,26 @@ namespace Microsoft.AspNetCore.Server.IISIntegration } ResetFeatureCollection(); + + // Check if the Http upgrade feature is available in IIS. + // To check this, we can look at the server variable WEBSOCKET_VERSION + // And see if there is a version. Same check that Katana did: + // https://github.com/aspnet/AspNetKatana/blob/9f6e09af6bf203744feb5347121fe25f6eec06d8/src/Microsoft.Owin.Host.SystemWeb/OwinAppContext.cs#L125 + // Actively not locking here as acquiring a lock on every request will hurt perf more than checking the + // server variables a few extra times if a bunch of requests hit the server at the same time. + if (_websocketAvailability == WebsocketAvailabilityStatus.Uninitialized) + { + NativeMethods.http_get_server_variable(pInProcessHandler, WebSocketVersionString, out var webSocketsSupported); + var webSocketsAvailable = !string.IsNullOrEmpty(webSocketsSupported); + _websocketAvailability = webSocketsAvailable ? + WebsocketAvailabilityStatus.Available : + WebsocketAvailabilityStatus.NotAvailable; + } + + if (_websocketAvailability == WebsocketAvailabilityStatus.NotAvailable) + { + _currentIHttpUpgradeFeature = null; + } } RequestBody = new IISHttpRequestBody(this); @@ -534,5 +556,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration } return null; } + + private enum WebsocketAvailabilityStatus + { + Uninitialized = 1, + Available, + NotAvailable + } } } diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs index 89ecd56739..a96305d4d8 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs @@ -19,6 +19,7 @@ namespace Microsoft.AspNetCore.Hosting private static readonly string ServerPath = "APPL_PATH"; private static readonly string PairingToken = "TOKEN"; private static readonly string IISAuth = "IIS_HTTPAUTH"; + private static readonly string IISWebSockets = "IIS_WEBSOCKETS_SUPPORTED"; /// /// Configures the port and base path the server should listen on when running behind AspNetCoreModule. @@ -74,6 +75,14 @@ namespace Microsoft.AspNetCore.Hosting var path = hostBuilder.GetSetting(ServerPath) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPath}"); var pairingToken = hostBuilder.GetSetting(PairingToken) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{PairingToken}"); var iisAuth = hostBuilder.GetSetting(IISAuth) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{IISAuth}"); + var websocketsSupported = hostBuilder.GetSetting(IISWebSockets) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{IISWebSockets}"); + + bool isWebSocketsSupported; + if (!bool.TryParse(websocketsSupported, out isWebSocketsSupported)) + { + // If the websocket support variable is not set, we will always fallback to assuming websockets are enabled. + isWebSocketsSupported = (Environment.OSVersion.Version >= new Version(6, 2)); + } if (!string.IsNullOrEmpty(port) && !string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(pairingToken)) { @@ -107,7 +116,7 @@ namespace Microsoft.AspNetCore.Hosting // Delay register the url so users don't accidently overwrite it. hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, address); hostBuilder.PreferHostingUrls(true); - services.AddSingleton(new IISSetupFilter(pairingToken, new PathString(path))); + services.AddSingleton(new IISSetupFilter(pairingToken, new PathString(path), isWebSocketsSupported)); services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; diff --git a/src/RequestHandler/RequestHandler.vcxproj b/src/RequestHandler/RequestHandler.vcxproj index 5a0ff2a9cf..cd074c4d75 100644 --- a/src/RequestHandler/RequestHandler.vcxproj +++ b/src/RequestHandler/RequestHandler.vcxproj @@ -208,6 +208,7 @@ + diff --git a/src/RequestHandler/RequestHandler.vcxproj.filters b/src/RequestHandler/RequestHandler.vcxproj.filters index ff33d9149e..fb47904137 100644 --- a/src/RequestHandler/RequestHandler.vcxproj.filters +++ b/src/RequestHandler/RequestHandler.vcxproj.filters @@ -80,6 +80,7 @@ + diff --git a/src/RequestHandler/dllmain.cxx b/src/RequestHandler/dllmain.cxx index 1167833d43..3036600948 100644 --- a/src/RequestHandler/dllmain.cxx +++ b/src/RequestHandler/dllmain.cxx @@ -112,8 +112,6 @@ InitializeGlobalConfiguration( g_fNsiApiNotSupported = TRUE; } - // WebSocket is supported on Win8 and above only - // todo: test on win7 g_fWebSocketSupported = IsWindows8OrGreater(); g_fGlobalInitialize = TRUE; diff --git a/src/RequestHandler/environmentvariablehelpers.h b/src/RequestHandler/environmentvariablehelpers.h new file mode 100644 index 0000000000..268011d30a --- /dev/null +++ b/src/RequestHandler/environmentvariablehelpers.h @@ -0,0 +1,421 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "precomp.hxx" + +class ENVIRONMENT_VAR_HELPERS +{ + +public: + static + VOID + CopyToMultiSz( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + STRU strTemp; + MULTISZ *pMultiSz = static_cast(pvData); + DBG_ASSERT(pMultiSz); + DBG_ASSERT(pEntry); + strTemp.Copy(pEntry->QueryName()); + strTemp.Append(pEntry->QueryValue()); + pMultiSz->Append(strTemp.QueryStr()); + } + + static + VOID + CopyToTable( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + // best effort copy, ignore the failure + ENVIRONMENT_VAR_ENTRY * pNewEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pNewEntry != NULL) + { + pNewEntry->Initialize(pEntry->QueryName(), pEntry->QueryValue()); + ENVIRONMENT_VAR_HASH *pHash = static_cast(pvData); + DBG_ASSERT(pHash); + pHash->InsertRecord(pNewEntry); + // Need to dereference as InsertRecord references it now + pNewEntry->Dereference(); + } + } + + static + VOID + AppendEnvironmentVariables + ( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + HRESULT hr = S_OK; + DWORD dwResult = 0; + DWORD dwError = 0; + STRU struNameBuffer; + STACK_STRU(struValueBuffer, 300); + BOOL fFound = FALSE; + + HRESULT* pHr = static_cast(pvData); + + // pEntry->QueryName includes the trailing =, remove it before calling stru + if (FAILED(hr = struNameBuffer.Copy(pEntry->QueryName()))) + { + goto Finished; + } + dwResult = struNameBuffer.LastIndexOf(L'='); + if (dwResult != -1) + { + struNameBuffer.QueryStr()[dwResult] = L'\0'; + if (FAILED(hr = struNameBuffer.SyncWithBuffer())) + { + goto Finished; + } + } + + dwResult = GetEnvironmentVariable(struNameBuffer.QueryStr(), struValueBuffer.QueryStr(), struValueBuffer.QuerySizeCCH()); + if (dwResult == 0) + { + dwError = GetLastError(); + // Windows API (e.g., CreateProcess) allows variable with empty string value + // in such case dwResult will be 0 and dwError will also be 0 + // As UI and CMD does not allow empty value, ignore this environment var + if (dwError != ERROR_ENVVAR_NOT_FOUND && dwError != ERROR_SUCCESS) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + else if (dwResult > struValueBuffer.QuerySizeCCH()) + { + // have to increase the buffer and try get environment var again + struValueBuffer.Reset(); + struValueBuffer.Resize(dwResult + (DWORD)wcslen(pEntry->QueryValue()) + 2); // for null char and semicolon + dwResult = GetEnvironmentVariable(struNameBuffer.QueryStr(), + struValueBuffer.QueryStr(), + struValueBuffer.QuerySizeCCH()); + if (struValueBuffer.IsEmpty()) + { + hr = E_UNEXPECTED; + goto Finished; + } + fFound = TRUE; + } + else + { + fFound = TRUE; + } + + if (FAILED(hr = struValueBuffer.SyncWithBuffer())) + { + goto Finished; + } + + if (fFound) + { + if (FAILED(hr = struValueBuffer.Append(L";"))) + { + goto Finished; + } + } + if (FAILED(hr = struValueBuffer.Append(pEntry->QueryValue()))) + { + goto Finished; + } + + if (FAILED(hr = pEntry->Initialize(pEntry->QueryName(), struValueBuffer.QueryStr()))) + { + goto Finished; + } + + Finished: + if (FAILED(hr)) + { + *pHr = hr; + } + return; + } + + static + VOID + SetEnvironmentVariables + ( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + UNREFERENCED_PARAMETER(pvData); + HRESULT hr = S_OK; + DWORD dwResult = 0; + STRU struNameBuffer; + + HRESULT* pHr = static_cast(pvData); + + // pEntry->QueryName includes the trailing =, remove it before calling SetEnvironmentVariable. + if (FAILED(hr = struNameBuffer.Copy(pEntry->QueryName()))) + { + goto Finished; + } + dwResult = struNameBuffer.LastIndexOf(L'='); + if (dwResult != -1) + { + struNameBuffer.QueryStr()[dwResult] = L'\0'; + if (FAILED(hr = struNameBuffer.SyncWithBuffer())) + { + goto Finished; + } + } + + dwResult = SetEnvironmentVariable(struNameBuffer.QueryStr(), pEntry->QueryValue()); + if (dwResult == 0) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + Finished: + if (FAILED(hr)) + { + *pHr = hr; + } + return; + } + + static + HRESULT + InitEnvironmentVariablesTable + ( + _In_ ENVIRONMENT_VAR_HASH* pInEnvironmentVarTable, + _In_ BOOL fWindowsAuthEnabled, + _In_ BOOL fBasicAuthEnabled, + _In_ BOOL fAnonymousAuthEnabled, + _Out_ ENVIRONMENT_VAR_HASH** ppEnvironmentVarTable + ) + { + HRESULT hr = S_OK; + BOOL fFound = FALSE; + DWORD dwResult, dwError; + STRU strIisAuthEnvValue; + STACK_STRU(strStartupAssemblyEnv, 1024); + ENVIRONMENT_VAR_ENTRY* pHostingEntry = NULL; + ENVIRONMENT_VAR_ENTRY* pIISAuthEntry = NULL; + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable = NULL; + + pEnvironmentVarTable = new ENVIRONMENT_VAR_HASH(); + if (pEnvironmentVarTable == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + // + // few environment variables expected, small bucket size for hash table + // + if (FAILED(hr = pEnvironmentVarTable->Initialize(37 /*prime*/))) + { + goto Finished; + } + + // copy the envirable hash table (from configuration) to a temp one as we may need to remove elements + pInEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HELPERS::CopyToTable, pEnvironmentVarTable); + if (pEnvironmentVarTable->Count() != pInEnvironmentVarTable->Count()) + { + // hash table copy failed + hr = E_UNEXPECTED; + goto Finished; + } + + pEnvironmentVarTable->FindKey((PWSTR)ASPNETCORE_IIS_AUTH_ENV_STR, &pIISAuthEntry); + if (pIISAuthEntry != NULL) + { + // user defined ASPNETCORE_IIS_HTTPAUTH in configuration, wipe it off + pIISAuthEntry->Dereference(); + pEnvironmentVarTable->DeleteKey((PWSTR)ASPNETCORE_IIS_AUTH_ENV_STR); + } + + if (fWindowsAuthEnabled) + { + strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_WINDOWS); + } + + if (fBasicAuthEnabled) + { + strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_BASIC); + } + + if (fAnonymousAuthEnabled) + { + strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_ANONYMOUS); + } + + if (strIisAuthEnvValue.IsEmpty()) + { + strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_NONE); + } + + pIISAuthEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pIISAuthEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = pIISAuthEntry->Initialize(ASPNETCORE_IIS_AUTH_ENV_STR, strIisAuthEnvValue.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISAuthEntry))) + { + goto Finished; + } + + // Compiler is complaining about conversion between PCWSTR and PWSTR here. + // Explictly casting. + pEnvironmentVarTable->FindKey((PWSTR)HOSTING_STARTUP_ASSEMBLIES_NAME, &pHostingEntry); + if (pHostingEntry != NULL) + { + // user defined ASPNETCORE_HOSTINGSTARTUPASSEMBLIES in configuration + // the value will be used in OutputEnvironmentVariables. Do nothing here + pHostingEntry->Dereference(); + pHostingEntry = NULL; + goto Skipped; + } + + //check whether ASPNETCORE_HOSTINGSTARTUPASSEMBLIES is defined in system + dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, + strStartupAssemblyEnv.QueryStr(), + strStartupAssemblyEnv.QuerySizeCCH()); + if (dwResult == 0) + { + dwError = GetLastError(); + // Windows API (e.g., CreateProcess) allows variable with empty string value + // in such case dwResult will be 0 and dwError will also be 0 + // As UI and CMD does not allow empty value, ignore this environment var + if (dwError != ERROR_ENVVAR_NOT_FOUND && dwError != ERROR_SUCCESS) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + else if (dwResult > strStartupAssemblyEnv.QuerySizeCCH()) + { + // have to increase the buffer and try get environment var again + strStartupAssemblyEnv.Reset(); + strStartupAssemblyEnv.Resize(dwResult + (DWORD)wcslen(HOSTING_STARTUP_ASSEMBLIES_VALUE) + 1); + dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, + strStartupAssemblyEnv.QueryStr(), + strStartupAssemblyEnv.QuerySizeCCH()); + if (strStartupAssemblyEnv.IsEmpty()) + { + hr = E_UNEXPECTED; + goto Finished; + } + fFound = TRUE; + } + else + { + fFound = TRUE; + } + + strStartupAssemblyEnv.SyncWithBuffer(); + if (fFound) + { + strStartupAssemblyEnv.Append(L";"); + } + strStartupAssemblyEnv.Append(HOSTING_STARTUP_ASSEMBLIES_VALUE); + + // the environment variable was not defined, create it and add to hashtable + pHostingEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pHostingEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = pHostingEntry->Initialize(HOSTING_STARTUP_ASSEMBLIES_NAME, strStartupAssemblyEnv.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pHostingEntry))) + { + goto Finished; + } + + Skipped: + *ppEnvironmentVarTable = pEnvironmentVarTable; + pEnvironmentVarTable = NULL; + + Finished: + if (pHostingEntry != NULL) + { + pHostingEntry->Dereference(); + pHostingEntry = NULL; + } + + if (pIISAuthEntry != NULL) + { + pIISAuthEntry->Dereference(); + pIISAuthEntry = NULL; + } + + if (pEnvironmentVarTable != NULL) + { + pEnvironmentVarTable->Clear(); + delete pEnvironmentVarTable; + pEnvironmentVarTable = NULL; + } + return hr; + } + + + static + HRESULT + AddWebsocketEnabledToEnvironmentVariables + ( + _Inout_ ENVIRONMENT_VAR_HASH* pInEnvironmentVarTable, + _In_ BOOL fWebsocketsEnabled + ) + { + HRESULT hr = S_OK; + ENVIRONMENT_VAR_ENTRY* pIISWebsocketEntry = NULL; + STACK_STRU(strIISWebsocketEnvValue, 40); + + // We only need to set the WEBSOCKET_SUPPORTED environment variable for out of process + pInEnvironmentVarTable->FindKey((PWSTR)ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR, &pIISWebsocketEntry); + if (pIISWebsocketEntry != NULL) + { + // user defined ASPNETCORE_IIS_WEBSOCKETS in configuration, wipe it off + pIISWebsocketEntry->Dereference(); + pInEnvironmentVarTable->DeleteKey((PWSTR)ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR); + } + // Set either true or false for the WebsocketEnvValue. + if (fWebsocketsEnabled) + { + if (FAILED(hr = strIISWebsocketEnvValue.Copy(L"true"))) + { + goto Finished; + } + } + else + { + if (FAILED(hr = strIISWebsocketEnvValue.Copy(L"false"))) + { + goto Finished; + } + } + + pIISWebsocketEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pIISWebsocketEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = pIISWebsocketEntry->Initialize(ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR, strIISWebsocketEnvValue.QueryStr())) || + FAILED(hr = pInEnvironmentVarTable->InsertRecord(pIISWebsocketEntry))) + { + goto Finished; + } + + Finished: + return hr; + } +public: + ENVIRONMENT_VAR_HELPERS(); + ~ENVIRONMENT_VAR_HELPERS(); +}; + diff --git a/src/RequestHandler/inprocess/inprocessapplication.cpp b/src/RequestHandler/inprocess/inprocessapplication.cpp index 3fc8122245..0127591ae1 100644 --- a/src/RequestHandler/inprocess/inprocessapplication.cpp +++ b/src/RequestHandler/inprocess/inprocessapplication.cpp @@ -726,7 +726,7 @@ IN_PROCESS_APPLICATION::SetEnvironementVariablesOnWorkerProcess( { HRESULT hr = S_OK; ENVIRONMENT_VAR_HASH* pHashTable = NULL; - if (FAILED(hr = ENVIRONMENT_VAR_HASH::InitEnvironmentVariablesTable( + if (FAILED(hr = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable( m_pConfig->QueryEnvironmentVariables(), m_pConfig->QueryWindowsAuthEnabled(), m_pConfig->QueryBasicAuthEnabled(), @@ -736,8 +736,16 @@ IN_PROCESS_APPLICATION::SetEnvironementVariablesOnWorkerProcess( goto Finished; } - pHashTable->Apply(ENVIRONMENT_VAR_HASH::AppendEnvironmentVariables, NULL); - pHashTable->Apply(ENVIRONMENT_VAR_HASH::SetEnvironmentVariables, NULL); + pHashTable->Apply(ENVIRONMENT_VAR_HELPERS::AppendEnvironmentVariables, &hr); + if (FAILED(hr)) + { + goto Finished; + } + pHashTable->Apply(ENVIRONMENT_VAR_HELPERS::SetEnvironmentVariables, &hr); + if (FAILED(hr)) + { + goto Finished; + } Finished: return hr; } @@ -770,15 +778,15 @@ IN_PROCESS_APPLICATION::ExecuteApplication( goto Finished; } - // There can only ever be a single instance of .NET Core - // loaded in the process but we need to get config information to boot it up in the - // first place. This is happening in an execute request handler and everyone waits - // until this initialization is done. if (FAILED(hr = SetEnvironementVariablesOnWorkerProcess())) { goto Finished; } + // There can only ever be a single instance of .NET Core + // loaded in the process but we need to get config information to boot it up in the + // first place. This is happening in an execute request handler and everyone waits + // until this initialization is done. // We set a static so that managed code can call back into this instance and // set the callbacks s_Application = this; diff --git a/src/RequestHandler/outofprocess/processmanager.cxx b/src/RequestHandler/outofprocess/processmanager.cxx index 88fc771b16..656743ef0a 100644 --- a/src/RequestHandler/outofprocess/processmanager.cxx +++ b/src/RequestHandler/outofprocess/processmanager.cxx @@ -225,6 +225,7 @@ PROCESS_MANAGER::GetProcess( pConfig->QueryAnonymousAuthEnabled(), pConfig->QueryEnvironmentVariables(), pConfig->QueryStdoutLogEnabled(), + pConfig->QueryWebSocketEnabled(), pConfig->QueryStdoutLogFile(), pConfig->QueryApplicationPhysicalPath(), // physical path pConfig->QueryApplicationPath(), // app path diff --git a/src/RequestHandler/outofprocess/serverprocess.cxx b/src/RequestHandler/outofprocess/serverprocess.cxx index a14b4eafc6..e6fc873202 100644 --- a/src/RequestHandler/outofprocess/serverprocess.cxx +++ b/src/RequestHandler/outofprocess/serverprocess.cxx @@ -22,6 +22,7 @@ SERVER_PROCESS::Initialize( BOOL fAnonymousAuthEnabled, ENVIRONMENT_VAR_HASH *pEnvironmentVariables, BOOL fStdoutLogEnabled, + BOOL fWebsocketsEnabled, STRU *pstruStdoutLogFile, STRU *pszAppPhysicalPath, STRU *pszAppPath, @@ -38,6 +39,7 @@ SERVER_PROCESS::Initialize( m_fWindowsAuthEnabled = fWindowsAuthEnabled; m_fBasicAuthEnabled = fBasicAuthEnabled; m_fAnonymousAuthEnabled = fAnonymousAuthEnabled; + m_fWebsocketsEnabled = fWebsocketsEnabled; m_pProcessManager->ReferenceProcessManager(); if (FAILED(hr = m_ProcessPath.Copy(*pszProcessExePath)) || @@ -402,7 +404,7 @@ SERVER_PROCESS::OutputEnvironmentVariables pszCurrentVariable = pszNextVariable; } // append the remaining env variable in hash table - pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToMultiSz, pmszOutput); + pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HELPERS::CopyToMultiSz, pmszOutput); Finished: if (pszEnvironmentVariables != NULL) @@ -755,7 +757,7 @@ SERVER_PROCESS::StartProcess( goto Failure; } - if (FAILED(hr = ENVIRONMENT_VAR_HASH::InitEnvironmentVariablesTable( + if (FAILED(hr = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable( m_pEnvironmentVarTable, m_fWindowsAuthEnabled, m_fBasicAuthEnabled, @@ -766,6 +768,16 @@ SERVER_PROCESS::StartProcess( goto Failure; } + if (FAILED(hr = ENVIRONMENT_VAR_HELPERS::AddWebsocketEnabledToEnvironmentVariables( + pHashTable, + m_fWebsocketsEnabled + ))) + { + pStrStage = L"AddWebsocketEnabledToEnvironmentVariables"; + goto Failure; + + } + // // setup the the port that the backend process will listen on // diff --git a/src/RequestHandler/outofprocess/serverprocess.h b/src/RequestHandler/outofprocess/serverprocess.h index 567ff3fde3..3b6ff4b370 100644 --- a/src/RequestHandler/outofprocess/serverprocess.h +++ b/src/RequestHandler/outofprocess/serverprocess.h @@ -33,6 +33,7 @@ public: _In_ BOOL fAnonymousAuthEnabled, _In_ ENVIRONMENT_VAR_HASH* pEnvironmentVariables, _In_ BOOL fStdoutLogEnabled, + _In_ BOOL fWebsocketsEnabled, _In_ STRU *pstruStdoutLogFile, _In_ STRU *pszAppPhysicalPath, _In_ STRU *pszAppPath, @@ -222,6 +223,7 @@ private: BOOL m_fWindowsAuthEnabled; BOOL m_fBasicAuthEnabled; BOOL m_fAnonymousAuthEnabled; + BOOL m_fWebsocketsEnabled; STTIMER m_Timer; SOCKET m_socket; diff --git a/src/RequestHandler/precomp.hxx b/src/RequestHandler/precomp.hxx index 42ee3e391b..9e70f88e79 100644 --- a/src/RequestHandler/precomp.hxx +++ b/src/RequestHandler/precomp.hxx @@ -59,6 +59,7 @@ #include "aspnetcore_event.h" #include "aspnetcore_msg.h" #include "disconnectcontext.h" +#include "environmentvariablehelpers.h" #include "sttimer.h" #include ".\inprocess\InProcessHandler.h" #include ".\inprocess\inprocessapplication.h" diff --git a/test/IISIntegration.IISServerFunctionalTests/Http.config b/test/IISIntegration.FunctionalTests/AppHostConfig/Http.config similarity index 100% rename from test/IISIntegration.IISServerFunctionalTests/Http.config rename to test/IISIntegration.FunctionalTests/AppHostConfig/Http.config diff --git a/test/IISIntegration.FunctionalTests/Https.config b/test/IISIntegration.FunctionalTests/AppHostConfig/Https.config similarity index 100% rename from test/IISIntegration.FunctionalTests/Https.config rename to test/IISIntegration.FunctionalTests/AppHostConfig/Https.config diff --git a/test/IISIntegration.FunctionalTests/NtlmAuthentation.config b/test/IISIntegration.FunctionalTests/AppHostConfig/NtlmAuthentation.config similarity index 100% rename from test/IISIntegration.FunctionalTests/NtlmAuthentation.config rename to test/IISIntegration.FunctionalTests/AppHostConfig/NtlmAuthentation.config diff --git a/test/IISIntegration.FunctionalTests/Http.config b/test/IISIntegration.FunctionalTests/AppHostConfig/WebsocketsNotSupported.config similarity index 99% rename from test/IISIntegration.FunctionalTests/Http.config rename to test/IISIntegration.FunctionalTests/AppHostConfig/WebsocketsNotSupported.config index e48138d588..ce53b89904 100644 --- a/test/IISIntegration.FunctionalTests/Http.config +++ b/test/IISIntegration.FunctionalTests/AppHostConfig/WebsocketsNotSupported.config @@ -212,9 +212,9 @@ --> - + - + @@ -228,7 +228,7 @@ - + @@ -236,14 +236,14 @@ - + - + @@ -799,7 +799,7 @@ - + @@ -870,7 +870,7 @@ - + diff --git a/test/IISIntegration.FunctionalTests/IISIntegration.FunctionalTests.csproj b/test/IISIntegration.FunctionalTests/IISIntegration.FunctionalTests.csproj index b0602adbfd..77f3646a70 100644 --- a/test/IISIntegration.FunctionalTests/IISIntegration.FunctionalTests.csproj +++ b/test/IISIntegration.FunctionalTests/IISIntegration.FunctionalTests.csproj @@ -5,15 +5,19 @@ - + + + + + - + - + diff --git a/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/AuthenticationTests.cs similarity index 96% rename from test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs rename to test/IISIntegration.FunctionalTests/Inprocess/AuthenticationTests.cs index 5b23d2ec37..4370014007 100644 --- a/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs +++ b/test/IISIntegration.FunctionalTests/Inprocess/AuthenticationTests.cs @@ -6,11 +6,10 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.IIS.FunctionalTests; using Microsoft.AspNetCore.Testing.xunit; using Xunit; -namespace IISIntegration.IISServerFunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [Collection(IISTestSiteCollection.Name)] public class AuthenticationTests diff --git a/test/IISIntegration.IISServerFunctionalTests/EnvironmentVariableTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/EnvironmentVariableTests.cs similarity index 96% rename from test/IISIntegration.IISServerFunctionalTests/EnvironmentVariableTests.cs rename to test/IISIntegration.FunctionalTests/Inprocess/EnvironmentVariableTests.cs index 95de23f9e6..413aa4c35f 100644 --- a/test/IISIntegration.IISServerFunctionalTests/EnvironmentVariableTests.cs +++ b/test/IISIntegration.FunctionalTests/Inprocess/EnvironmentVariableTests.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Testing.xunit; using Xunit; -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [Collection(IISTestSiteCollection.Name)] public class EnvironmentVariableTests diff --git a/test/IISIntegration.IISServerFunctionalTests/FeatureCollectionTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/FeatureCollectionTests.cs similarity index 93% rename from test/IISIntegration.IISServerFunctionalTests/FeatureCollectionTests.cs rename to test/IISIntegration.FunctionalTests/Inprocess/FeatureCollectionTests.cs index fb085cd5f8..8aa54716e3 100644 --- a/test/IISIntegration.IISServerFunctionalTests/FeatureCollectionTests.cs +++ b/test/IISIntegration.FunctionalTests/Inprocess/FeatureCollectionTests.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Testing.xunit; using Xunit; -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [Collection(IISTestSiteCollection.Name)] public class FeatureCollectionTest diff --git a/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/HelloWorldTests.cs similarity index 83% rename from test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs rename to test/IISIntegration.FunctionalTests/Inprocess/HelloWorldTests.cs index 5416aa0c4f..ce569acfe7 100644 --- a/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs +++ b/test/IISIntegration.FunctionalTests/Inprocess/HelloWorldTests.cs @@ -6,14 +6,14 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Testing.xunit; using Xunit; -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [Collection(IISTestSiteCollection.Name)] - public class HelloWorldTests + public class HelloWorldInProcessTests { private readonly IISTestSiteFixture _fixture; - public HelloWorldTests(IISTestSiteFixture fixture) + public HelloWorldInProcessTests(IISTestSiteFixture fixture) { _fixture = fixture; } diff --git a/test/IISIntegration.IISServerFunctionalTests/LargeResponseBodyTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/LargeResponseBodyTests.cs similarity index 92% rename from test/IISIntegration.IISServerFunctionalTests/LargeResponseBodyTests.cs rename to test/IISIntegration.FunctionalTests/Inprocess/LargeResponseBodyTests.cs index 999cfd91e4..ac5c9e6db3 100644 --- a/test/IISIntegration.IISServerFunctionalTests/LargeResponseBodyTests.cs +++ b/test/IISIntegration.FunctionalTests/Inprocess/LargeResponseBodyTests.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Testing.xunit; using Xunit; -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [Collection(IISTestSiteCollection.Name)] public class LargeResponseBodyTests diff --git a/test/IISIntegration.IISServerFunctionalTests/ResponseHeaderTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/ResponseHeaderTests.cs similarity index 95% rename from test/IISIntegration.IISServerFunctionalTests/ResponseHeaderTests.cs rename to test/IISIntegration.FunctionalTests/Inprocess/ResponseHeaderTests.cs index 6ec2682cd8..0ab845f1d4 100644 --- a/test/IISIntegration.IISServerFunctionalTests/ResponseHeaderTests.cs +++ b/test/IISIntegration.FunctionalTests/Inprocess/ResponseHeaderTests.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Net.Http.Headers; using Xunit; -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [Collection(IISTestSiteCollection.Name)] diff --git a/test/IISIntegration.IISServerFunctionalTests/ResponseInvalidOrderingTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/ResponseInvalidOrderingTests.cs similarity index 92% rename from test/IISIntegration.IISServerFunctionalTests/ResponseInvalidOrderingTests.cs rename to test/IISIntegration.FunctionalTests/Inprocess/ResponseInvalidOrderingTests.cs index 181220b4ea..2e484724e6 100644 --- a/test/IISIntegration.IISServerFunctionalTests/ResponseInvalidOrderingTests.cs +++ b/test/IISIntegration.FunctionalTests/Inprocess/ResponseInvalidOrderingTests.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Testing.xunit; using Xunit; -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [Collection(IISTestSiteCollection.Name)] public class ResponseInvalidOrderingTest diff --git a/test/IISIntegration.IISServerFunctionalTests/ServerVariablesTest.cs b/test/IISIntegration.FunctionalTests/Inprocess/ServerVariablesTest.cs similarity index 94% rename from test/IISIntegration.IISServerFunctionalTests/ServerVariablesTest.cs rename to test/IISIntegration.FunctionalTests/Inprocess/ServerVariablesTest.cs index 6772c57d16..f86b3bcffd 100644 --- a/test/IISIntegration.IISServerFunctionalTests/ServerVariablesTest.cs +++ b/test/IISIntegration.FunctionalTests/Inprocess/ServerVariablesTest.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Testing.xunit; using Xunit; -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [Collection(IISTestSiteCollection.Name)] public class ServerVariablesTest diff --git a/test/IISIntegration.FunctionalTests/HelloWorldTest.cs b/test/IISIntegration.FunctionalTests/OutOfProcess/HelloWorldTest.cs similarity index 96% rename from test/IISIntegration.FunctionalTests/HelloWorldTest.cs rename to test/IISIntegration.FunctionalTests/OutOfProcess/HelloWorldTest.cs index 0a27bb285f..6125b0df84 100644 --- a/test/IISIntegration.FunctionalTests/HelloWorldTest.cs +++ b/test/IISIntegration.FunctionalTests/OutOfProcess/HelloWorldTest.cs @@ -41,10 +41,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { var logger = loggerFactory.CreateLogger("HelloWorldTest"); - var deploymentParameters = new DeploymentParameters(Helpers.GetTestSitesPath(), serverType, runtimeFlavor, architecture) + var deploymentParameters = new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), serverType, runtimeFlavor, architecture) { EnvironmentName = "HelloWorld", // Will pick the Start class named 'StartupHelloWorld', - ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("Http.config") : null, + ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/Http.config") : null, SiteName = "HttpTestSite", // This is configured in the Http.config TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0", ApplicationType = applicationType, diff --git a/test/IISIntegration.FunctionalTests/HttpsTest.cs b/test/IISIntegration.FunctionalTests/OutOfProcess/HttpsTest.cs similarity index 97% rename from test/IISIntegration.FunctionalTests/HttpsTest.cs rename to test/IISIntegration.FunctionalTests/OutOfProcess/HttpsTest.cs index 369fa30305..40d1a6f66f 100644 --- a/test/IISIntegration.FunctionalTests/HttpsTest.cs +++ b/test/IISIntegration.FunctionalTests/OutOfProcess/HttpsTest.cs @@ -48,11 +48,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { var logger = loggerFactory.CreateLogger("HttpsHelloWorldTest"); - var deploymentParameters = new DeploymentParameters(Helpers.GetTestSitesPath(), serverType, runtimeFlavor, architecture) + var deploymentParameters = new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), serverType, runtimeFlavor, architecture) { ApplicationBaseUriHint = applicationBaseUrl, EnvironmentName = "HttpsHelloWorld", // Will pick the Start class named 'StartupHttpsHelloWorld', - ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("Https.config") : null, + ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/Https.config") : null, SiteName = "HttpsTestSite", // This is configured in the Https.config TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0", ApplicationType = applicationType, @@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { var logger = loggerFactory.CreateLogger("HttpsHelloWorldTest"); - var deploymentParameters = new DeploymentParameters(Helpers.GetTestSitesPath(), serverType, runtimeFlavor, architecture) + var deploymentParameters = new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), serverType, runtimeFlavor, architecture) { ApplicationBaseUriHint = applicationBaseUrl, EnvironmentName = "HttpsHelloWorld", // Will pick the Start class named 'StartupHttpsHelloWorld', diff --git a/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs b/test/IISIntegration.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs similarity index 97% rename from test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs rename to test/IISIntegration.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs index f5f8fd7aa1..e0bcb5e5e5 100644 --- a/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs +++ b/test/IISIntegration.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs @@ -50,11 +50,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests ? "win7-x64" : "win7-x86"; - var deploymentParameters = new DeploymentParameters(Helpers.GetTestSitesPath(), serverType, runtimeFlavor, architecture) + var deploymentParameters = new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), serverType, runtimeFlavor, architecture) { ApplicationBaseUriHint = $"http://localhost:{port}", EnvironmentName = "NtlmAuthentication", // Will pick the Start class named 'StartupNtlmAuthentication' - ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("NtlmAuthentation.config") : null, + ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/NtlmAuthentation.config") : null, SiteName = "NtlmAuthenticationTestSite", // This is configured in the NtlmAuthentication.config TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0", ApplicationType = applicationType, diff --git a/test/IISIntegration.FunctionalTests/Properties/AssemblyInfo.cs b/test/IISIntegration.FunctionalTests/Properties/AssemblyInfo.cs index 918c66949c..ebca2988e8 100644 --- a/test/IISIntegration.FunctionalTests/Properties/AssemblyInfo.cs +++ b/test/IISIntegration.FunctionalTests/Properties/AssemblyInfo.cs @@ -1,3 +1,5 @@ -using Xunit; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] +// All functional tests in this project require a version of IIS express with an updated schema +[assembly: Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests.IISExpressSupportsInProcessHosting] diff --git a/test/IISIntegration.FunctionalTests/UpgradeFeatureDetectionTests.cs b/test/IISIntegration.FunctionalTests/UpgradeFeatureDetectionTests.cs new file mode 100644 index 0000000000..6ade4dc4f1 --- /dev/null +++ b/test/IISIntegration.FunctionalTests/UpgradeFeatureDetectionTests.cs @@ -0,0 +1,117 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class UpgradeFeatureDetectionTests : LoggedTest + { + public UpgradeFeatureDetectionTests(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public Task UpgradeFeatureDetectionEnabled_InProcess_IISExpress_CoreClr_x64_Portable() + { + return UpgradeFeatureDetectionDeployer(RuntimeFlavor.CoreClr, + ApplicationType.Portable, + "AppHostConfig/WebsocketsNotSupported.config", + Helpers.GetInProcessTestSitesPath(), + "Disabled"); + } + + [Fact] + public Task UpgradeFeatureDetectionDisabled_InProcess_IISExpress_CoreClr_x64_Portable() + { + return UpgradeFeatureDetectionDeployer(RuntimeFlavor.CoreClr, + ApplicationType.Portable, + "AppHostConfig/Http.config", + Helpers.GetInProcessTestSitesPath(), + "Enabled"); + } + + [Fact] + public Task UpgradeFeatureDetectionEnabled_OutOfProcess_IISExpress_CoreClr_x64_Portable() + { + return UpgradeFeatureDetectionDeployer(RuntimeFlavor.CoreClr, + ApplicationType.Portable, + "AppHostConfig/WebsocketsNotSupported.config", + Helpers.GetOutOfProcessTestSitesPath(), + "Disabled"); + } + + [Fact] + public Task UpgradeFeatureDetectionDisabled_OutOfProcess_IISExpress_CoreClr_x64_Portable() + { + return UpgradeFeatureDetectionDeployer(RuntimeFlavor.CoreClr, + ApplicationType.Portable, + "AppHostConfig/Http.config", + Helpers.GetOutOfProcessTestSitesPath(), + "Enabled"); + } + + private async Task UpgradeFeatureDetectionDeployer(RuntimeFlavor runtimeFlavor, + ApplicationType applicationType, + string configPath, + string sitePath, + string expected) + { + var serverType = ServerType.IISExpress; + var architecture = RuntimeArchitecture.x64; + var testName = $"HelloWorld_{runtimeFlavor}"; + using (StartLog(out var loggerFactory, testName)) + { + var logger = loggerFactory.CreateLogger("HelloWorldTest"); + + var deploymentParameters = new DeploymentParameters(Helpers.GetInProcessTestSitesPath(), serverType, runtimeFlavor, architecture) + { + EnvironmentName = "UpgradeFeatureDetection", // Will pick the Start class named 'StartupHelloWorld', + ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText(configPath) : null, + SiteName = "HttpTestSite", // This is configured in the Http.config + TargetFramework = "netcoreapp2.0", + ApplicationType = applicationType, + Configuration = +#if DEBUG + "Debug" +#else + "Release" +#endif + }; + + using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) + { + var deploymentResult = await deployer.DeployAsync(); + deploymentResult.HttpClient.Timeout = TimeSpan.FromSeconds(5); + + // Request to base address and check if various parts of the body are rendered & measure the cold startup time. + var response = await RetryHelper.RetryRequest(() => + { + return deploymentResult.HttpClient.GetAsync("UpgradeFeatureDetection"); + }, logger, deploymentResult.HostShutdownToken, retryCount: 30); + + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal(expected, responseText); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + } + } + } +} diff --git a/test/IISIntegration.FunctionalTests/Helpers.cs b/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs similarity index 59% rename from test/IISIntegration.FunctionalTests/Helpers.cs rename to test/IISIntegration.FunctionalTests/Utilities/Helpers.cs index 92e684db3b..c09b1bf3a7 100644 --- a/test/IISIntegration.FunctionalTests/Helpers.cs +++ b/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs @@ -8,7 +8,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { public class Helpers { - public static string GetTestSitesPath() + public static string GetInProcessTestSitesPath() + { + return Path.GetFullPath( + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, + "..", // tfm + "..", // debug + "..", // obj + "..", // projectfolder + "IISTestSite")); + } + + public static string GetOutOfProcessTestSitesPath() { return Path.GetFullPath( Path.Combine(AppDomain.CurrentDomain.BaseDirectory, diff --git a/test/IISIntegration.IISServerFunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs b/test/IISIntegration.FunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs similarity index 96% rename from test/IISIntegration.IISServerFunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs rename to test/IISIntegration.FunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs index 8df2eeb406..5f2edd22f6 100644 --- a/test/IISIntegration.IISServerFunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs +++ b/test/IISIntegration.FunctionalTests/Utilities/IISExpressSupportsInProcessHostingAttribute.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; using System.Xml.Linq; using Microsoft.AspNetCore.Testing.xunit; -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [AttributeUsage(AttributeTargets.Method | AttributeTargets.Assembly | AttributeTargets.Class)] public sealed class IISExpressSupportsInProcessHostingAttribute : Attribute, ITestCondition diff --git a/test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteCollection.cs b/test/IISIntegration.FunctionalTests/Utilities/IISTestSiteCollection.cs similarity index 87% rename from test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteCollection.cs rename to test/IISIntegration.FunctionalTests/Utilities/IISTestSiteCollection.cs index 88f0710e48..8d53affc98 100644 --- a/test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteCollection.cs +++ b/test/IISIntegration.FunctionalTests/Utilities/IISTestSiteCollection.cs @@ -3,7 +3,7 @@ using Xunit; -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { /// /// This type just maps collection names to available fixtures diff --git a/test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteFixture.cs b/test/IISIntegration.FunctionalTests/Utilities/IISTestSiteFixture.cs similarity index 92% rename from test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteFixture.cs rename to test/IISIntegration.FunctionalTests/Utilities/IISTestSiteFixture.cs index 59727bb9fb..7b03c1dc5f 100644 --- a/test/IISIntegration.IISServerFunctionalTests/Utilities/IISTestSiteFixture.cs +++ b/test/IISIntegration.FunctionalTests/Utilities/IISTestSiteFixture.cs @@ -8,7 +8,7 @@ using System.Threading; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.Extensions.Logging.Abstractions; -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { public class IISTestSiteFixture : IDisposable { @@ -16,12 +16,12 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public IISTestSiteFixture() { - var deploymentParameters = new DeploymentParameters(Helpers.GetTestSitesPath(), + var deploymentParameters = new DeploymentParameters(Helpers.GetInProcessTestSitesPath(), ServerType.IISExpress, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { - ServerConfigTemplateContent = File.ReadAllText("Http.config"), + ServerConfigTemplateContent = File.ReadAllText("AppHostConfig/Http.config"), SiteName = "HttpTestSite", TargetFramework = "netcoreapp2.0", ApplicationType = ApplicationType.Portable, diff --git a/test/IISIntegration.IISServerFunctionalTests/IISIntegration.IISServerFunctionalTests.csproj b/test/IISIntegration.IISServerFunctionalTests/IISIntegration.IISServerFunctionalTests.csproj deleted file mode 100644 index 2761d3dd76..0000000000 --- a/test/IISIntegration.IISServerFunctionalTests/IISIntegration.IISServerFunctionalTests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - $(StandardTestTfms) - Microsoft.AspNetCore.Server.IIS.FunctionalTests - Microsoft.AspNetCore.Server.IIS.FunctionalTests - - - - - - - - - - - - - - - - - - - - diff --git a/test/IISIntegration.IISServerFunctionalTests/Properties/AssemblyInfo.cs b/test/IISIntegration.IISServerFunctionalTests/Properties/AssemblyInfo.cs deleted file mode 100644 index c473b24155..0000000000 --- a/test/IISIntegration.IISServerFunctionalTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -// All functional tests in this project require a version of IIS express with an updated schema -[assembly: Microsoft.AspNetCore.Server.IIS.FunctionalTests.IISExpressSupportsInProcessHosting] diff --git a/test/IISIntegration.IISServerFunctionalTests/Utilities/Helpers.cs b/test/IISIntegration.IISServerFunctionalTests/Utilities/Helpers.cs deleted file mode 100644 index 48a6a47f18..0000000000 --- a/test/IISIntegration.IISServerFunctionalTests/Utilities/Helpers.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; - -namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests -{ - public class Helpers - { - public static string GetTestSitesPath() - { - return Path.GetFullPath( - Path.Combine(AppDomain.CurrentDomain.BaseDirectory, - "..", // tfm - "..", // debug - "..", // obj - "..", // projectfolder - "IISTestSite")); - } - } -} diff --git a/test/IISTestSite/Startup.cs b/test/IISTestSite/Startup.cs index 75d6047425..41b95ea488 100644 --- a/test/IISTestSite/Startup.cs +++ b/test/IISTestSite/Startup.cs @@ -43,6 +43,7 @@ namespace IISTestSite app.Map("/ReadAndWriteEchoTwice", ReadAndWriteEchoTwice); app.Map("/ReadAndWriteSlowConnection", ReadAndWriteSlowConnection); app.Map("/WebsocketRequest", WebsocketRequest); + app.Map("/UpgradeFeatureDetection", UpgradeFeatureDetection); } private void ServerVariable(IApplicationBuilder app) @@ -425,5 +426,20 @@ namespace IISTestSite await context.Request.Body.CopyToAsync(context.Response.Body); }); } + + private void UpgradeFeatureDetection(IApplicationBuilder app) + { + app.Run(async ctx => + { + if (ctx.Features.Get() != null) + { + await ctx.Response.WriteAsync("Enabled"); + } + else + { + await ctx.Response.WriteAsync("Disabled"); + } + }); + } } } diff --git a/test/TestSites/StartupUpgradeFeatureDetection.cs b/test/TestSites/StartupUpgradeFeatureDetection.cs new file mode 100644 index 0000000000..81bc3149a0 --- /dev/null +++ b/test/TestSites/StartupUpgradeFeatureDetection.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace TestSites +{ + public class StartupUpgradeFeatureDetection + { + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + app.Run(async ctx => + { + if (ctx.Features.Get() != null) + { + await ctx.Response.WriteAsync("Enabled"); + } + else + { + await ctx.Response.WriteAsync("Disabled"); + } + }); + } + } +}