Map ListenOptions.Protocols from IConfiguration #2903
This commit is contained in:
parent
de5ccb5c78
commit
f38f60f8ce
|
|
@ -9,9 +9,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
internal class ConfigurationReader
|
||||
{
|
||||
private const string ProtocolsKey = "Protocols";
|
||||
private const string CertificatesKey = "Certificates";
|
||||
private const string CertificateKey = "Certificate";
|
||||
private const string EndpointDefaultsKey = "EndpointDefaults";
|
||||
private const string EndpointsKey = "Endpoints";
|
||||
private const string UrlKey = "Url";
|
||||
|
||||
private IConfiguration _configuration;
|
||||
private IDictionary<string, CertificateConfig> _certificates;
|
||||
private IList<EndpointConfig> _endpoints;
|
||||
private EndpointDefaults _endpointDefaults;
|
||||
|
||||
public ConfigurationReader(IConfiguration configuration)
|
||||
{
|
||||
|
|
@ -31,6 +39,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
}
|
||||
}
|
||||
|
||||
public EndpointDefaults EndpointDefaults
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_endpointDefaults == null)
|
||||
{
|
||||
ReadEndpointDefaults();
|
||||
}
|
||||
|
||||
return _endpointDefaults;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<EndpointConfig> Endpoints
|
||||
{
|
||||
get
|
||||
|
|
@ -48,29 +69,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
_certificates = new Dictionary<string, CertificateConfig>(0);
|
||||
|
||||
var certificatesConfig = _configuration.GetSection("Certificates").GetChildren();
|
||||
var certificatesConfig = _configuration.GetSection(CertificatesKey).GetChildren();
|
||||
foreach (var certificateConfig in certificatesConfig)
|
||||
{
|
||||
_certificates.Add(certificateConfig.Key, new CertificateConfig(certificateConfig));
|
||||
}
|
||||
}
|
||||
|
||||
// "EndpointDefaults": {
|
||||
// "Protocols": "Http1AndHttp2",
|
||||
// }
|
||||
private void ReadEndpointDefaults()
|
||||
{
|
||||
var configSection = _configuration.GetSection(EndpointDefaultsKey);
|
||||
_endpointDefaults = new EndpointDefaults()
|
||||
{
|
||||
Protocols = ParseProtocols(configSection[ProtocolsKey])
|
||||
};
|
||||
}
|
||||
|
||||
private void ReadEndpoints()
|
||||
{
|
||||
_endpoints = new List<EndpointConfig>();
|
||||
|
||||
var endpointsConfig = _configuration.GetSection("Endpoints").GetChildren();
|
||||
var endpointsConfig = _configuration.GetSection(EndpointsKey).GetChildren();
|
||||
foreach (var endpointConfig in endpointsConfig)
|
||||
{
|
||||
// "EndpointName": {
|
||||
// "Url": "https://*:5463",
|
||||
// "Certificate": {
|
||||
// "Path": "testCert.pfx",
|
||||
// "Password": "testPassword"
|
||||
// }
|
||||
// "Url": "https://*:5463",
|
||||
// "Protocols": "Http1AndHttp2",
|
||||
// "Certificate": {
|
||||
// "Path": "testCert.pfx",
|
||||
// "Password": "testPassword"
|
||||
// }
|
||||
// }
|
||||
|
||||
var url = endpointConfig["Url"];
|
||||
|
||||
var url = endpointConfig[UrlKey];
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
throw new InvalidOperationException(CoreStrings.FormatEndpointMissingUrl(endpointConfig.Key));
|
||||
|
|
@ -80,16 +114,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
Name = endpointConfig.Key,
|
||||
Url = url,
|
||||
Protocols = ParseProtocols(endpointConfig[ProtocolsKey]),
|
||||
ConfigSection = endpointConfig,
|
||||
Certificate = new CertificateConfig(endpointConfig.GetSection("Certificate")),
|
||||
Certificate = new CertificateConfig(endpointConfig.GetSection(CertificateKey)),
|
||||
};
|
||||
_endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpProtocols? ParseProtocols(string protocols)
|
||||
{
|
||||
if (Enum.TryParse<HttpProtocols>(protocols, ignoreCase: true, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// "EndpointDefaults": {
|
||||
// "Protocols": "Http1AndHttp2",
|
||||
// }
|
||||
internal class EndpointDefaults
|
||||
{
|
||||
public HttpProtocols? Protocols { get; set; }
|
||||
public IConfigurationSection ConfigSection { get; set; }
|
||||
}
|
||||
|
||||
// "EndpointName": {
|
||||
// "Url": "https://*:5463",
|
||||
// "Protocols": "Http1AndHttp2",
|
||||
// "Certificate": {
|
||||
// "Path": "testCert.pfx",
|
||||
// "Password": "testPassword"
|
||||
|
|
@ -99,6 +154,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
public string Name { get; set; }
|
||||
public string Url { get; set; }
|
||||
public HttpProtocols? Protocols { get; set; }
|
||||
public IConfigurationSection ConfigSection { get; set; }
|
||||
public CertificateConfig Certificate { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
{
|
||||
public class KestrelConfigurationLoader
|
||||
{
|
||||
private bool _loaded = false;
|
||||
|
||||
internal KestrelConfigurationLoader(KestrelServerOptions options, IConfiguration configuration)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
ConfigurationReader = new ConfigurationReader(Configuration);
|
||||
}
|
||||
|
||||
public KestrelServerOptions Options { get; }
|
||||
public IConfiguration Configuration { get; }
|
||||
internal ConfigurationReader ConfigurationReader { get; }
|
||||
private IDictionary<string, Action<EndpointConfiguration>> EndpointConfigurations { get; }
|
||||
= new Dictionary<string, Action<EndpointConfiguration>>(0, StringComparer.OrdinalIgnoreCase);
|
||||
// Actions that will be delayed until Load so that they aren't applied if the configuration loader is replaced.
|
||||
|
|
@ -197,24 +201,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
return this;
|
||||
}
|
||||
|
||||
// Called from ApplyEndpointDefaults so it applies to even explicit Listen endpoints.
|
||||
// Does not require a call to Load.
|
||||
internal void ApplyConfigurationDefaults(ListenOptions listenOptions)
|
||||
{
|
||||
var defaults = ConfigurationReader.EndpointDefaults;
|
||||
|
||||
if (defaults.Protocols.HasValue)
|
||||
{
|
||||
listenOptions.Protocols = defaults.Protocols.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
if (Options.ConfigurationLoader == null)
|
||||
if (_loaded)
|
||||
{
|
||||
// The loader has already been run.
|
||||
return;
|
||||
}
|
||||
Options.ConfigurationLoader = null;
|
||||
_loaded = true;
|
||||
|
||||
var configReader = new ConfigurationReader(Configuration);
|
||||
LoadDefaultCert(ConfigurationReader);
|
||||
|
||||
LoadDefaultCert(configReader);
|
||||
|
||||
foreach (var endpoint in configReader.Endpoints)
|
||||
foreach (var endpoint in ConfigurationReader.Endpoints)
|
||||
{
|
||||
var listenOptions = AddressBinder.ParseAddress(endpoint.Url, out var https);
|
||||
Options.ApplyEndpointDefaults(listenOptions);
|
||||
|
||||
if (endpoint.Protocols.HasValue)
|
||||
{
|
||||
listenOptions.Protocols = endpoint.Protocols.Value;
|
||||
}
|
||||
|
||||
// Compare to UseHttps(httpsOptions => { })
|
||||
var httpsOptions = new HttpsConnectionAdapterOptions();
|
||||
if (https)
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
internal void ApplyEndpointDefaults(ListenOptions listenOptions)
|
||||
{
|
||||
listenOptions.KestrelServerOptions = this;
|
||||
ConfigurationLoader?.ApplyConfigurationDefaults(listenOptions);
|
||||
EndpointDefaults(listenOptions);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,13 +81,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
|
|||
|
||||
Assert.Single(serverOptions.ListenOptions);
|
||||
Assert.Equal(5001, serverOptions.ListenOptions[0].IPEndPoint.Port);
|
||||
Assert.Null(serverOptions.ConfigurationLoader);
|
||||
Assert.NotNull(serverOptions.ConfigurationLoader);
|
||||
|
||||
builder.Load();
|
||||
|
||||
Assert.Single(serverOptions.ListenOptions);
|
||||
Assert.Equal(5001, serverOptions.ListenOptions[0].IPEndPoint.Port);
|
||||
Assert.Null(serverOptions.ConfigurationLoader);
|
||||
Assert.NotNull(serverOptions.ConfigurationLoader);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -131,6 +131,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
|
|||
serverOptions.ConfigureEndpointDefaults(opt =>
|
||||
{
|
||||
opt.NoDelay = false;
|
||||
opt.Protocols = HttpProtocols.Http2;
|
||||
});
|
||||
|
||||
serverOptions.ConfigureHttpsDefaults(opt =>
|
||||
|
|
@ -153,11 +154,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
|
|||
Assert.NotNull(opt.HttpsOptions.ServerCertificate);
|
||||
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.HttpsOptions.ClientCertificateMode);
|
||||
Assert.False(opt.ListenOptions.NoDelay);
|
||||
Assert.Equal(HttpProtocols.Http2, opt.ListenOptions.Protocols);
|
||||
})
|
||||
.LocalhostEndpoint(5002, opt =>
|
||||
{
|
||||
ran2 = true;
|
||||
Assert.False(opt.NoDelay);
|
||||
Assert.Equal(HttpProtocols.Http2, opt.Protocols);
|
||||
})
|
||||
.Load();
|
||||
|
||||
|
|
@ -316,6 +319,119 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http1", HttpProtocols.Http1)]
|
||||
[InlineData("http2", HttpProtocols.Http2)]
|
||||
[InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)]
|
||||
public void DefaultConfigSectionCanSetProtocols(string input, HttpProtocols expected)
|
||||
{
|
||||
var serverOptions = CreateServerOptions();
|
||||
var ranDefault = false;
|
||||
serverOptions.ConfigureEndpointDefaults(opt =>
|
||||
{
|
||||
Assert.Equal(expected, opt.Protocols);
|
||||
ranDefault = true;
|
||||
});
|
||||
|
||||
serverOptions.ConfigureHttpsDefaults(opt =>
|
||||
{
|
||||
opt.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
|
||||
opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
|
||||
});
|
||||
|
||||
var ran1 = false;
|
||||
var ran2 = false;
|
||||
var ran3 = false;
|
||||
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("EndpointDefaults:Protocols", input),
|
||||
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
|
||||
}).Build();
|
||||
serverOptions.Configure(config)
|
||||
.Endpoint("End1", opt =>
|
||||
{
|
||||
Assert.True(opt.IsHttps);
|
||||
Assert.NotNull(opt.HttpsOptions.ServerCertificate);
|
||||
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.HttpsOptions.ClientCertificateMode);
|
||||
Assert.Equal(expected, opt.ListenOptions.Protocols);
|
||||
ran1 = true;
|
||||
})
|
||||
.LocalhostEndpoint(5002, opt =>
|
||||
{
|
||||
Assert.Equal(expected, opt.Protocols);
|
||||
ran2 = true;
|
||||
})
|
||||
.Load();
|
||||
serverOptions.ListenAnyIP(0, opt =>
|
||||
{
|
||||
Assert.Equal(expected, opt.Protocols);
|
||||
ran3 = true;
|
||||
});
|
||||
|
||||
Assert.True(ranDefault);
|
||||
Assert.True(ran1);
|
||||
Assert.True(ran2);
|
||||
Assert.True(ran3);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http1", HttpProtocols.Http1)]
|
||||
[InlineData("http2", HttpProtocols.Http2)]
|
||||
[InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)]
|
||||
public void EndpointConfigSectionCanSetProtocols(string input, HttpProtocols expected)
|
||||
{
|
||||
var serverOptions = CreateServerOptions();
|
||||
var ranDefault = false;
|
||||
serverOptions.ConfigureEndpointDefaults(opt =>
|
||||
{
|
||||
// Kestrel default.
|
||||
Assert.Equal(HttpProtocols.Http1AndHttp2, opt.Protocols);
|
||||
ranDefault = true;
|
||||
});
|
||||
|
||||
serverOptions.ConfigureHttpsDefaults(opt =>
|
||||
{
|
||||
opt.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
|
||||
opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
|
||||
});
|
||||
|
||||
var ran1 = false;
|
||||
var ran2 = false;
|
||||
var ran3 = false;
|
||||
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("Endpoints:End1:Protocols", input),
|
||||
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
|
||||
}).Build();
|
||||
serverOptions.Configure(config)
|
||||
.Endpoint("End1", opt =>
|
||||
{
|
||||
Assert.True(opt.IsHttps);
|
||||
Assert.NotNull(opt.HttpsOptions.ServerCertificate);
|
||||
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.HttpsOptions.ClientCertificateMode);
|
||||
Assert.Equal(expected, opt.ListenOptions.Protocols);
|
||||
ran1 = true;
|
||||
})
|
||||
.LocalhostEndpoint(5002, opt =>
|
||||
{
|
||||
// Kestrel default.
|
||||
Assert.Equal(HttpProtocols.Http1AndHttp2, opt.Protocols);
|
||||
ran2 = true;
|
||||
})
|
||||
.Load();
|
||||
serverOptions.ListenAnyIP(0, opt =>
|
||||
{
|
||||
// Kestrel default.
|
||||
Assert.Equal(HttpProtocols.Http1AndHttp2, opt.Protocols);
|
||||
ran3 = true;
|
||||
});
|
||||
|
||||
Assert.True(ranDefault);
|
||||
Assert.True(ran1);
|
||||
Assert.True(ran2);
|
||||
Assert.True(ran3);
|
||||
}
|
||||
|
||||
private static string GetCertificatePath()
|
||||
{
|
||||
var appData = Environment.GetEnvironmentVariable("APPDATA");
|
||||
|
|
|
|||
|
|
@ -20,6 +20,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.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
|
@ -770,6 +771,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http1", HttpProtocols.Http1)]
|
||||
[InlineData("http2", HttpProtocols.Http2)]
|
||||
[InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)]
|
||||
public void EndpointDefaultsConfig_CanSetProtocolForUrlsConfig(string input, HttpProtocols expected)
|
||||
{
|
||||
KestrelServerOptions capturedOptions = null;
|
||||
var hostBuilder = TransportSelector.GetWebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("EndpointDefaults:Protocols", input),
|
||||
}).Build();
|
||||
options.Configure(config);
|
||||
|
||||
capturedOptions = options;
|
||||
})
|
||||
.ConfigureServices(AddTestLogging)
|
||||
.UseUrls("http://127.0.0.1:0")
|
||||
.Configure(ConfigureEchoAddress);
|
||||
|
||||
using (var host = hostBuilder.Build())
|
||||
{
|
||||
host.Start();
|
||||
Assert.Single(capturedOptions.ListenOptions);
|
||||
Assert.Equal(expected, capturedOptions.ListenOptions[0].Protocols);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowsWhenBindingLocalhostToAddressInUse(AddressFamily addressFamily)
|
||||
{
|
||||
TestApplicationErrorLogger.IgnoredExceptions.Add(typeof(IOException));
|
||||
|
|
|
|||
Loading…
Reference in New Issue