Implement IHttpBufferingFeature (#1391)

This commit is contained in:
Pavel Krymets 2018-09-17 12:04:12 -07:00 committed by GitHub
parent 2cd6ad6d50
commit 5e896ca506
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 457 additions and 80 deletions

View File

@ -86,6 +86,19 @@ Finished:
return hr;
}
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
HRESULT
http_set_server_variable(
_In_ IN_PROCESS_HANDLER* pInProcessHandler,
_In_ PCSTR pszVariableName,
_In_ PCWSTR pszVariableValue
)
{
return pInProcessHandler
->QueryHttpContext()
->SetServerVariable(pszVariableName, pszVariableValue);
}
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
HRESULT
http_set_response_status_code(
@ -400,6 +413,17 @@ http_cancel_io(
return pInProcessHandler->QueryHttpContext()->CancelIo();
}
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
HRESULT
http_disable_buffering(
_In_ IN_PROCESS_HANDLER* pInProcessHandler
)
{
pInProcessHandler->QueryHttpContext()->GetResponse()->DisableBuffering();
return S_OK;
}
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
HRESULT
http_response_set_unknown_header(

View File

@ -23,7 +23,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
IHttpUpgradeFeature,
IHttpRequestLifetimeFeature,
IHttpAuthenticationFeature,
IServerVariablesFeature
IServerVariablesFeature,
IHttpBufferingFeature
{
// NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation,
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
@ -202,7 +203,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{
if (string.IsNullOrEmpty(variableName))
{
return null;
throw new ArgumentException($"{nameof(variableName)} should be non-empty string");
}
// Synchronize access to native methods that might run in parallel with IO loops
@ -211,6 +212,19 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
return NativeMethods.HttpTryGetServerVariable(_pInProcessHandler, variableName, out var value) ? value : null;
}
}
set
{
if (string.IsNullOrEmpty(variableName))
{
throw new ArgumentException($"{nameof(variableName)} should be non-empty string");
}
// Synchronize access to native methods that might run in parallel with IO loops
lock (_contextLock)
{
NativeMethods.HttpSetServerVariable(_pInProcessHandler, variableName, value);
}
}
}
object IFeatureCollection.this[Type key]
@ -284,5 +298,21 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{
Abort();
}
void IHttpBufferingFeature.DisableRequestBuffering()
{
}
void IHttpBufferingFeature.DisableResponseBuffering()
{
NativeMethods.HttpDisableBuffering(_pInProcessHandler);
DisableCompression();
}
private void DisableCompression()
{
var serverVariableFeature = (IServerVariablesFeature)this;
serverVariableFeature["IIS_EnableDynamicCompression"] = "0";
}
}
}

View File

@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
private static readonly Type IISHttpContextType = typeof(IISHttpContext);
private static readonly Type IServerVariablesFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature);
private static readonly Type IHttpBufferingFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBufferingFeature);
private object _currentIHttpRequestFeature;
private object _currentIHttpResponseFeature;
@ -41,14 +42,12 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
private object _currentIResponseCookiesFeature;
private object _currentIItemsFeature;
private object _currentITlsConnectionFeature;
private object _currentIHttpMaxRequestBodySizeFeature;
private object _currentIHttpMinRequestBodyDataRateFeature;
private object _currentIHttpMinResponseDataRateFeature;
private object _currentIHttpWebSocketFeature;
private object _currentISessionFeature;
private object _currentIHttpBodyControlFeature;
private object _currentIHttpSendFileFeature;
private object _currentIServerVariablesFeature;
private object _currentIHttpBufferingFeature;
private void Initialize()
{
@ -58,12 +57,10 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_currentIHttpRequestIdentifierFeature = this;
_currentIHttpRequestLifetimeFeature = this;
_currentIHttpConnectionFeature = this;
_currentIHttpMaxRequestBodySizeFeature = this;
_currentIHttpMinRequestBodyDataRateFeature = this;
_currentIHttpMinResponseDataRateFeature = this;
_currentIHttpBodyControlFeature = this;
_currentIHttpAuthenticationFeature = this;
_currentIServerVariablesFeature = this;
_currentIHttpBufferingFeature = this;
}
internal object FastFeatureGet(Type key)
@ -142,7 +139,11 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
}
if (key == IServerVariablesFeature)
{
return this;
return _currentIServerVariablesFeature;
}
if (key == IHttpBufferingFeature)
{
return _currentIHttpBufferingFeature;
}
return ExtraFeatureGet(key);
@ -242,6 +243,11 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_currentIServerVariablesFeature = feature;
return;
}
if (key == IHttpBufferingFeature)
{
_currentIHttpBufferingFeature = feature;
return;
}
if (key == IISHttpContextType)
{
throw new InvalidOperationException("Cannot set IISHttpContext in feature collection");
@ -323,6 +329,10 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{
yield return new KeyValuePair<Type, object>(IServerVariablesFeature, _currentIServerVariablesFeature as global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature);
}
if (_currentIHttpBufferingFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpBufferingFeature, _currentIHttpBufferingFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBufferingFeature);
}
if (MaybeExtra != null)
{

View File

@ -15,10 +15,10 @@ namespace Microsoft.AspNetCore.Http.Features
public interface IServerVariablesFeature
{
/// <summary>
/// Gets the value of a server variable for the current request.
/// Gets or sets the value of a server variable for the current request.
/// </summary>
/// <param name="variableName">The variable name</param>
/// <returns>May return null or empty if the variable does not exist or is not set.</returns>
string this[string variableName] { get; }
string this[string variableName] { get; set; }
}
}

