Skip to content

Commit 7b85d7c

Browse files
authored
HTTP/3: Update alt-svc value to h3-29 and add unit test (#28772)
1 parent 8cb8375 commit 7b85d7c

File tree

5 files changed

+83
-4
lines changed

5 files changed

+83
-4
lines changed

src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1187,13 +1187,16 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted)
11871187
}
11881188

11891189
// TODO allow customization of this.
1190+
// Chrome is using h3-29 for protocol ID as of 12/2020. This is likely to be the alt-svc
1191+
// value until HTTP/3 is finalized.
1192+
// More info: https://blog.chromium.org/2020/10/chrome-is-deploying-http3-and-ietf-quic.html
11901193
if (ServerOptions.EnableAltSvc && _httpVersion < Http.HttpVersion.Http3)
11911194
{
11921195
foreach (var option in ServerOptions.ListenOptions)
11931196
{
11941197
if ((option.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3)
11951198
{
1196-
responseHeaders.HeaderAltSvc = $"h3-25=\":{option.IPEndPoint!.Port}\"; ma=84600";
1199+
responseHeaders.HeaderAltSvc = $"h3-29=\":{option.IPEndPoint!.Port}\"; ma=84600";
11971200
break;
11981201
}
11991202
}

src/Servers/Kestrel/Core/src/KestrelServerOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public class KestrelServerOptions
7676
public bool DisableStringReuse { get; set; } = false;
7777

7878
/// <summary>
79-
/// Controls whether to return the AltSvcHeader from on an HTTP/2 or lower response for HTTP/3
79+
/// Controls whether to return the "Alt-Svc" header from an HTTP/2 or lower response for HTTP/3.
8080
/// </summary>
8181
/// <remarks>
8282
/// Defaults to false.

src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Experimental.Quic
1818
{
1919
internal class QuicConnectionFactory : IMultiplexedConnectionFactory
2020
{
21-
private QuicTransportContext _transportContext;
21+
private readonly QuicTransportContext _transportContext;
2222

2323
public QuicConnectionFactory(IOptions<QuicTransportOptions> options, ILoggerFactory loggerFactory)
2424
{

src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Threading;
1313
using System.Threading.Tasks;
1414
using Microsoft.AspNetCore.Connections;
15+
using Microsoft.AspNetCore.Connections.Experimental;
1516
using Microsoft.AspNetCore.Connections.Features;
1617
using Microsoft.AspNetCore.Http;
1718
using Microsoft.AspNetCore.Http.Features;
@@ -4076,6 +4077,41 @@ await connection.Receive(
40764077
}
40774078
}
40784079

4080+
[Theory]
4081+
[InlineData(HttpProtocols.Http1AndHttp2AndHttp3)]
4082+
[InlineData(HttpProtocols.Http3)]
4083+
public async Task EnableAltSvc_Http3EndpointConfigured_AltSvcInResponseHeaders(HttpProtocols protocols)
4084+
{
4085+
await using (var server = new TestServer(
4086+
context => Task.CompletedTask,
4087+
new TestServiceContext(),
4088+
options =>
4089+
{
4090+
options.EnableAltSvc = true;
4091+
options.CodeBackedListenOptions.Add(new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)));
4092+
options.CodeBackedListenOptions.Add(new ListenOptions(new IPEndPoint(IPAddress.Loopback, 1)) { Protocols = protocols });
4093+
},
4094+
services => { }))
4095+
{
4096+
using (var connection = server.CreateConnection())
4097+
{
4098+
await connection.Send(
4099+
"GET / HTTP/1.1",
4100+
"Host:",
4101+
"",
4102+
"");
4103+
4104+
await connection.Receive(
4105+
$"HTTP/1.1 200 OK",
4106+
$"Date: {server.Context.DateHeaderValue}",
4107+
"Content-Length: 0",
4108+
@"Alt-Svc: h3-29="":1""; ma=84600",
4109+
"",
4110+
"");
4111+
}
4112+
}
4113+
}
4114+
40794115
private static async Task ResponseStatusCodeSetBeforeHttpContextDispose(
40804116
ITestSink testSink,
40814117
ILoggerFactory loggerFactory,

src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
using System.Threading.Tasks;
1212
using Microsoft.AspNetCore.Builder;
1313
using Microsoft.AspNetCore.Connections;
14+
using Microsoft.AspNetCore.Connections.Experimental;
1415
using Microsoft.AspNetCore.Hosting;
1516
using Microsoft.AspNetCore.Hosting.Server;
1617
using Microsoft.AspNetCore.Http;
18+
using Microsoft.AspNetCore.Http.Features;
1719
using Microsoft.AspNetCore.Server.Kestrel.Core;
1820
using Microsoft.AspNetCore.Testing;
1921
using Microsoft.Extensions.DependencyInjection;
@@ -89,7 +91,12 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action<Kestre
8991
{
9092
context.ServerOptions.ApplicationServices = sp;
9193
configureKestrel(context.ServerOptions);
92-
return new KestrelServerImpl(_transportFactory, context);
94+
return new KestrelServerImpl(
95+
new IConnectionListenerFactory[] { _transportFactory },
96+
// Mock multiplexed connection listner is added so Kestrel doesn't error
97+
// when a HTTP/3 endpoint is configured.
98+
new IMultiplexedConnectionListenerFactory[] { new MockMultiplexedConnectionListenerFactory() },
99+
context);
93100
});
94101
});
95102

@@ -133,5 +140,38 @@ public async ValueTask DisposeAsync()
133140
await ((IAsyncDisposable)_host).DisposeAsync().ConfigureAwait(false);
134141
_memoryPool.Dispose();
135142
}
143+
144+
private class MockMultiplexedConnectionListenerFactory : IMultiplexedConnectionListenerFactory
145+
{
146+
public ValueTask<IMultiplexedConnectionListener> BindAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default)
147+
{
148+
return ValueTask.FromResult<IMultiplexedConnectionListener>(new MockMultiplexedConnectionListener(endpoint));
149+
}
150+
}
151+
152+
private class MockMultiplexedConnectionListener : IMultiplexedConnectionListener
153+
{
154+
public MockMultiplexedConnectionListener(EndPoint endpoint)
155+
{
156+
EndPoint = endpoint;
157+
}
158+
159+
public EndPoint EndPoint { get; }
160+
161+
public ValueTask<MultiplexedConnectionContext> AcceptAsync(IFeatureCollection features = null, CancellationToken cancellationToken = default)
162+
{
163+
return ValueTask.FromResult<MultiplexedConnectionContext>(null);
164+
}
165+
166+
public ValueTask DisposeAsync()
167+
{
168+
return default;
169+
}
170+
171+
public ValueTask UnbindAsync(CancellationToken cancellationToken = default)
172+
{
173+
return default;
174+
}
175+
}
136176
}
137177
}

0 commit comments

Comments
 (0)