diff --git a/KestrelHttpServer.sln b/KestrelHttpServer.sln index 2b528d0f1b..d907c16aee 100644 --- a/KestrelHttpServer.sln +++ b/KestrelHttpServer.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26403.7 +VisualStudioVersion = 15.0.26510.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}" ProjectSection(SolutionItems) = preProject @@ -22,7 +22,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{0EF2AC test\shared\DummyApplication.cs = test\shared\DummyApplication.cs test\shared\HttpClientSlim.cs = test\shared\HttpClientSlim.cs test\shared\HttpParsingData.cs = test\shared\HttpParsingData.cs - test\shared\KestrelTestLoggerFactory.cs = test\shared\KestrelTestLoggerFactory.cs + test\shared\KestrelTestLoggerProvider.cs = test\shared\KestrelTestLoggerProvider.cs test\shared\LifetimeNotImplemented.cs = test\shared\LifetimeNotImplemented.cs test\shared\MockConnectionInformation.cs = test\shared\MockConnectionInformation.cs test\shared\MockFrameControl.cs = test\shared\MockFrameControl.cs @@ -71,6 +71,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Tests", "test\Microsoft.AspNetCore.Server.Kestrel.Tests\Microsoft.AspNetCore.Server.Kestrel.Tests.csproj", "{4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{FD3692F8-F5C7-49A5-8D69-29F8A87A9DB7}" + ProjectSection(SolutionItems) = preProject + build\common.props = build\common.props + build\dependencies.props = build\dependencies.props + build\Key.snk = build\Key.snk + build\repo.props = build\repo.props + build\repo.targets = build\repo.targets + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -270,5 +279,6 @@ Global {2E9CB89D-EC8F-4DD9-A72B-08D5BABF752D} = {2D5D5227-4DBD-499A-96B1-76A36B03B750} {D95A7EC3-48AC-4D03-B2E2-0DA3E13BD3A4} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} {4F1C30F8-CCAA-48D7-9DF6-2A84021F5BCC} = {D3273454-EA07-41D2-BF0B-FCC3675C2483} + {FD3692F8-F5C7-49A5-8D69-29F8A87A9DB7} = {7972A5D6-3385-4127-9277-428506DD44FF} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/ConfigureDefaultKestrelServerOptions.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/ConfigureDefaultKestrelServerOptions.cs new file mode 100644 index 0000000000..02e1f0d561 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/ConfigureDefaultKestrelServerOptions.cs @@ -0,0 +1,116 @@ +// 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.Linq; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Certificates.Configuration; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options.Infrastructure; + +namespace Microsoft.AspNetCore.Server.Kestrel.Internal +{ + public class ConfigureDefaultKestrelServerOptions : ConfigureDefaultOptions + { + private const string DefaultCertificateSubjectName = "CN=localhost"; + private const string DevelopmentSSLCertificateName = "localhost"; + + private readonly IServiceProvider _services; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IConfiguration _configurationRoot; + private readonly ILoggerFactory _loggerFactory; + + public ConfigureDefaultKestrelServerOptions( + IServiceProvider services, + IHostingEnvironment hostingEnvironment, + IConfiguration configurationRoot, + ILoggerFactory loggerFactory) + { + _services = services; + _hostingEnvironment = hostingEnvironment; + _configurationRoot = configurationRoot; + _loggerFactory = loggerFactory; + } + + public override void Configure(string name, KestrelServerOptions options) + { + // Don't assume KestrelServerOptionsSetup has already set the services. Needed for UseHttps. + options.ApplicationServices = _services; + BindConfiguration(options); + } + + private void BindConfiguration(KestrelServerOptions options) + { + var certificateLoader = new CertificateLoader(_configurationRoot.GetSection("Certificates"), _loggerFactory, _hostingEnvironment.EnvironmentName); + + foreach (var endPoint in _configurationRoot.GetSection("Microsoft:AspNetCore:Server:Kestrel:EndPoints").GetChildren()) + { + BindEndPoint(options, endPoint, certificateLoader); + } + } + + private void BindEndPoint( + KestrelServerOptions options, + IConfigurationSection endPoint, + CertificateLoader certificateLoader) + { + var configAddress = endPoint.GetValue("Address"); + var configPort = endPoint.GetValue("Port"); + + if (!IPAddress.TryParse(configAddress, out var address)) + { + throw new InvalidOperationException(KestrelStrings.FormatInvalidIp(configAddress)); + } + + if (!int.TryParse(configPort, out var port)) + { + throw new InvalidOperationException(KestrelStrings.FormatInvalidPort(configPort)); + } + + options.Listen(address, port, listenOptions => + { + var certificateConfig = endPoint.GetSection("Certificate"); + X509Certificate2 certificate = null; + if (certificateConfig.Exists()) + { + try + { + try + { + certificate = certificateLoader.Load(certificateConfig).FirstOrDefault(); + } + catch (KeyNotFoundException) when (certificateConfig.Value.Equals(DevelopmentSSLCertificateName, StringComparison.Ordinal) && _hostingEnvironment.IsDevelopment()) + { + var storeLoader = new CertificateStoreLoader(); + certificate = storeLoader.Load(DefaultCertificateSubjectName, "My", StoreLocation.CurrentUser, validOnly: false) ?? + storeLoader.Load(DefaultCertificateSubjectName, "My", StoreLocation.LocalMachine, validOnly: false); + + if (certificate == null) + { + var logger = _loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.KestrelServerConfigureOptions"); + logger.LogError(KestrelStrings.NoCertFoundLog); + } + } + + if (certificate == null) + { + throw new InvalidOperationException(KestrelStrings.FormatNoCertForEndpoint(endPoint.Key)); + } + } + catch (Exception ex) + { + throw new InvalidOperationException(KestrelStrings.UnableToConfigureHttps, ex); + } + + listenOptions.UseHttps(certificate); + } + }); + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelStrings.resx b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelStrings.resx new file mode 100644 index 0000000000..bf5c93410c --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelStrings.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Invalid IP address in configuration: {configAddress}. + + + Invalid port in configuration: {configPort}. + + + No certificate found for endpoint {endPoint}. + + + Unable to configure HTTPS endpoint. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054. + + + No HTTPS certificate was found for development. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054. + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj b/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj index 616830814d..fbaa34e0b7 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Microsoft.AspNetCore.Server.Kestrel.csproj @@ -4,7 +4,7 @@ ASP.NET Core Kestrel cross-platform web server. - netstandard2.0 + netstandard2.0;netcoreapp2.0 true aspnetcore;kestrel CS1591;$(NoWarn) @@ -12,11 +12,14 @@ + + + diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Properties/KestrelStrings.Designer.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Properties/KestrelStrings.Designer.cs new file mode 100644 index 0000000000..ee0b31857d --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Properties/KestrelStrings.Designer.cs @@ -0,0 +1,100 @@ +// +namespace Microsoft.AspNetCore.Server.Kestrel +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class KestrelStrings + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Server.Kestrel.KestrelStrings", typeof(KestrelStrings).GetTypeInfo().Assembly); + + /// + /// Invalid IP address in configuration: {configAddress}. + /// + internal static string InvalidIp + { + get => GetString("InvalidIp"); + } + + /// + /// Invalid IP address in configuration: {configAddress}. + /// + internal static string FormatInvalidIp(object configAddress) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidIp", "configAddress"), configAddress); + + /// + /// Invalid port in configuration: {configPort}. + /// + internal static string InvalidPort + { + get => GetString("InvalidPort"); + } + + /// + /// Invalid port in configuration: {configPort}. + /// + internal static string FormatInvalidPort(object configPort) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidPort", "configPort"), configPort); + + /// + /// No certificate found for endpoint {endPoint}. + /// + internal static string NoCertForEndpoint + { + get => GetString("NoCertForEndpoint"); + } + + /// + /// No certificate found for endpoint {endPoint}. + /// + internal static string FormatNoCertForEndpoint(object endPoint) + => string.Format(CultureInfo.CurrentCulture, GetString("NoCertForEndpoint", "endPoint"), endPoint); + + /// + /// Unable to configure HTTPS endpoint. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054. + /// + internal static string UnableToConfigureHttps + { + get => GetString("UnableToConfigureHttps"); + } + + /// + /// Unable to configure HTTPS endpoint. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054. + /// + internal static string FormatUnableToConfigureHttps() + => GetString("UnableToConfigureHttps"); + + /// + /// No HTTPS certificate was found for development. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054. + /// + internal static string NoCertFoundLog + { + get => GetString("NoCertFoundLog"); + } + + /// + /// No HTTPS certificate was found for development. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054. + /// + internal static string FormatNoCertFoundLog() + => GetString("NoCertFoundLog"); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/WebHostBuilderKestrelExtensions.cs b/src/Microsoft.AspNetCore.Server.Kestrel/WebHostBuilderKestrelExtensions.cs index 3d3dad73cb..0823e62db1 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/WebHostBuilderKestrelExtensions.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/WebHostBuilderKestrelExtensions.cs @@ -3,10 +3,12 @@ using System; using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Server.Kestrel.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options.Infrastructure; namespace Microsoft.AspNetCore.Hosting { @@ -28,6 +30,9 @@ namespace Microsoft.AspNetCore.Hosting return hostBuilder.ConfigureServices(services => { services.AddTransient, KestrelServerOptionsSetup>(); + // https://github.com/aspnet/DependencyInjection/issues/500 + services.AddTransient, ConfigureDefaults>(); + services.AddTransient, ConfigureDefaultKestrelServerOptions>(); services.AddSingleton(); }); } diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/KestrelServerTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/KestrelServerTests.cs index 75ab8a3dbb..a61feba7bc 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/KestrelServerTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/KestrelServerTests.cs @@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests private static KestrelServer CreateServer(KestrelServerOptions options, ILogger testLogger) { - return new KestrelServer(Options.Create(options), new MockTransportFactory(), new KestrelTestLoggerFactory(testLogger)); + return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new [] { new KestrelTestLoggerProvider(testLogger)} )); } private static void StartDummyApplication(IServer server) diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj index fb273003e7..fba2976f4e 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj @@ -26,6 +26,7 @@ + diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs index 38ea0f1933..35a472a0db 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs @@ -19,6 +19,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Xunit; @@ -31,9 +32,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { private const int MaxRetries = 10; - private readonly ILoggerFactory _loggerFactory; + private readonly Action _configureLoggingDelegate; - public AddressRegistrationTests(ITestOutputHelper output) => _loggerFactory = new LoggerFactory().AddXunit(output); + public AddressRegistrationTests(ITestOutputHelper output) => _configureLoggingDelegate = builder => builder.AddXunit(output); [ConditionalFact] [NetworkIsReachable] @@ -161,7 +162,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { var hostBuilder = new WebHostBuilder() .UseKestrel() - .UseLoggerFactory(_loggerFactory) + .ConfigureLogging(_configureLoggingDelegate) .UseUrls(addressInput) .Configure(ConfigureEchoAddress); @@ -220,7 +221,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests private async Task RegisterIPEndPoint_Success(IPEndPoint endPoint, string testUrl, int testPort = 0) { var hostBuilder = new WebHostBuilder() - .UseLoggerFactory(_loggerFactory) + .ConfigureLogging(_configureLoggingDelegate) .UseKestrel(options => { options.Listen(endPoint, listenOptions => @@ -301,9 +302,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var testLogger = new TestApplicationErrorLogger(); var hostBuilder = new WebHostBuilder() - .UseLoggerFactory(_loggerFactory) + .ConfigureLogging(_configureLoggingDelegate) .UseKestrel() - .UseLoggerFactory(_ => new KestrelTestLoggerFactory(testLogger)) + .ConfigureLogging(builder => builder + .AddProvider(new KestrelTestLoggerProvider(testLogger)) + .SetMinimumLevel(LogLevel.Debug)) .Configure(ConfigureEchoAddress); using (var host = hostBuilder.Build()) @@ -331,7 +334,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var port = ((IPEndPoint)socket.LocalEndPoint).Port; var hostBuilder = new WebHostBuilder() - .UseLoggerFactory(_loggerFactory) + .ConfigureLogging(_configureLoggingDelegate) .UseKestrel() .UseUrls($"http://127.0.0.1:{port}") .Configure(ConfigureEchoAddress); @@ -354,7 +357,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var port = ((IPEndPoint)socket.LocalEndPoint).Port; var hostBuilder = new WebHostBuilder() - .UseLoggerFactory(_loggerFactory) + .ConfigureLogging(_configureLoggingDelegate) .UseKestrel() .UseUrls($"http://[::1]:{port}") .Configure(ConfigureEchoAddress); @@ -382,7 +385,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests }) .UseUrls(useUrlsAddress) .PreferHostingUrls(true) - .UseLoggerFactory(_ => new KestrelTestLoggerFactory(testLogger)) + .ConfigureLogging(builder => builder.AddProvider(new KestrelTestLoggerProvider(testLogger))) .Configure(ConfigureEchoAddress); using (var host = hostBuilder.Build()) @@ -412,7 +415,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var useUrlsAddress = $"http://127.0.0.1:0"; var testLogger = new TestApplicationErrorLogger(); var hostBuilder = new WebHostBuilder() - .UseLoggerFactory(_loggerFactory) + .ConfigureLogging(_configureLoggingDelegate) .UseKestrel(options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => @@ -422,7 +425,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests }) .UseUrls($"http://127.0.0.1:0") .PreferHostingUrls(false) - .UseLoggerFactory(_ => new KestrelTestLoggerFactory(testLogger)) + .ConfigureLogging(builder => builder.AddProvider(new KestrelTestLoggerProvider(testLogger))) .Configure(ConfigureEchoAddress); using (var host = hostBuilder.Build()) @@ -450,7 +453,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests public async Task DoesNotOverrideDirectConfigurationWithIServerAddressesFeature_IfAddressesEmpty() { var hostBuilder = new WebHostBuilder() - .UseLoggerFactory(_loggerFactory) + .ConfigureLogging(_configureLoggingDelegate) .UseKestrel(options => { options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => @@ -528,7 +531,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var port = ((IPEndPoint)socket.LocalEndPoint).Port; var hostBuilder = new WebHostBuilder() - .UseLoggerFactory(_loggerFactory) + .ConfigureLogging(_configureLoggingDelegate) .UseKestrel() .UseUrls($"http://localhost:{port}") .Configure(ConfigureEchoAddress); diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ConfigurationTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ConfigurationTests.cs new file mode 100644 index 0000000000..c7581dfd15 --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ConfigurationTests.cs @@ -0,0 +1,153 @@ +// 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.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests +{ + public class ConfigurationTests + { + private void ConfigureEchoAddress(IApplicationBuilder app) + { + app.Run(context => + { + return context.Response.WriteAsync(context.Request.GetDisplayUrl()); + }); + } + + [Fact] + public void BindsKestrelToInvalidIp_FailsToStart() + { + var hostBuilder = new WebHostBuilder() + .UseKestrel() + .UseConfiguration(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary() + { + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Address", "ABCDEFGH" }, + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Port", "0" } + }).Build()) + .ConfigureServices(services => + { + // Microsoft.AspNetCore.dll does this + services.AddTransient(typeof(IConfigureOptions<>), typeof(ConfigureDefaults<>)); + }) + .Configure(ConfigureEchoAddress); + + Assert.Throws(() => hostBuilder.Build()); + } + + [Theory] + [InlineData("127.0.0.1", "127.0.0.1")] + [InlineData("::1", "[::1]")] + public async Task BindsKestrelHttpEndPointFromConfiguration(string endPointAddress, string requestAddress) + { + var hostBuilder = new WebHostBuilder() + .UseKestrel() + .UseConfiguration(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary() + { + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Address", $"{endPointAddress}" }, + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Port", "0" } + }).Build()) + .ConfigureServices(services => + { + // Microsoft.AspNetCore.dll does this + services.AddTransient(typeof(IConfigureOptions<>), typeof(ConfigureDefaults<>)); + }) + .Configure(ConfigureEchoAddress); + + using (var webHost = hostBuilder.Start()) + { + var port = GetWebHostPort(webHost); + + Assert.NotEqual(5000, port); // Default port + + Assert.NotEqual(0, port); + + using (var client = new HttpClient()) + { + var response = await client.GetStringAsync($"http://{requestAddress}:{port}"); + Assert.Equal($"http://{requestAddress}:{port}/", response); + } + } + } + + [Fact] + public async Task BindsKestrelHttpsEndPointFromConfiguration_ReferencedCertificateFile() + { + var hostBuilder = new WebHostBuilder() + .UseKestrel() + .UseConfiguration(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary() + { + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Address", "127.0.0.1" }, + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Port", "0" }, + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Certificate", "TestCert" }, + { "Certificates:TestCert:Source", "File" }, + { "Certificates:TestCert:Path", "testCert.pfx" }, + { "Certificates:TestCert:Password", "testPassword" } + }).Build()) + .ConfigureServices(services => + { + // Microsoft.AspNetCore.dll does this + services.AddTransient(typeof(IConfigureOptions<>), typeof(ConfigureDefaults<>)); + }) + .Configure(ConfigureEchoAddress); + + using (var webHost = hostBuilder.Start()) + { + var port = GetWebHostPort(webHost); + + var response = await HttpClientSlim.GetStringAsync($"https://127.0.0.1:{port}", validateCertificate: false); + Assert.Equal($"https://127.0.0.1:{port}/", response); + } + } + + [Fact] + public async Task BindsKestrelHttpsEndPointFromConfiguration_InlineCertificateFile() + { + var hostBuilder = new WebHostBuilder() + .UseKestrel() + .UseConfiguration(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary() + { + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Address", "127.0.0.1" }, + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Port", "0" }, + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Certificate:Source", "File" }, + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Certificate:Path", "testCert.pfx" }, + { "Microsoft:AspNetCore:Server:Kestrel:Endpoints:0:Certificate:Password", "testPassword" } + }).Build()) + .ConfigureServices(services => + { + // Microsoft.AspNetCore.dll does this + services.AddTransient(typeof(IConfigureOptions<>), typeof(ConfigureDefaults<>)); + }) + .Configure(ConfigureEchoAddress); + + using (var webHost = hostBuilder.Start()) + { + var port = GetWebHostPort(webHost); + + var response = await HttpClientSlim.GetStringAsync($"https://127.0.0.1:{port}", validateCertificate: false); + Assert.Equal($"https://127.0.0.1:{port}/", response); + } + } + + private static int GetWebHostPort(IWebHost webHost) + => webHost.ServerFeatures.Get().Addresses + .Select(serverAddress => new Uri(serverAddress).Port) + .Single(); + } +} diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/FrameConnectionManagerTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/FrameConnectionManagerTests.cs index ac71db8730..0e71450a5e 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/FrameConnectionManagerTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/FrameConnectionManagerTests.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging; using Moq; using Xunit; @@ -42,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var tcs = new TaskCompletionSource(); return tcs.Task; }, - new TestServiceContext(new KestrelTestLoggerFactory(), mockTrace.Object))) + new TestServiceContext(new LoggerFactory(), mockTrace.Object))) { using (var connection = server.CreateConnection()) { diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/HttpsTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/HttpsTests.cs index 01987f3464..fcb67652cc 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/HttpsTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/HttpsTests.cs @@ -11,6 +11,7 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Https; @@ -26,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [Fact] public async Task EmptyRequestLoggedAsInformation() { - var loggerFactory = new HandshakeErrorLoggerFactory(); + var loggerProvider = new HandshakeErrorLoggerProvider(); var hostBuilder = new WebHostBuilder() .UseKestrel(options => @@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); }); }) - .UseLoggerFactory(loggerFactory) + .ConfigureLogging(builder => builder.AddProvider(loggerProvider)) .Configure(app => { }); using (var host = hostBuilder.Build()) @@ -48,19 +49,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests // Close socket immediately } - await loggerFactory.FilterLogger.LogTcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10)); + await loggerProvider.FilterLogger.LogTcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10)); } - Assert.Equal(1, loggerFactory.FilterLogger.LastEventId.Id); - Assert.Equal(LogLevel.Information, loggerFactory.FilterLogger.LastLogLevel); - Assert.True(loggerFactory.ErrorLogger.TotalErrorsLogged == 0, - userMessage: string.Join(Environment.NewLine, loggerFactory.ErrorLogger.ErrorMessages)); + Assert.Equal(1, loggerProvider.FilterLogger.LastEventId.Id); + Assert.Equal(LogLevel.Information, loggerProvider.FilterLogger.LastLogLevel); + Assert.True(loggerProvider.ErrorLogger.TotalErrorsLogged == 0, + userMessage: string.Join(Environment.NewLine, loggerProvider.ErrorLogger.ErrorMessages)); } [Fact] public async Task ClientHandshakeFailureLoggedAsInformation() { - var loggerFactory = new HandshakeErrorLoggerFactory(); + var loggerProvider = new HandshakeErrorLoggerProvider(); var hostBuilder = new WebHostBuilder() .UseKestrel(options => @@ -70,7 +71,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); }); }) - .UseLoggerFactory(loggerFactory) + .ConfigureLogging(builder => builder.AddProvider(loggerProvider)) .Configure(app => { }); using (var host = hostBuilder.Build()) @@ -84,20 +85,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests await stream.WriteAsync(new byte[10], 0, 10); } - await loggerFactory.FilterLogger.LogTcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10)); + await loggerProvider.FilterLogger.LogTcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10)); } - Assert.Equal(1, loggerFactory.FilterLogger.LastEventId.Id); - Assert.Equal(LogLevel.Information, loggerFactory.FilterLogger.LastLogLevel); - Assert.True(loggerFactory.ErrorLogger.TotalErrorsLogged == 0, - userMessage: string.Join(Environment.NewLine, loggerFactory.ErrorLogger.ErrorMessages)); + Assert.Equal(1, loggerProvider.FilterLogger.LastEventId.Id); + Assert.Equal(LogLevel.Information, loggerProvider.FilterLogger.LastLogLevel); + Assert.True(loggerProvider.ErrorLogger.TotalErrorsLogged == 0, + userMessage: string.Join(Environment.NewLine, loggerProvider.ErrorLogger.ErrorMessages)); } // Regression test for https://github.com/aspnet/KestrelHttpServer/issues/1103#issuecomment-246971172 [Fact] public async Task DoesNotThrowObjectDisposedExceptionOnConnectionAbort() { - var loggerFactory = new HandshakeErrorLoggerFactory(); + var loggerProvider = new HandshakeErrorLoggerProvider(); var hostBuilder = new WebHostBuilder() .UseKestrel(options => { @@ -106,7 +107,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); }); }) - .UseLoggerFactory(loggerFactory) + .ConfigureLogging(builder => builder.AddProvider(loggerProvider)) .Configure(app => app.Run(async httpContext => { var ct = httpContext.RequestAborted; @@ -142,14 +143,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } - Assert.False(loggerFactory.ErrorLogger.ObjectDisposedExceptionLogged); + Assert.False(loggerProvider.ErrorLogger.ObjectDisposedExceptionLogged); } [Fact] public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnectionIsAborted() { var tcs = new TaskCompletionSource(); - var loggerFactory = new HandshakeErrorLoggerFactory(); + var loggerProvider = new HandshakeErrorLoggerProvider(); var hostBuilder = new WebHostBuilder() .UseKestrel(options => { @@ -158,7 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); }); }) - .UseLoggerFactory(loggerFactory) + .ConfigureLogging(builder => builder.AddProvider(loggerProvider)) .Configure(app => app.Run(async httpContext => { httpContext.Abort(); @@ -198,7 +199,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [Fact] public async Task DoesNotThrowObjectDisposedExceptionOnEmptyConnection() { - var loggerFactory = new HandshakeErrorLoggerFactory(); + var loggerProvider = new HandshakeErrorLoggerProvider(); var hostBuilder = new WebHostBuilder() .UseKestrel(options => { @@ -207,7 +208,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); }); }) - .UseLoggerFactory(loggerFactory) + .ConfigureLogging(builder => builder.AddProvider(loggerProvider)) .Configure(app => app.Run(httpContext => Task.CompletedTask)); using (var host = hostBuilder.Build()) @@ -224,14 +225,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } - Assert.False(loggerFactory.ErrorLogger.ObjectDisposedExceptionLogged); + Assert.False(loggerProvider.ErrorLogger.ObjectDisposedExceptionLogged); } // Regression test for https://github.com/aspnet/KestrelHttpServer/pull/1197 [Fact] public void ConnectionFilterDoesNotLeakBlock() { - var loggerFactory = new HandshakeErrorLoggerFactory(); + var loggerProvider = new HandshakeErrorLoggerProvider(); var hostBuilder = new WebHostBuilder() .UseKestrel(options => @@ -241,7 +242,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword"); }); }) - .UseLoggerFactory(loggerFactory) + .ConfigureLogging(builder => builder.AddProvider(loggerProvider)) .Configure(app => { }); using (var host = hostBuilder.Build()) @@ -258,7 +259,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } - private class HandshakeErrorLoggerFactory : ILoggerFactory + private class HandshakeErrorLoggerProvider : ILoggerProvider { public HttpsConnectionFilterLogger FilterLogger { get; } = new HttpsConnectionFilterLogger(); public ApplicationErrorLogger ErrorLogger { get; } = new ApplicationErrorLogger(); @@ -275,11 +276,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } - public void AddProvider(ILoggerProvider provider) - { - throw new NotImplementedException(); - } - public void Dispose() { } diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.csproj b/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.csproj index 9fd8124c11..d6e8bf6ba9 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.csproj +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.csproj @@ -27,6 +27,7 @@ + diff --git a/test/shared/KestrelTestLoggerFactory.cs b/test/shared/KestrelTestLoggerProvider.cs similarity index 69% rename from test/shared/KestrelTestLoggerFactory.cs rename to test/shared/KestrelTestLoggerProvider.cs index 80a94b0d1a..45462791d1 100644 --- a/test/shared/KestrelTestLoggerFactory.cs +++ b/test/shared/KestrelTestLoggerProvider.cs @@ -6,16 +6,16 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Testing { - public class KestrelTestLoggerFactory : ILoggerFactory + public class KestrelTestLoggerProvider : ILoggerProvider { private readonly ILogger _testLogger; - public KestrelTestLoggerFactory() + public KestrelTestLoggerProvider() : this(new TestApplicationErrorLogger()) { } - public KestrelTestLoggerFactory(ILogger testLogger) + public KestrelTestLoggerProvider(ILogger testLogger) { _testLogger = testLogger; } @@ -25,11 +25,6 @@ namespace Microsoft.AspNetCore.Testing return _testLogger; } - public void AddProvider(ILoggerProvider provider) - { - throw new NotImplementedException(); - } - public void Dispose() { throw new NotImplementedException(); diff --git a/test/shared/TestServiceContext.cs b/test/shared/TestServiceContext.cs index 9d59a66d44..5f4b572047 100644 --- a/test/shared/TestServiceContext.cs +++ b/test/shared/TestServiceContext.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Testing public class TestServiceContext : ServiceContext { public TestServiceContext() - : this(new KestrelTestLoggerFactory()) + : this(new LoggerFactory(new[] { new KestrelTestLoggerProvider() })) { }