From 6020fdef0b0471a7ba32f1e1c5bd67b6032155ce Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 7 Aug 2020 11:08:40 -0700 Subject: [PATCH] Add reset support for IIS (#24552) --- .../managedexports.cpp | 12 + .../Core/IISHttpContext.FeatureCollection.cs | 30 +- .../IIS/src/Core/IISHttpContext.Features.cs | 15 + .../IIS/IIS/src/Core/IISHttpContextOfT.cs | 7 + src/Servers/IIS/IIS/src/NativeMethods.cs | 8 + .../Inprocess/ResetTests.cs | 413 ++++++++++++++++++ .../IIS.FunctionalTests.csproj | 18 + .../IIS.NewHandler.FunctionalTests.csproj | 19 + .../IIS.NewShim.FunctionalTests.csproj | 18 + .../IISExpress.FunctionalTests.csproj | 19 + .../testassets/InProcessWebSite/Startup.cs | 187 +++++++- src/Servers/IIS/IISIntegration.slnf | 31 +- 12 files changed, 759 insertions(+), 18 deletions(-) create mode 100644 src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResetTests.cs diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp index 25bd2a2da9..25ae8bb7fb 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp @@ -589,4 +589,16 @@ http_response_set_trailer( IHttpResponse4* pHttpResponse = (IHttpResponse4*)pInProcessHandler->QueryHttpContext()->GetResponse(); return pHttpResponse->SetTrailer(pszHeaderName, pszHeaderValue, usHeaderValueLength, fReplace); } + +EXTERN_C __MIDL_DECLSPEC_DLLEXPORT +VOID +http_reset_stream( + _In_ IN_PROCESS_HANDLER* pInProcessHandler, + ULONG errorCode +) +{ + IHttpResponse4* pHttpResponse = (IHttpResponse4*)pInProcessHandler->QueryHttpContext()->GetResponse(); + pHttpResponse->ResetStream(errorCode); +} + // End of export diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs index 7d5f3aafd9..a0b8e69934 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs @@ -33,7 +33,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core ITlsConnectionFeature, IHttpBodyControlFeature, IHttpMaxRequestBodySizeFeature, - IHttpResponseTrailersFeature + IHttpResponseTrailersFeature, + IHttpResetFeature { // 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. @@ -394,6 +395,33 @@ namespace Microsoft.AspNetCore.Server.IIS.Core set => ResponseTrailers = value; } + internal IHttpResetFeature GetResetFeature() + { + // Check version is above 2. + if (HttpVersion >= System.Net.HttpVersion.Version20 && NativeMethods.HttpSupportTrailer(_pInProcessHandler)) + { + return this; + } + + return null; + } + + void IHttpResetFeature.Reset(int errorCode) + { + if (errorCode < 0) + { + throw new ArgumentOutOfRangeException("'errorCode' cannot be negative"); + } + + SetResetCode(errorCode); + AbortIO(clientDisconnect: false); + } + + internal unsafe void SetResetCode(int errorCode) + { + NativeMethods.HttpResetStream(_pInProcessHandler, (ulong)errorCode); + } + void IHttpResponseBodyFeature.DisableBuffering() { NativeMethods.HttpDisableBuffering(_pInProcessHandler); diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.Features.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.Features.cs index 0945312bec..e918e18f84 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.Features.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.Features.cs @@ -29,6 +29,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core private static readonly Type IServerVariablesFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature); private static readonly Type IHttpMaxRequestBodySizeFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature); private static readonly Type IHttpResponseTrailersFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpResponseTrailersFeature); + private static readonly Type IHttpResetFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpResetFeature); private object _currentIHttpRequestFeature; private object _currentIHttpResponseFeature; @@ -50,6 +51,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core private object _currentIServerVariablesFeature; private object _currentIHttpMaxRequestBodySizeFeature; private object _currentIHttpResponseTrailersFeature; + private object _currentIHttpResetFeature; private void Initialize() { @@ -66,6 +68,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core _currentIHttpMaxRequestBodySizeFeature = this; _currentITlsConnectionFeature = this; _currentIHttpResponseTrailersFeature = GetResponseTrailersFeature(); + _currentIHttpResetFeature = GetResetFeature(); } internal object FastFeatureGet(Type key) @@ -154,6 +157,10 @@ namespace Microsoft.AspNetCore.Server.IIS.Core { return _currentIHttpResponseTrailersFeature; } + if (key == IHttpResetFeature) + { + return _currentIHttpResetFeature; + } return ExtraFeatureGet(key); } @@ -260,6 +267,10 @@ namespace Microsoft.AspNetCore.Server.IIS.Core { _currentIHttpResponseTrailersFeature = feature; } + if (key == IHttpResetFeature) + { + _currentIHttpResetFeature = feature; + } if (key == IISHttpContextType) { throw new InvalidOperationException("Cannot set IISHttpContext in feature collection"); @@ -349,6 +360,10 @@ namespace Microsoft.AspNetCore.Server.IIS.Core { yield return new KeyValuePair(IHttpResponseTrailersFeature, _currentIHttpResponseTrailersFeature as global::Microsoft.AspNetCore.Http.Features.IHttpResponseTrailersFeature); } + if (_currentIHttpResetFeature != null) + { + yield return new KeyValuePair(IHttpResponseTrailersFeature, _currentIHttpResetFeature as global::Microsoft.AspNetCore.Http.Features.IHttpResetFeature); + } if (MaybeExtra != null) { diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs index 070e13c48f..8731cfa2de 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs @@ -59,6 +59,13 @@ namespace Microsoft.AspNetCore.Server.IIS.Core // Dispose } + if (!success && HasResponseStarted) + { + // HTTP/2 INTERNAL_ERROR = 0x2 https://tools.ietf.org/html/rfc7540#section-7 + // Otherwise the default is Cancel = 0x8. + SetResetCode(2); + } + if (_onCompleted != null) { await FireOnCompleted(); diff --git a/src/Servers/IIS/IIS/src/NativeMethods.cs b/src/Servers/IIS/IIS/src/NativeMethods.cs index 83fb9ee7d7..1cb6f1ab24 100644 --- a/src/Servers/IIS/IIS/src/NativeMethods.cs +++ b/src/Servers/IIS/IIS/src/NativeMethods.cs @@ -146,6 +146,9 @@ namespace Microsoft.AspNetCore.Server.IIS [DllImport(AspNetCoreModuleDll)] private static extern unsafe int http_response_set_trailer(IntPtr pInProcessHandler, byte* pszHeaderName, byte* pszHeaderValue, ushort usHeaderValueLength, bool replace); + [DllImport(AspNetCoreModuleDll)] + private static extern unsafe int http_reset_stream(IntPtr pInProcessHandler, ulong errorCode); + [DllImport(AspNetCoreModuleDll)] private static extern unsafe int http_response_set_known_header(IntPtr pInProcessHandler, int headerId, byte* pHeaderValue, ushort length, bool fReplace); @@ -318,6 +321,11 @@ namespace Microsoft.AspNetCore.Server.IIS Validate(http_response_set_trailer(pInProcessHandler, pHeaderName, pHeaderValue, length, false)); } + internal static unsafe void HttpResetStream(IntPtr pInProcessHandler, ulong errorCode) + { + Validate(http_reset_stream(pInProcessHandler, errorCode)); + } + internal static unsafe bool HttpSupportTrailer(IntPtr pInProcessHandler) { bool supportsTrailers; diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResetTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResetTests.cs new file mode 100644 index 0000000000..8bcca34d26 --- /dev/null +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResetTests.cs @@ -0,0 +1,413 @@ +// 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.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http2Cat; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting.Common; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class ResetTests : IISFunctionalTestBase + { + public ResetTests(PublishedSitesFixture fixture) : base(fixture) + { + } + + private static readonly Version Win10_Regressed_DataFrame = new Version(10, 0, 20145, 0); + + public static readonly IEnumerable> Headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Scheme, "https"), + new KeyValuePair(HeaderNames.Authority, "localhost:443"), + new KeyValuePair("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"), + new KeyValuePair("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), + new KeyValuePair("accept-language", "en-US,en;q=0.5"), + new KeyValuePair("accept-encoding", "gzip, deflate, br"), + new KeyValuePair("upgrade-insecure-requests", "1"), + }; + + public static readonly IEnumerable> PostRequestHeaders = new[] +{ + new KeyValuePair(HeaderNames.Method, "POST"), + new KeyValuePair(HeaderNames.Scheme, "https"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + }; + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] + public async Task AppException_BeforeResponseHeaders_500() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + await new HostBuilder() + .UseHttp2Cat(deploymentResult.ApplicationBaseUri, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, GetHeaders("/AppException_BeforeResponseHeaders_500"), endStream: true); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("500", decodedHeaders[HeaderNames.Status]); + }); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame) + { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 0); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + } + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_20H1, SkipReason = "This is last version without custom Reset support")] + public async Task AppException_AfterHeaders_PriorOSVersions_ResetCancel() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + await new HostBuilder() + .UseHttp2Cat(deploymentResult.ApplicationBaseUri + "AppException_AfterHeaders_PriorOSVersions_ResetCancel", async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, GetHeaders("/AppException_AfterHeaders_PriorOSVersions_ResetCancel"), endStream: true); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, Http2ErrorCode.CANCEL); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Custom Reset support was added in Win10_20H2.")] + public async Task AppException_AfterHeaders_ResetInternalError() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + await new HostBuilder() + .UseHttp2Cat(deploymentResult.ApplicationBaseUri, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, GetHeaders("/AppException_AfterHeaders_ResetInternalError"), endStream: true); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var frame = await h2Connection.ReceiveFrameAsync(); + if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame) + { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + Http2Utilities.VerifyDataFrame(frame, 1, endOfStream: false, length: 0); + + frame = await h2Connection.ReceiveFrameAsync(); + } + Http2Utilities.VerifyResetFrame(frame, expectedStreamId: 1, Http2ErrorCode.INTERNAL_ERROR); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + public async Task Reset_Http1_NotSupported() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var handler = new HttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + using HttpClient client = new HttpClient(handler); + client.DefaultRequestVersion = HttpVersion.Version11; + var response = await client.GetStringAsync(deploymentResult.ApplicationBaseUri + "Reset_Http1_NotSupported"); + Assert.Equal("Hello World", response); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] + [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_20H1, SkipReason = "This is last version without Reset support")] + public async Task Reset_PriorOSVersions_NotSupported() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var handler = new HttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + using HttpClient client = new HttpClient(handler); + client.DefaultRequestVersion = HttpVersion.Version20; + var response = await client.GetStringAsync(deploymentResult.ApplicationBaseUri); + Assert.Equal("Hello World", response); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_BeforeResponse_Resets() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + await new HostBuilder() + .UseHttp2Cat(deploymentResult.ApplicationBaseUri, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, GetHeaders("/Reset_BeforeResponse_Resets"), endStream: true); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + + // Any app errors? + var client = CreateClient(); + var response = await client.GetAsync(deploymentResult.ApplicationBaseUri + "/Reset_BeforeResponse_Resets_Complete"); + Assert.True(response.IsSuccessStatusCode); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_BeforeResponse_Zero_Resets() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + await new HostBuilder() + .UseHttp2Cat(deploymentResult.ApplicationBaseUri, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, GetHeaders("/Reset_BeforeResponse_Zero_Resets"), endStream: true); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)0); + + // Any app errors? + var client = CreateClient(); + var response = await client.GetAsync(deploymentResult.ApplicationBaseUri + "/Reset_BeforeResponse_Zero_Resets_Complete"); + Assert.True(response.IsSuccessStatusCode); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_AfterResponseHeaders_Resets() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + await new HostBuilder() + .UseHttp2Cat(deploymentResult.ApplicationBaseUri, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, GetHeaders("/Reset_AfterResponseHeaders_Resets"), endStream: true); + + // Any app errors? + var client = CreateClient(); + var response = await client.GetAsync(deploymentResult.ApplicationBaseUri + "/Reset_AfterResponseHeaders_Resets_Complete"); + Assert.True(response.IsSuccessStatusCode); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, expectedStreamId: 1, endOfStream: false, length: 0); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_DuringResponseBody_Resets() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + await new HostBuilder() + .UseHttp2Cat(deploymentResult.ApplicationBaseUri, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, GetHeaders("/Reset_DuringResponseBody_Resets"), endStream: true); + + // This is currently flaky, can either receive header or reset at this point + var headerOrResetFrame = await h2Connection.ReceiveFrameAsync(); + Assert.True(headerOrResetFrame.Type == Http2FrameType.HEADERS || headerOrResetFrame.Type == Http2FrameType.RST_STREAM); + + if (headerOrResetFrame.Type == Http2FrameType.HEADERS) + { + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 11); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + } + else + { + Http2Utilities.VerifyResetFrame(headerOrResetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + } + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_BeforeRequestBody_Resets() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + await new HostBuilder() + .UseHttp2Cat(deploymentResult.ApplicationBaseUri, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, GetPostHeaders("/Reset_BeforeRequestBody_Resets"), endStream: false); + + // Any app errors? + //Assert.Equal(0, await appResult.Task.DefaultTimeout()); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, "10.0.19529", SkipReason = "Reset support was added in Win10_20H2.")] + public async Task Reset_DuringRequestBody_Resets() + { + var deploymentParameters = GetHttpsDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + await new HostBuilder() + .UseHttp2Cat(deploymentResult.ApplicationBaseUri, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, GetPostHeaders("/Reset_DuringRequestBody_Resets"), endStream: false); + await h2Connection.SendDataAsync(1, new byte[10], endStream: false); + + // Any app errors? + //Assert.Equal(0, await appResult.Task.DefaultTimeout()); + + var resetFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + private static List> GetHeaders(string path) + { + var headers = Headers.ToList(); + + var kvp = new KeyValuePair(HeaderNames.Path, path); + headers.Add(kvp); + return headers; + } + + private static List> GetPostHeaders(string path) + { + var headers = PostRequestHeaders.ToList(); + + var kvp = new KeyValuePair(HeaderNames.Path, path); + headers.Add(kvp); + return headers; + } + + + private IISDeploymentParameters GetHttpsDeploymentParameters() + { + var port = TestPortHelper.GetNextSSLPort(); + var deploymentParameters = Fixture.GetBaseDeploymentParameters(); + deploymentParameters.ApplicationBaseUriHint = $"https://localhost:{port}/"; + deploymentParameters.AddHttpsToServerConfig(); + return deploymentParameters; + } + + private static HttpClient CreateClient() + { + var handler = new HttpClientHandler(); + handler.MaxResponseHeadersLength = 128; + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + var client = new HttpClient(handler); + return client; + } + + } +} diff --git a/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj index 0999c4903e..b03eeccaff 100644 --- a/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj +++ b/src/Servers/IIS/IIS/test/IIS.FunctionalTests/IIS.FunctionalTests.csproj @@ -5,6 +5,7 @@ IIS.FunctionalTests True true + true @@ -19,13 +20,30 @@ + + + + + + + + + + Microsoft.AspNetCore.Server.SharedStrings + + + + System.Net.Http.SR + + + diff --git a/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj index 4a5234c6bb..70f9a69469 100644 --- a/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj +++ b/src/Servers/IIS/IIS/test/IIS.NewHandler.FunctionalTests/IIS.NewHandler.FunctionalTests.csproj @@ -5,6 +5,7 @@ IISNewHandler.FunctionalTests True true + true @@ -12,6 +13,12 @@ + + + + + + @@ -30,10 +37,22 @@ + + + + Microsoft.AspNetCore.Server.SharedStrings + + + + System.Net.Http.SR + + + + diff --git a/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj index 6ab82203b9..e70b5c17d4 100644 --- a/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj +++ b/src/Servers/IIS/IIS/test/IIS.NewShim.FunctionalTests/IIS.NewShim.FunctionalTests.csproj @@ -5,6 +5,7 @@ IISNewShim.FunctionalTests True true + true @@ -21,13 +22,30 @@ + + + + + + + + + + Microsoft.AspNetCore.Server.SharedStrings + + + + System.Net.Http.SR + + + diff --git a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj index 28d78f1f47..7fdf5ba6ad 100644 --- a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj +++ b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj @@ -7,6 +7,7 @@ True IISExpress.FunctionalTests true + true @@ -23,13 +24,31 @@ + + + + + + + + + + Microsoft.AspNetCore.Server.SharedStrings + + + + System.Net.Http.SR + + + + diff --git a/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs b/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs index 2fcdec580b..bf173864ae 100644 --- a/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs +++ b/src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs @@ -1033,7 +1033,7 @@ namespace TestSite return Task.CompletedTask; } - #if !FORWARDCOMPAT +#if !FORWARDCOMPAT public Task ResponseTrailers_HTTP2_TrailersAvailable(HttpContext context) { Assert.Equal("HTTP/2", context.Request.Protocol); @@ -1135,6 +1135,191 @@ namespace TestSite return Task.FromResult(0); } + public Task AppException_BeforeResponseHeaders_500(HttpContext context) + { + throw new Exception("Application exception"); + } + + public async Task AppException_AfterHeaders_PriorOSVersions_ResetCancel(HttpContext httpContext) + { + await httpContext.Response.Body.FlushAsync(); + throw new Exception("Application exception"); + } + + public async Task AppException_AfterHeaders_ResetInternalError(HttpContext httpContext) + { + await httpContext.Response.Body.FlushAsync(); + throw new Exception("Application exception"); + } + + public Task Reset_PriorOSVersions_NotSupported(HttpContext httpContext) + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.Null(feature); + return httpContext.Response.WriteAsync("Hello World"); + } + + public Task Reset_Http1_NotSupported(HttpContext httpContext) + { + Assert.Equal("HTTP/1.1", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.Null(feature); + return httpContext.Response.WriteAsync("Hello World"); + } + + private TaskCompletionSource _resetBeforeResponseResetsCts = new TaskCompletionSource(); + public Task Reset_BeforeResponse_Resets(HttpContext httpContext) + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + feature.Reset(1111); // Custom + _resetBeforeResponseResetsCts.SetResult(0); + } + catch (Exception ex) + { + _resetBeforeResponseResetsCts.SetException(ex); + } + return Task.FromResult(0); + } + + public async Task Reset_BeforeResponse_Resets_Complete(HttpContext httpContext) + { + await _resetBeforeResponseResetsCts.Task; + } + + private TaskCompletionSource _resetBeforeResponseZeroResetsCts = new TaskCompletionSource(); + public Task Reset_BeforeResponse_Zero_Resets(HttpContext httpContext) + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + feature.Reset(0); // Zero should be an allowed errorCode + _resetBeforeResponseZeroResetsCts.SetResult(0); + } + catch (Exception ex) + { + _resetBeforeResponseZeroResetsCts.SetException(ex); + } + return Task.FromResult(0); + } + + public async Task Reset_BeforeResponse_Resets_Zero_Complete(HttpContext httpContext) + { + await _resetBeforeResponseZeroResetsCts.Task; + } + + private TaskCompletionSource _resetAfterResponseHeadersResetsCts = new TaskCompletionSource(); + + public async Task Reset_AfterResponseHeaders_Resets(HttpContext httpContext) + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + await httpContext.Response.Body.FlushAsync(); + feature.Reset(1111); // Custom + _resetAfterResponseHeadersResetsCts.SetResult(0); + } + catch (Exception ex) + { + _resetAfterResponseHeadersResetsCts.SetException(ex); + } + } + public async Task Reset_AfterResponseHeaders_Resets_Complete(HttpContext httpContext) + { + await _resetAfterResponseHeadersResetsCts.Task; + } + + private TaskCompletionSource _resetDuringResponseBodyResetsCts = new TaskCompletionSource(); + + public async Task Reset_DuringResponseBody_Resets(HttpContext httpContext) + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + await httpContext.Response.WriteAsync("Hello World"); + await httpContext.Response.Body.FlushAsync(); + feature.Reset(1111); // Custom + _resetDuringResponseBodyResetsCts.SetResult(0); + } + catch (Exception ex) + { + _resetDuringResponseBodyResetsCts.SetException(ex); + } + } + + public async Task Reset_DuringResponseBody_Resets_Complete(HttpContext httpContext) + { + await _resetDuringResponseBodyResetsCts.Task; + } + + private TaskCompletionSource _resetBeforeRequestBodyResetsCts = new TaskCompletionSource(); + + public async Task Reset_BeforeRequestBody_Resets(HttpContext httpContext) + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + var readTask = httpContext.Request.Body.ReadAsync(new byte[10], 0, 10); + + feature.Reset(1111); + + await Assert.ThrowsAsync(() => readTask); + + _resetBeforeRequestBodyResetsCts.SetResult(0); + } + catch (Exception ex) + { + _resetBeforeRequestBodyResetsCts.SetException(ex); + } + } + + public async Task Reset_BeforeRequestBody_Resets_Complete(HttpContext httpContext) + { + await _resetBeforeRequestBodyResetsCts.Task; + } + + private TaskCompletionSource _resetDuringRequestBodyResetsCts = new TaskCompletionSource(); + + public async Task Reset_DuringRequestBody_Resets(HttpContext httpContext) + { + try + { + Assert.Equal("HTTP/2", httpContext.Request.Protocol); + var feature = httpContext.Features.Get(); + Assert.NotNull(feature); + + var read = await httpContext.Request.Body.ReadAsync(new byte[10], 0, 10); + Assert.Equal(10, read); + + var readTask = httpContext.Request.Body.ReadAsync(new byte[10], 0, 10); + feature.Reset(1111); + await Assert.ThrowsAsync(() => readTask); + + _resetDuringRequestBodyResetsCts.SetResult(0); + } + catch (Exception ex) + { + _resetDuringRequestBodyResetsCts.SetException(ex); + } + } + + public async Task Reset_DuringRequestBody_Resets_Complete(HttpContext httpContext) + { + await _resetDuringRequestBodyResetsCts.Task; + } + internal static readonly HashSet<(string, StringValues, StringValues)> NullTrailers = new HashSet<(string, StringValues, StringValues)>() { ("NullString", (string)null, (string)null), diff --git a/src/Servers/IIS/IISIntegration.slnf b/src/Servers/IIS/IISIntegration.slnf index 604dcae459..bbe169943e 100644 --- a/src/Servers/IIS/IISIntegration.slnf +++ b/src/Servers/IIS/IISIntegration.slnf @@ -1,30 +1,29 @@ -{ +{ "solution": { "path": "..\\..\\..\\AspNetCore.sln", - "projects" : [ - "src\\Servers\\IIS\\IISIntegration\\src\\Microsoft.AspNetCore.Server.IISIntegration.csproj", - "src\\Servers\\IIS\\IIS\\test\\IISExpress.FunctionalTests\\IISExpress.FunctionalTests.csproj", - "src\\Servers\\IIS\\IIS\\samples\\NativeIISSample\\NativeIISSample.csproj", - "src\\Servers\\IIS\\IIS\\src\\Microsoft.AspNetCore.Server.IIS.csproj", + "projects": [ "src\\Servers\\IIS\\AspNetCoreModuleV2\\AspNetCore\\AspNetCore.vcxproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\CommonLibTests\\CommonLibTests.vcxproj", "src\\Servers\\IIS\\AspNetCoreModuleV2\\CommonLib\\CommonLib.vcxproj", "src\\Servers\\IIS\\AspNetCoreModuleV2\\IISLib\\IISLib.vcxproj", - "src\\Servers\\IIS\\IIS\\benchmarks\\IIS.Performance\\IIS.Performance.csproj", "src\\Servers\\IIS\\AspNetCoreModuleV2\\InProcessRequestHandler\\InProcessRequestHandler.vcxproj", "src\\Servers\\IIS\\AspNetCoreModuleV2\\OutOfProcessRequestHandler\\OutOfProcessRequestHandler.vcxproj", "src\\Servers\\IIS\\AspNetCoreModuleV2\\RequestHandlerLib\\RequestHandlerLib.vcxproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\Symbols\\Microsoft.AspNetCore.ANCMSymbols.csproj", + "src\\Servers\\IIS\\AspNetCoreModuleV2\\gtest\\gtest.vcxproj", + "src\\Servers\\IIS\\IISIntegration\\samples\\IISSample\\IISSample.csproj", + "src\\Servers\\IIS\\IISIntegration\\src\\Microsoft.AspNetCore.Server.IISIntegration.csproj", + "src\\Servers\\IIS\\IISIntegration\\test\\Tests\\Microsoft.AspNetCore.Server.IISIntegration.Tests.csproj", + "src\\Servers\\IIS\\IIS\\benchmarks\\IIS.Performance\\IIS.Performance.csproj", + "src\\Servers\\IIS\\IIS\\samples\\NativeIISSample\\NativeIISSample.csproj", + "src\\Servers\\IIS\\IIS\\src\\Microsoft.AspNetCore.Server.IIS.csproj", "src\\Servers\\IIS\\IIS\\test\\IIS.FunctionalTests\\IIS.FunctionalTests.csproj", - "src\\Servers\\IIS\\IIS\\test\\IIS.Tests\\IIS.Tests.csproj", - "src\\Servers\\IIS\\IIS\\test\\testassets\\IIS.Common.TestLib\\IIS.Common.TestLib.csproj", "src\\Servers\\IIS\\IIS\\test\\IIS.NewHandler.FunctionalTests\\IIS.NewHandler.FunctionalTests.csproj", "src\\Servers\\IIS\\IIS\\test\\IIS.NewShim.FunctionalTests\\IIS.NewShim.FunctionalTests.csproj", - "src\\Servers\\IIS\\IIS\\test\\testassets\\TestTasks\\TestTasks.csproj", + "src\\Servers\\IIS\\IIS\\test\\IIS.Tests\\IIS.Tests.csproj", + "src\\Servers\\IIS\\IIS\\test\\IISExpress.FunctionalTests\\IISExpress.FunctionalTests.csproj", "src\\Servers\\IIS\\IIS\\test\\testassets\\InProcessWebSite\\InProcessWebSite.csproj", - "src\\Servers\\IIS\\IISIntegration\\test\\Tests\\Microsoft.AspNetCore.Server.IISIntegration.Tests.csproj", - "src\\Servers\\IIS\\IntegrationTesting.IIS\\src\\Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj", - "src\\Servers\\IIS\\AspNetCoreModuleV2\\CommonLibTests\\CommonLibTests.vcxproj", - "src\\Servers\\IIS\\IISIntegration\\samples\\IISSample\\IISSample.csproj", - "src\\Servers\\IIS\\AspNetCoreModuleV2\\gtest\\gtest.vcxproj" + "src\\Servers\\IIS\\IntegrationTesting.IIS\\src\\Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj" ] } -} +} \ No newline at end of file