View File

@ -76,6 +76,9 @@ namespace Microsoft.AspNetCore.Server.IIS
[DllImport(AspNetCoreModuleDll)]
private static extern int http_stop_incoming_requests(IntPtr pInProcessApplication);
[DllImport(AspNetCoreModuleDll)]
private static extern int http_disable_buffering(IntPtr pInProcessApplication);
[DllImport(AspNetCoreModuleDll, CharSet = CharSet.Ansi)]
private static extern int http_set_response_status_code(IntPtr pInProcessHandler, ushort statusCode, string pszReason);
@ -97,6 +100,12 @@ namespace Microsoft.AspNetCore.Server.IIS
[MarshalAs(UnmanagedType.LPStr)] string variableName,
[MarshalAs(UnmanagedType.BStr)] out string value);
[DllImport(AspNetCoreModuleDll)]
private static extern int http_set_server_variable(
IntPtr pInProcessHandler,
[MarshalAs(UnmanagedType.LPStr)] string variableName,
[MarshalAs(UnmanagedType.LPWStr)] string value);
[DllImport(AspNetCoreModuleDll)]
private static extern unsafe int http_websockets_read_bytes(
IntPtr pInProcessHandler,
@ -176,6 +185,11 @@ namespace Microsoft.AspNetCore.Server.IIS
Validate(http_stop_incoming_requests(pInProcessApplication));
}
public static void HttpDisableBuffering(IntPtr pInProcessApplication)
{
Validate(http_disable_buffering(pInProcessApplication));
}
public static void HttpSetResponseStatusCode(IntPtr pInProcessHandler, ushort statusCode, string pszReason)
{
Validate(http_set_response_status_code(pInProcessHandler, statusCode, pszReason));
@ -208,6 +222,11 @@ namespace Microsoft.AspNetCore.Server.IIS
return http_get_server_variable(pInProcessHandler, variableName, out value) == 0;
}
public static void HttpSetServerVariable(IntPtr pInProcessHandler, string variableName, string value)
{
Validate(http_set_server_variable(pInProcessHandler, variableName, value));
}
public static unsafe int HttpWebsocketsReadBytes(
IntPtr pInProcessHandler,
byte* pvBuffer,

View File

@ -80,5 +80,31 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
aspNetCoreElement.SetAttributeValue("arguments", transformation((string)aspNetCoreElement.Attribute("arguments"), contentRoot));
});
}
public static void EnableModule(this IISDeploymentParameters parameters, string moduleName, string modulePath)
{
if (parameters.ServerType == ServerType.IIS)
{
modulePath = modulePath.Replace("%IIS_BIN%", "%windir%\\System32\\inetsrv");
}
parameters.ServerConfigActionList.Add(
(element, _) => {
var webServerElement = element
.RequiredElement("system.webServer");
webServerElement
.RequiredElement("globalModules")
.GetOrAdd("add", "name", moduleName)
.SetAttributeValue("image", modulePath);
(webServerElement.Element("modules") ??
element
.Element("location")
.RequiredElement("system.webServer")
.RequiredElement("modules"))
.GetOrAdd("add", "name", moduleName);
});
}
}
}

View File

