From a842580fef43fec2b54062305de87ba006bdf851 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 18 Jul 2021 17:25:36 +1200 Subject: [PATCH 1/2] HTTP/3: Change alt-svc protocol to h3 and add upgrade test --- .../Core/src/Internal/Http/HttpProtocol.cs | 8 +-- ...Server.Kestrel.Transport.Quic.Tests.csproj | 5 ++ .../Transport.Quic/test/WebHostTests.cs | 67 +++++++++++++++++++ .../InMemory.FunctionalTests/ResponseTests.cs | 2 +- 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index d163b737b1cb..a79f4ef4753b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -1194,17 +1194,15 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted) } } - // TODO allow customization of this. - // Chrome is using h3-29 for protocol ID as of 12/2020. This is likely to be the alt-svc - // value until HTTP/3 is finalized. - // More info: https://blog.chromium.org/2020/10/chrome-is-deploying-http3-and-ietf-quic.html if (ServerOptions.EnableAltSvc && _httpVersion < Http.HttpVersion.Http3) { + // TODO: Perf. Avoid allocating enumerator and property's LINQ. foreach (var option in ServerOptions.ListenOptions) { if ((option.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) { - responseHeaders.HeaderAltSvc = $"h3-29=\":{option.IPEndPoint!.Port}\"; ma=84600"; + // TODO: Perf. Create string once instead of per-request. + responseHeaders.HeaderAltSvc = $"h3=\":{option.IPEndPoint!.Port}\"; ma=84600"; break; } } diff --git a/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj b/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj index 4b24207b2ac9..ca4fbc0ea84d 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj +++ b/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj @@ -14,6 +14,11 @@ + + + + + diff --git a/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs b/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs index c36f9a0b6f63..30ff9766e4a0 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs @@ -152,6 +152,73 @@ public async Task Listen_Http3AndSocketsCoexistOnSameEndpoint_ClientSuccess() await host.StopAsync().DefaultTimeout(); } + [ConditionalFact] + [MsQuicSupported] + public async Task Listen_Http3AndSocketsCoexistOnSameEndpoint_AltSvcEnabled_Upgrade() + { + // Arrange + var builder = GetHostBuilder() + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseKestrel(o => + { + o.EnableAltSvc = true; + o.Listen(IPAddress.Parse("127.0.0.1"), 0, listenOptions => + { + listenOptions.Protocols = Core.HttpProtocols.Http1AndHttp2AndHttp3; + listenOptions.UseHttps(); + }); + }) + .Configure(app => + { + app.Run(async context => + { + await context.Response.WriteAsync("hello, world"); + }); + }); + }) + .ConfigureServices(AddTestLogging); + + using var host = builder.Build(); + await host.StartAsync().DefaultTimeout(); + + var httpClientHandler = new HttpClientHandler(); + httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + + using (var client = new HttpClient(httpClientHandler)) + { + // Act + var request1 = new HttpRequestMessage(HttpMethod.Get, $"https://127.0.0.1:{host.GetPort()}/"); + request1.VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher; + var response1 = await client.SendAsync(request1).DefaultTimeout(); + + // Assert + response1.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version20, response1.Version); + var responseText1 = await response1.Content.ReadAsStringAsync().DefaultTimeout(); + Assert.Equal("hello, world", responseText1); + + Assert.True(response1.Headers.TryGetValues("alt-svc", out var altSvcValues)); + Assert.Single(altSvcValues, @$"h3="":{host.GetPort()}""; ma=84600"); + + // Act + var request2 = new HttpRequestMessage(HttpMethod.Get, $"https://127.0.0.1:{host.GetPort()}/"); + request2.VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher; + var response2 = await client.SendAsync(request2).DefaultTimeout(); + + // Assert + response2.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version30, response2.Version); + var responseText2 = await response2.Content.ReadAsStringAsync().DefaultTimeout(); + Assert.Equal("hello, world", responseText2); + + Assert.False(response2.Headers.Contains("alt-svc")); + } + + await host.StopAsync().DefaultTimeout(); + } + private static async Task CallHttp3AndHttp1EndpointsAsync(int http3Port, int http1Port) { // HTTP/3 diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs index 330b443cc723..a56d1ad10040 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs @@ -4106,7 +4106,7 @@ await connection.Receive( $"HTTP/1.1 200 OK", "Content-Length: 0", $"Date: {server.Context.DateHeaderValue}", - @"Alt-Svc: h3-29="":1""; ma=84600", + @"Alt-Svc: h3="":1""; ma=84600", "", ""); } From 836d664bee463d7ceb3312df39296151f8b06d0d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 18 Jul 2021 17:27:13 +1200 Subject: [PATCH 2/2] Update --- src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index a79f4ef4753b..39dc14ff2318 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -1197,11 +1197,13 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted) if (ServerOptions.EnableAltSvc && _httpVersion < Http.HttpVersion.Http3) { // TODO: Perf. Avoid allocating enumerator and property's LINQ. + // https://github.com/dotnet/aspnetcore/issues/34468 foreach (var option in ServerOptions.ListenOptions) { if ((option.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) { // TODO: Perf. Create string once instead of per-request. + // https://github.com/dotnet/aspnetcore/issues/34468 responseHeaders.HeaderAltSvc = $"h3=\":{option.IPEndPoint!.Port}\"; ma=84600"; break; }