Adds websocket feature detection, test cleanup. (#624)

This commit is contained in:
Justin Kotalik 2018-03-08 09:52:45 -08:00 committed by GitHub
parent ad4b172660
commit 00b1948937
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 772 additions and 467 deletions

View File

@ -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}

View File

@ -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<IHttpUpgradeFeature>() != null)
{
await context.Response.WriteAsync("Websocket feature is enabled.");
}
else
{
await context.Response.WriteAsync("Websocket feature is disabled.");
}
});
}

View File

@ -18,4 +18,4 @@
<aspNetCore processPath="dotnet" arguments=".\IISSample.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
-->
</system.webServer>
</configuration>
</configuration>

View File

@ -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<IHttpUpgradeFeature>() != 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)

View File

@ -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<MULTISZ *>(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<ENVIRONMENT_VAR_HASH *>(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 &);

View File

@ -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<IISOptions> 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<IISOptions> options,
string pairingToken,
IAuthenticationSchemeProvider authentication,
IApplicationLifetime applicationLifetime)
: this(next, loggerFactory, options, pairingToken, isWebsocketsSupported: true, authentication, applicationLifetime)
{
}
public IISMiddleware(RequestDelegate next,
ILoggerFactory loggerFactory,
IOptions<IISOptions> 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<IISMiddleware>();
_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<IHttpUpgradeFeature>(null);
}
await _next(httpContext);
}
}

View File

@ -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<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
@ -25,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{
app.UsePathBase(_pathBase);
app.UseForwardedHeaders();
app.UseMiddleware<IISMiddleware>(_pairingToken);
app.UseMiddleware<IISMiddleware>(_pairingToken, _isWebsocketsSupported);
next(app);
};
}

View File

@ -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;

View File

@ -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<byte> 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
}
}
}

View File

@ -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";
/// <summary>
/// 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<IStartupFilter>(new IISSetupFilter(pairingToken, new PathString(path)));
services.AddSingleton<IStartupFilter>(new IISSetupFilter(pairingToken, new PathString(path), isWebSocketsSupported));
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;

View File

@ -208,6 +208,7 @@
<ItemGroup>
<ClInclude Include="aspnetcore_event.h" />
<ClInclude Include="disconnectcontext.h" />
<ClInclude Include="environmentvariablehelpers.h" />
<ClInclude Include="sttimer.h" />
<ClInclude Include="outofprocess\forwarderconnection.h" />
<ClInclude Include="outofprocess\processmanager.h" />

View File

@ -80,6 +80,7 @@
<ClInclude Include="aspnetcore_event.h" />
<ClInclude Include="sttimer.h" />
<ClInclude Include="disconnectcontext.h" />
<ClInclude Include="environmentvariablehelpers.h" />
</ItemGroup>
<ItemGroup>
<None Include="Source.def" />

View File

@ -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;

View File