@ -0,0 +1,96 @@
// 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.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[Collection(IISCompressionSiteCollection.Name)]
public class CompressionTests : FixtureLoggedTest
{
private readonly IISCompressionSiteFixture _fixture;
public CompressionTests(IISCompressionSiteFixture fixture): base(fixture)
{
_fixture = fixture;
}
[ConditionalTheory]
[RequiresIIS(IISCapability.DynamicCompression)]
[InlineData(true)]
[InlineData(false)]
public async Task BufferingDisabled(bool compression)
{
using (var connection = _fixture.CreateTestConnection())
{
var requestLength = 0;
var messages = new List<string>();
for (var i = 1; i < 100; i++)
{
var message = i + Environment.NewLine;
requestLength += message.Length;
messages.Add(message);
}
await connection.Send(
"POST /ReadAndWriteEchoLinesNoBuffering HTTP/1.1",
$"Content-Length: {requestLength}",
"Accept-Encoding: " + (compression ? "gzip": "identity"),
"Response-Content-Type: text/event-stream",
"Host: localhost",
"Connection: close",
"",
"");
await connection.Receive(
"HTTP/1.1 200 OK",
"");
await connection.ReceiveHeaders();
foreach (var message in messages)
{
await connection.Send(message);
await connection.ReceiveChunk(message);
}
await connection.Send("\r\n");
await connection.ReceiveChunk("");
await connection.WaitForConnectionClose();
}
}
[ConditionalFact]
[RequiresIIS(IISCapability.DynamicCompression)]
public async Task DynamicResponsesAreCompressed()
{
var handler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip
};
var client = new HttpClient(handler)
{
BaseAddress = _fixture.Client.BaseAddress,
};
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("identity", 0));
client.DefaultRequestHeaders.Add("Response-Content-Type", "text/event-stream");
var messages = "Message1\r\nMessage2\r\n";
// Send messages with terminator
var response = await client.PostAsync("ReadAndWriteEchoLines", new StringContent(messages + "\r\n"));
Assert.Equal(messages, await response.Content.ReadAsStringAsync());
Assert.True(response.Content.Headers.TryGetValues("Content-Type", out var contentTypes));
Assert.Single(contentTypes, "text/event-stream");
// Not the cleanest check but I wasn't able to figure out other way to check
// that response was compressed
Assert.Contains("gzip", response.Content.GetType().FullName, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@ -34,6 +34,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.Equal("THIS_VAR_IS_UNDEFINED: (null)", await _fixture.Client.GetStringAsync("/ServerVariable?q=THIS_VAR_IS_UNDEFINED"));
}
[ConditionalFact]
public async Task CanSetAndReadVariable()
{
Assert.Equal("ROUNDTRIP: 1", await _fixture.Client.GetStringAsync("/ServerVariable?v=1&q=ROUNDTRIP"));
}
[ConditionalFact]
public async Task BasePathIsNotPrefixedBySlashSlashQuestionMark()
{

View File

@ -12,6 +12,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Websockets = 1,
WindowsAuthentication = 2,
PoolEnvironmentVariables = 4,
ShutdownToken = 8
ShutdownToken = 8,
DynamicCompression = 16
}
}

View File

@ -0,0 +1,13 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[CollectionDefinition(Name)]
public class IISCompressionSiteCollection : ICollectionFixture<IISCompressionSiteFixture>
{
public const string Name = nameof(IISCompressionSiteCollection);
}
}

View File

@ -0,0 +1,44 @@
// 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.Collections.Generic;
using System.Net.Http;
using System.Threading;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
public class IISCompressionSiteFixture : IISTestSiteFixture
{
public IISCompressionSiteFixture() : base(Configure)
{
}
private static void Configure(IISDeploymentParameters deploymentParameters)
{
// Enable dynamic compression
deploymentParameters.ServerConfigActionList.Add(
(element, _) => {
var webServerElement = element
.RequiredElement("system.webServer");
webServerElement
.GetOrAdd("urlCompression")
.SetAttributeValue("doDynamicCompression", "true");
webServerElement
.GetOrAdd("httpCompression")
.GetOrAdd("dynamicTypes")
.GetOrAdd("add", "mimeType", "text/*")
.SetAttributeValue("enabled", "true");
});
deploymentParameters.EnableModule("DynamicCompressionModule", "%IIS_BIN%\\compdyn.dll");
}
}
}

View File

