Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP/3: Change alt-svc protocol to h3 and add upgrade test #34469

Merged
merged 2 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1194,17 +1194,17 @@ 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.
// https://github.com/dotnet/aspnetcore/issues/34468
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.
// https://github.com/dotnet/aspnetcore/issues/34468
responseHeaders.HeaderAltSvc = $"h3=\":{option.IPEndPoint!.Port}\"; ma=84600";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latest draft has finalized "h3"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was testing the ALPN code with similar values. I think we should include both h3 and h3-29 for compat for at least a milestone, that's really common on websites I've checked. h3 alpn didn't work with HttpClient yet.

Copy link
Member Author

@JamesNK JamesNK Jul 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chrome (and Edge) support "h3"

image

If HttpClient and major browsers support h3 then I think it's fine to completely switch in RC1.

break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<!-- Required for QUIC & HTTP/3 in .NET 6 - https://github.com/dotnet/runtime/pull/55332 -->
<RuntimeHostConfigurationOption Include="System.Net.SocketsHttpHandler.Http3Support" Value="true" />
</ItemGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Http" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Core" />
Expand Down
67 changes: 67 additions & 0 deletions src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the ALPN setting need to be updated to match?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HttpClient is being inconsistent and using h3 in the altsvc but not as it’s alpn dotnet/runtime#55894

Copy link
Member

@Tratcher Tratcher Jul 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. The alt-svc and ALPN should be kept in sync, let's cover this in the meeting later today.

.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
"",
"");
}
Expand Down