@ -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<MULTISZ *>(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<ENVIRONMENT_VAR_HASH *>(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<HRESULT*>(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<HRESULT*>(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();
};

View File

@ -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;

View File

@ -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

View File

@ -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
//

View File

@ -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;

View File

@ -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"

View File

@ -212,9 +212,9 @@
-->
<globalModules>
<add name="UriCacheModule" image="%IIS_BIN%\cachuri.dll" />
<!-- <add name="FileCacheModule" image="%IIS_BIN%\cachfile.dll" /> -->
<!-- <add name="FileCacheModule" image="%IIS_BIN%\cachfile.dll" /> -->
<add name="TokenCacheModule" image="%IIS_BIN%\cachtokn.dll" />
<!-- <add name="HttpCacheModule" image="%IIS_BIN%\cachhttp.dll" /> -->
<!-- <add name="HttpCacheModule" image="%IIS_BIN%\cachhttp.dll" /> -->
<add name="DynamicCompressionModule" image="%IIS_BIN%\compdyn.dll" />
<add name="StaticCompressionModule" image="%IIS_BIN%\compstat.dll" />
<add name="DefaultDocumentModule" image="%IIS_BIN%\defdoc.dll" />
@ -228,7 +228,7 @@
<add name="UrlAuthorizationModule" image="%IIS_BIN%\urlauthz.dll" />
<add name="BasicAuthenticationModule" image="%IIS_BIN%\authbas.dll" />
<add name="WindowsAuthenticationModule" image="%IIS_BIN%\authsspi.dll" />
<!-- <add name="DigestAuthenticationModule" image="%IIS_BIN%\authmd5.dll" /> -->
<!-- <add name="DigestAuthenticationModule" image="%IIS_BIN%\authmd5.dll" /> -->
<add name="IISCertificateMappingAuthenticationModule" image="%IIS_BIN%\authmap.dll" />
<add name="IpRestrictionModule" image="%IIS_BIN%\iprestr.dll" />
<add name="DynamicIpRestrictionModule" image="%IIS_BIN%\diprestr.dll" />
@ -236,14 +236,14 @@
<add name="CustomLoggingModule" image="%IIS_BIN%\logcust.dll" />
<add name="CustomErrorModule" image="%IIS_BIN%\custerr.dll" />
<add name="HttpLoggingModule" image="%IIS_BIN%\loghttp.dll" />
<!-- <add name="TracingModule" image="%IIS_BIN%\iisetw.dll" /> -->
<!-- <add name="TracingModule" image="%IIS_BIN%\iisetw.dll" /> -->
<add name="FailedRequestsTracingModule" image="%IIS_BIN%\iisfreb.dll" />
<add name="RequestMonitorModule" image="%IIS_BIN%\iisreqs.dll" />
<add name="IsapiModule" image="%IIS_BIN%\isapi.dll" />
<add name="IsapiFilterModule" image="%IIS_BIN%\filter.dll" />
<add name="CgiModule" image="%IIS_BIN%\cgi.dll" />
<add name="FastCgiModule" image="%IIS_BIN%\iisfcgi.dll" />
<!-- <add name="WebDAVModule" image="%IIS_BIN%\webdav.dll" /> -->
<!-- <add name="WebDAVModule" image="%IIS_BIN%\webdav.dll" /> -->
<add name="RewriteModule" image="%IIS_BIN%\rewrite.dll" />
<add name="ConfigurationValidationModule" image="%IIS_BIN%\validcfg.dll" />
<add name="ApplicationInitializationModule" image="%IIS_BIN%\warmup.dll" />
@ -799,7 +799,7 @@
<tracing>
<traceProviderDefinitions>
<traceProviderDefinitions>
<add name="WWW Server" guid="{3a2a4e84-4c21-4981-ae10-3fda0d9b0f83}">
<areas>
<clear />
@ -870,7 +870,7 @@
<authoringRules />
</webdav>
<applicationInitialization />
<webSocket />
<webSocket enabled="false" />
</system.webServer>
<location path="" overrideMode="Allow">

View File

@ -5,15 +5,19 @@
</PropertyGroup>
<ItemGroup>
<Content Include="*.config" CopyToOutputDirectory="PreserveNewest" />
<Content Include="AppHostConfig\*.config" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.IISIntegration\Microsoft.AspNetCore.Server.IISIntegration.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Server.IntegrationTesting" Version="$(MicrosoftAspNetCoreServerIntegrationTestingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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)]

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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',

View File

@ -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,

View File

@ -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]

View File

@ -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;
}
}
}
}
}
}

View File

@ -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,

View File

@ -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

View File

@ -3,7 +3,7 @@
using Xunit;
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
/// <summary>
/// This type just maps collection names to available fixtures

View File

@ -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,

View File

@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<RootNamespace>Microsoft.AspNetCore.Server.IIS.FunctionalTests</RootNamespace>
<AssemblyName>Microsoft.AspNetCore.Server.IIS.FunctionalTests</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Server.IISIntegration\Microsoft.AspNetCore.Server.IISIntegration.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="*.config" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Server.IntegrationTesting" Version="$(MicrosoftAspNetCoreServerIntegrationTestingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -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]

View File

@ -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"));
}
}
}

View File

@ -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<IHttpUpgradeFeature>() != null)
{
await ctx.Response.WriteAsync("Enabled");
}
else
{
await ctx.Response.WriteAsync("Disabled");
}
});
}
}
}

View File

@ -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<IHttpUpgradeFeature>() != null)
{
await ctx.Response.WriteAsync("Enabled");
}
else
{
await ctx.Response.WriteAsync("Disabled");
}
});
}
}
}