@ -14,40 +14,36 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
public class IISTestSiteFixture : IDisposable
{
private readonly ApplicationDeployer _deployer;
private readonly ForwardingProvider _forwardingProvider;
private ApplicationDeployer _deployer;
private ILoggerFactory _loggerFactory;
private ForwardingProvider _forwardingProvider;
private IISDeploymentResult _deploymentResult;
private readonly Action<IISDeploymentParameters> _configure;
public IISTestSiteFixture()
public IISTestSiteFixture() : this(_ => { })
{
var logging = AssemblyTestLog.ForAssembly(typeof(IISTestSiteFixture).Assembly);
var deploymentParameters = new IISDeploymentParameters(Helpers.GetInProcessTestSitesPath(),
DeployerSelector.ServerType,
RuntimeFlavor.CoreClr,
RuntimeArchitecture.x64)
{
TargetFramework = Tfm.NetCoreApp22,
AncmVersion = AncmVersion.AspNetCoreModuleV2,
HostingModel = HostingModel.InProcess,
PublishApplicationBeforeDeployment = true,
};
_forwardingProvider = new ForwardingProvider();
var loggerFactory = logging.CreateLoggerFactory(null, nameof(IISTestSiteFixture));
loggerFactory.AddProvider(_forwardingProvider);
_deployer = IISApplicationDeployerFactory.Create(deploymentParameters, loggerFactory);
DeploymentResult = (IISDeploymentResult)_deployer.DeployAsync().Result;
Client = DeploymentResult.HttpClient;
BaseUri = DeploymentResult.ApplicationBaseUri;
ShutdownToken = DeploymentResult.HostShutdownToken;
}
public string BaseUri { get; }
public HttpClient Client { get; }
public CancellationToken ShutdownToken { get; }
public IISDeploymentResult DeploymentResult { get; }
public IISTestSiteFixture(Action<IISDeploymentParameters> configure)
{
var logging = AssemblyTestLog.ForAssembly(typeof(IISTestSiteFixture).Assembly);
_loggerFactory = logging.CreateLoggerFactory(null, nameof(IISTestSiteFixture));
_forwardingProvider = new ForwardingProvider();
_loggerFactory.AddProvider(_forwardingProvider);
_configure = configure;
}
public HttpClient Client => DeploymentResult.HttpClient;
public IISDeploymentResult DeploymentResult
{
get
{
EnsureInitialized();
return _deploymentResult;
}
}
public TestConnection CreateTestConnection()
{
@ -56,7 +52,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
public void Dispose()
{
_deployer.Dispose();
_deployer?.Dispose();
}
public void Attach(LoggedTest test)
@ -79,6 +75,30 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
_forwardingProvider.LoggerFactory = null;
}
private void EnsureInitialized()
{
if (_deployer != null)
{
return;
}
var deploymentParameters = new IISDeploymentParameters(Helpers.GetInProcessTestSitesPath(),
DeployerSelector.ServerType,
RuntimeFlavor.CoreClr,
RuntimeArchitecture.x64)
{
TargetFramework = Tfm.NetCoreApp22,
AncmVersion = AncmVersion.AspNetCoreModuleV2,
HostingModel = HostingModel.InProcess,
PublishApplicationBeforeDeployment = true,
};
_configure(deploymentParameters);
_deployer = IISApplicationDeployerFactory.Create(deploymentParameters, _loggerFactory);
_deploymentResult = (IISDeploymentResult)_deployer.DeployAsync().Result;
}
private class ForwardingProvider : ILoggerProvider
{
private readonly List<ForwardingLogger> _loggers = new List<ForwardingLogger>();

View File

@ -24,7 +24,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
private readonly bool _ownsSocket;
private readonly Socket _socket;
private readonly NetworkStream _stream;
private readonly StreamReader _reader;
public TestConnection(int port)
: this(port, AddressFamily.InterNetwork)
@ -46,13 +45,10 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
_ownsSocket = ownsSocket;
_socket = socket;
_stream = new NetworkStream(_socket, ownsSocket: false);
_reader = new StreamReader(_stream, Encoding.ASCII);
}
public Socket Socket => _socket;
public StreamReader Reader => _reader;
public void Dispose()
{
_stream.Dispose();
@ -79,50 +75,96 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
await _stream.FlushAsync().ConfigureAwait(false);
}
public async Task<int> ReadCharAsync()
{
var bytes = new byte[1];
return (await _stream.ReadAsync(bytes, 0, 1) == 1) ? bytes[0] : -1;
}
public async Task<string> ReadLineAsync()
{
var builder = new StringBuilder();
var current = await ReadCharAsync();
while (current != '\r')
{
builder.Append((char)current);
current = await ReadCharAsync();
}
// Consume \n
await ReadCharAsync();
return builder.ToString();
}
public async Task<Memory<byte>> ReceiveChunk()
{
var length = int.Parse(await ReadLineAsync(), System.Globalization.NumberStyles.HexNumber);
var bytes = await Receive(length);
await ReadLineAsync();
return bytes;
}
public async Task ReceiveChunk(string expected)
{
Assert.Equal(expected, Encoding.ASCII.GetString((await ReceiveChunk()).Span));
}
public async Task Receive(params string[] lines)
{
var expected = string.Join("\r\n", lines);
var actual = new char[expected.Length];
var offset = 0;
var actual = await Receive(expected.Length);
Assert.Equal(expected, Encoding.ASCII.GetString(actual.Span));
}
private async Task<Memory<byte>> Receive(int length)
{
var actual = new byte[length];
int offset = 0;
try
{
while (offset < expected.Length)
while (offset < length)
{
var data = new byte[expected.Length];
var task = _reader.ReadAsync(actual, offset, actual.Length - offset);
var task = _stream.ReadAsync(actual, offset, actual.Length - offset);
if (!Debugger.IsAttached)
{
task = task.TimeoutAfter(Timeout);
}
var count = await task.ConfigureAwait(false);
if (count == 0)
{
break;
}
offset += count;
}
}
catch (TimeoutException ex) when (offset != 0)
{
throw new TimeoutException($"Did not receive a complete response within {Timeout}.{Environment.NewLine}{Environment.NewLine}" +
$"Expected:{Environment.NewLine}{expected}{Environment.NewLine}{Environment.NewLine}" +
$"Actual:{Environment.NewLine}{new string(actual, 0, offset)}{Environment.NewLine}",
throw new TimeoutException(
$"Did not receive a complete response within {Timeout}.{Environment.NewLine}{Environment.NewLine}" +
$"Expected:{Environment.NewLine}{length} bytes of data{Environment.NewLine}{Environment.NewLine}" +
$"Actual:{Environment.NewLine}{Encoding.ASCII.GetString(actual, 0, offset)}{Environment.NewLine}",
ex);
}
Assert.Equal(expected, new string(actual, 0, offset));
return actual.AsMemory(0, offset);
}
public async Task ReceiveStartsWith(string prefix, int maxLineLength = 1024)
{
var actual = new char[maxLineLength];
var actual = new byte[maxLineLength];
var offset = 0;
while (offset < maxLineLength)
{
// Read one char at a time so we don't read past the end of the line.
var task = _reader.ReadAsync(actual, offset, 1);
var task = _stream.ReadAsync(actual, offset, 1);
if (!Debugger.IsAttached)
{
Assert.True(task.Wait(4000), "timeout");
@ -142,7 +184,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
}
}
var actualLine = new string(actual, 0, offset);
var actualLine = Encoding.ASCII.GetString(actual, 0, offset);
Assert.StartsWith(prefix, actualLine);
}
@ -152,7 +194,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
string line;
do
{
line = await _reader.ReadLineAsync();
line = await ReadLineAsync();
headers.Add(line);
} while (line != "");
@ -190,7 +232,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
else
{
tcs.SetException(new IOException(
$"Expected connection close, received data instead: \"{_reader.CurrentEncoding.GetString(e.Buffer, 0, e.BytesTransferred)}\""));
$"Expected connection close, received data instead: \"{Encoding.ASCII.GetString(e.Buffer, 0, e.BytesTransferred)}\""));
}
}

