diff --git a/src/Hosting/Abstractions/src/PublicAPI.Unshipped.txt b/src/Hosting/Abstractions/src/PublicAPI.Unshipped.txt
index 7dc5c58110bf..1e94a8b9b73a 100644
--- a/src/Hosting/Abstractions/src/PublicAPI.Unshipped.txt
+++ b/src/Hosting/Abstractions/src/PublicAPI.Unshipped.txt
@@ -1 +1,3 @@
#nullable enable
+static readonly Microsoft.AspNetCore.Hosting.WebHostDefaults.HttpPortsKey -> string!
+static readonly Microsoft.AspNetCore.Hosting.WebHostDefaults.HttpsPortsKey -> string!
diff --git a/src/Hosting/Abstractions/src/WebHostDefaults.cs b/src/Hosting/Abstractions/src/WebHostDefaults.cs
index 4bc80699019d..1024c7f1e23e 100644
--- a/src/Hosting/Abstractions/src/WebHostDefaults.cs
+++ b/src/Hosting/Abstractions/src/WebHostDefaults.cs
@@ -53,6 +53,16 @@ public static class WebHostDefaults
///
public static readonly string ServerUrlsKey = "urls";
+ ///
+ /// The configuration key associated with the "http_ports" configuration.
+ ///
+ public static readonly string HttpPortsKey = "http_ports";
+
+ ///
+ /// The configuration key associated with the "https_ports" configuration.
+ ///
+ public static readonly string HttpsPortsKey = "https_ports";
+
///
/// The configuration key associated with the "ContentRoot" configuration.
///
diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostService.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostService.cs
index f70e65f388b7..d281484cc048 100644
--- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostService.cs
+++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostService.cs
@@ -73,6 +73,27 @@ public async Task StartAsync(CancellationToken cancellationToken)
urls = Options.WebHostOptions.ServerUrls;
}
+ var httpPorts = Configuration[WebHostDefaults.HttpPortsKey] ?? string.Empty;
+ var httpsPorts = Configuration[WebHostDefaults.HttpsPortsKey] ?? string.Empty;
+ if (string.IsNullOrEmpty(urls))
+ {
+ // HTTP_PORTS and HTTPS_PORTS, these are lower priority than Urls.
+ static string ExpandPorts(string ports, string scheme)
+ {
+ return string.Join(';',
+ ports.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
+ .Select(port => $"{scheme}://*:{port}"));
+ }
+
+ var httpUrls = ExpandPorts(httpPorts, Uri.UriSchemeHttp);
+ var httpsUrls = ExpandPorts(httpsPorts, Uri.UriSchemeHttps);
+ urls = $"{httpUrls};{httpsUrls}";
+ }
+ else if (!string.IsNullOrEmpty(httpPorts) || !string.IsNullOrEmpty(httpsPorts))
+ {
+ Logger.PortsOverridenByUrls(httpPorts, httpsPorts, urls);
+ }
+
if (!string.IsNullOrEmpty(urls))
{
// We support reading "preferHostingUrls" from app configuration
diff --git a/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs b/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs
index c5550af76c6b..2c9d46b6c1d1 100644
--- a/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs
+++ b/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs
@@ -42,5 +42,12 @@ public static void ApplicationError(this ILogger logger, EventId eventId, string
message: message,
exception: exception);
}
+
+ public static void PortsOverridenByUrls(this ILogger logger, string httpPorts, string httpsPorts, string urls)
+ {
+ logger.LogWarning(eventId: LoggerEventIds.PortsOverridenByUrls,
+ message: "Overriding HTTP_PORTS '{http}' and HTTPS_PORTS '{https}'. Binding to values defined by URLS instead '{urls}'.",
+ httpPorts, httpsPorts, urls);
+ }
}
diff --git a/src/Hosting/Hosting/src/Internal/LoggerEventIds.cs b/src/Hosting/Hosting/src/Internal/LoggerEventIds.cs
index beb534aa406c..6b644347c863 100644
--- a/src/Hosting/Hosting/src/Internal/LoggerEventIds.cs
+++ b/src/Hosting/Hosting/src/Internal/LoggerEventIds.cs
@@ -19,4 +19,5 @@ internal static class LoggerEventIds
public const int ServerShutdownException = 12;
public const int HostingStartupAssemblyLoaded = 13;
public const int ServerListeningOnAddresses = 14;
+ public const int PortsOverridenByUrls = 15;
}
diff --git a/src/Hosting/Hosting/test/GenericWebHostBuilderTests.cs b/src/Hosting/Hosting/test/GenericWebHostBuilderTests.cs
index 36cc21e2deff..d517e6e0fe6b 100644
--- a/src/Hosting/Hosting/test/GenericWebHostBuilderTests.cs
+++ b/src/Hosting/Hosting/test/GenericWebHostBuilderTests.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Linq;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
@@ -87,6 +88,46 @@ public void UseUrlsWorksAfterHostConfigurationSourcesAreCleared()
Assert.Equal("TEST_URL", server.Addresses.Single());
}
+ [Theory]
+ [InlineData(null, null, null, "")]
+ [InlineData("", "", "", "")]
+ [InlineData("http://urls", "", "", "http://urls")]
+ [InlineData("http://urls", "5000", "", "http://urls")]
+ [InlineData("http://urls", "", "5001", "http://urls")]
+ [InlineData("http://urls", "5000", "5001", "http://urls")]
+ [InlineData("", "5000", "", "http://*:5000")]
+ [InlineData("", "5000;5002;5004", "", "http://*:5000;http://*:5002;http://*:5004")]
+ [InlineData("", "", "5001", "https://*:5001")]
+ [InlineData("", "", "5001;5003;5005", "https://*:5001;https://*:5003;https://*:5005")]
+ [InlineData("", "5000", "5001", "http://*:5000;https://*:5001")]
+ [InlineData("", "5000;5002", "5001;5003", "http://*:5000;http://*:5002;https://*:5001;https://*:5003")]
+ public void ReadsUrlsOrPorts(string urls, string httpPorts, string httpsPorts, string expected)
+ {
+ var server = new TestServer();
+
+ using var host = new HostBuilder()
+ .ConfigureHostConfiguration(config =>
+ {
+ config.AddInMemoryCollection(new[]
+ {
+ new KeyValuePair("urls", urls),
+ new KeyValuePair("http_ports", httpPorts),
+ new KeyValuePair("https_ports", httpsPorts),
+ });
+ })
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .UseServer(server)
+ .Configure(_ => { });
+ })
+ .Build();
+
+ host.Start();
+
+ Assert.Equal(expected, string.Join(';', server.Addresses));
+ }
+
private class TestServer : IServer, IServerAddressesFeature
{
public TestServer()