diff --git a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs index de1eab76de9e4a0736457d52ab60d0fa0276920c..140d978f6d1ef1ffa8063734996c6814fe51be46 100644 --- a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs @@ -178,7 +178,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core // Http/3 requires TLS. Note we only let it fall back to HTTP/1, not HTTP/2 else if (hasHttp3) { - throw new InvalidOperationException("HTTP/3 requires https."); + throw new InvalidOperationException("HTTP/3 requires HTTPS."); } } @@ -188,6 +188,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core throw new InvalidOperationException("This platform doesn't support QUIC or HTTP/3."); } + // Disable adding alt-svc header if endpoint has configured not to or there is no + // multiplexed transport factory, which happens if QUIC isn't supported. + var addAltSvcHeader = !options.DisableAltSvcHeader && _multiplexedTransportFactory != null; + // Add the HTTP middleware as the terminal connection middleware if (hasHttp1 || hasHttp2 || options.Protocols == HttpProtocols.None) // TODO a test fails because it doesn't throw an exception in the right place @@ -198,7 +202,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core throw new InvalidOperationException($"Cannot start HTTP/1.x or HTTP/2 server if no {nameof(IConnectionListenerFactory)} is registered."); } - options.UseHttpServer(ServiceContext, application, options.Protocols, !options.DisableAltSvcHeader); + options.UseHttpServer(ServiceContext, application, options.Protocols, addAltSvcHeader); var connectionDelegate = options.Build(); // Add the connection limit middleware @@ -209,7 +213,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core if (hasHttp3 && _multiplexedTransportFactory is not null) { - options.UseHttp3Server(ServiceContext, application, options.Protocols, !options.DisableAltSvcHeader); + options.UseHttp3Server(ServiceContext, application, options.Protocols, addAltSvcHeader); var multiplexedConnectionDelegate = ((IMultiplexedConnectionBuilder)options).Build(); // Add the connection limit middleware diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs index 9f782cce377e0c62f5c8b76cb1acff97a1d129d2..323b864bb9d930483022e1228c4acf5ad67292d9 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs @@ -553,7 +553,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests }); Assert.False(bindCalled); - Assert.Equal("HTTP/3 requires https.", ex.InnerException.InnerException.Message); + Assert.Equal("HTTP/3 requires HTTPS.", ex.InnerException.InnerException.Message); } [Fact] @@ -589,7 +589,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests }); Assert.False(bindCalled); - Assert.Equal("HTTP/3 requires https.", ex.InnerException.InnerException.Message); + Assert.Equal("HTTP/3 requires HTTPS.", ex.InnerException.InnerException.Message); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs index 187c32053f6b7855d7c84172eab4dfb71b1b97fa..bb40ed8fd2ae86131bffb1fcce9c0484225c1ebe 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs @@ -4128,7 +4128,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests IsTls = true }); }, - services => { })) + services => + { + services.AddSingleton<IMultiplexedConnectionListenerFactory>(new MockMultiplexedConnectionListenerFactory()); + })) { using (var connection = server.CreateConnection()) { @@ -4148,6 +4151,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } } + [Fact] + public async Task AltSvc_Http1And2And3EndpointConfigured_NoMultiplexedFactory_NoAltSvcInResponseHeaders() + { + await using (var server = new TestServer( + httpContext => Task.CompletedTask, + new TestServiceContext(LoggerFactory), + options => + { + options.CodeBackedListenOptions.Add(new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + { + Protocols = HttpProtocols.Http1AndHttp2AndHttp3, + IsTls = true + }); + }, + services => {})) + { + using (var connection = server.CreateConnection()) + { + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + await connection.Receive( + "HTTP/1.1 200 OK", + "Content-Length: 0", + $"Date: {server.Context.DateHeaderValue}", + "", + ""); + } + } + } + [Fact] public async Task AltSvc_Http1_NoAltSvcInResponseHeaders() { @@ -4191,7 +4227,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests IsTls = true }); }, - services => { })) + services => + { + services.AddSingleton<IMultiplexedConnectionListenerFactory>(new MockMultiplexedConnectionListenerFactory()); + })) { using (var connection = server.CreateConnection()) { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs index fa1db23639f9a13c197abae2626dce844ea8eefa..ee42118af0192cc7754313be72f8e0d7f379e595 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs @@ -84,14 +84,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTrans { configureServices(services); - // Ensure there is at least one multiplexed connection lister factory if none was added to services. - if (!services.Any(d => d.ServiceType == typeof(IMultiplexedConnectionListenerFactory))) - { - // Mock multiplexed connection listner is added so Kestrel doesn't error - // when a HTTP/3 endpoint is configured. - services.AddSingleton<IMultiplexedConnectionListenerFactory>(new MockMultiplexedConnectionListenerFactory()); - } - services.AddSingleton<IStartup>(this); services.AddSingleton(context.LoggerFactory);