View File

@ -18,12 +18,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
private static readonly bool _isMetStatic;
private static readonly string _skipReasonStatic;
private readonly bool _isMet;
private readonly string _skipReason;
private static readonly bool _websocketsAvailable;
private static readonly bool _windowsAuthAvailable;
private static readonly bool _poolEnvironmentVariablesAvailable;
private static readonly bool _dynamicCompressionAvailable;
static RequiresIISAttribute()
{
@ -84,6 +82,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
_windowsAuthAvailable = File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", "authsspi.dll"));
_dynamicCompressionAvailable = File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", "compdyn.dll"));
var iisRegistryKey = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\InetStp", writable: false);
if (iisRegistryKey == null)
{
@ -98,47 +98,56 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
}
}
public RequiresIISAttribute()
public RequiresIISAttribute()
: this (IISCapability.None) { }
public RequiresIISAttribute(IISCapability capabilities)
{
_isMet = _isMetStatic;
_skipReason = _skipReasonStatic;
IsMet = _isMetStatic;
SkipReason = _skipReasonStatic;
if (capabilities.HasFlag(IISCapability.Websockets))
{
_isMet &= _websocketsAvailable;
IsMet &= _websocketsAvailable;
if (!_websocketsAvailable)
{
_skipReason += "The machine does not have IIS websockets installed.";
SkipReason += "The machine does not have IIS websockets installed.";
}
}
if (capabilities.HasFlag(IISCapability.WindowsAuthentication))
{
_isMet &= _windowsAuthAvailable;
IsMet &= _windowsAuthAvailable;
if (!_windowsAuthAvailable)
{
_skipReason += "The machine does not have IIS windows authentication installed.";
SkipReason += "The machine does not have IIS windows authentication installed.";
}
}
if (capabilities.HasFlag(IISCapability.PoolEnvironmentVariables))
{
_isMet &= _poolEnvironmentVariablesAvailable;
IsMet &= _poolEnvironmentVariablesAvailable;
if (!_poolEnvironmentVariablesAvailable)
{
_skipReason += "The machine does allow for setting environment variables on application pools.";
SkipReason += "The machine does allow for setting environment variables on application pools.";
}
}
if (capabilities.HasFlag(IISCapability.ShutdownToken))
{
_isMet = false;
_skipReason += "https://github.com/aspnet/IISIntegration/issues/1074";
IsMet = false;
SkipReason += "https://github.com/aspnet/IISIntegration/issues/1074";
}
if (capabilities.HasFlag(IISCapability.DynamicCompression))
{
IsMet &= _dynamicCompressionAvailable;
if (!_dynamicCompressionAvailable)
{
SkipReason += "The machine does not have IIS dynamic compression installed.";
}
}
}
public bool IsMet => _isMet;
public string SkipReason => _skipReason;
public bool IsMet { get; }
public string SkipReason { get; }
}
}

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
public WebSocketsTests(IISTestSiteFixture fixture)
{
_webSocketUri = fixture.BaseUri.Replace("http:", "ws:");
_webSocketUri = fixture.DeploymentResult.ApplicationBaseUri.Replace("http:", "ws:");
}
[ConditionalFact]

View File

@ -30,7 +30,13 @@ namespace TestSite
private async Task ServerVariable(HttpContext ctx)
{
var varName = ctx.Request.Query["q"];
await ctx.Response.WriteAsync($"{varName}: {ctx.GetIISServerVariable(varName) ?? "(null)"}");
var newValue = ctx.Request.Query["v"];
var feature = ctx.Features.Get<IServerVariablesFeature>();
if (newValue.Count != 0)
{
feature[varName] = newValue;
}
await ctx.Response.WriteAsync($"{varName}: {feature[varName] ?? "(null)"}");
}
private async Task AuthenticationAnonymous(HttpContext ctx)
@ -321,6 +327,11 @@ namespace TestSite
private async Task ReadAndWriteEchoLines(HttpContext ctx)
{
if (ctx.Request.Headers.TryGetValue("Response-Content-Type", out var contentType))
{
ctx.Response.ContentType = contentType;
}
//Send headers
await ctx.Response.Body.FlushAsync();
@ -337,6 +348,31 @@ namespace TestSite
}
}
private async Task ReadAndWriteEchoLinesNoBuffering(HttpContext ctx)
{
var feature = ctx.Features.Get<IHttpBufferingFeature>();
feature.DisableResponseBuffering();
if (ctx.Request.Headers.TryGetValue("Response-Content-Type", out var contentType))
{
ctx.Response.ContentType = contentType;
}
//Send headers
await ctx.Response.Body.FlushAsync();
var reader = new StreamReader(ctx.Request.Body);
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
if (line == "")
{
return;
}
await ctx.Response.WriteAsync(line + Environment.NewLine);
}
}
private async Task ReadPartialBody(HttpContext ctx)
{
var data = new byte[5];
@ -631,10 +667,11 @@ namespace TestSite
// executed on background thread while request thread calls GetServerVariable
// concurrent native calls may cause native object corruption
var serverVariableFeature = ctx.Features.Get<IServerVariablesFeature>();
await ctx.Response.WriteAsync("Response Begin");
for (int i = 0; i < 1000; i++)
{
await ctx.Response.WriteAsync(ctx.GetIISServerVariable("REMOTE_PORT"));
await ctx.Response.WriteAsync(serverVariableFeature["REMOTE_PORT"]);
await ctx.Response.Body.FlushAsync();
}
await ctx.Response.WriteAsync("Response End");