Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 17452b0

Browse files
committed
#2139 Add ListenLocalhost and ListenPrefix
1 parent 065e9bb commit 17452b0

File tree

6 files changed

+139
-53
lines changed

6 files changed

+139
-53
lines changed

samples/SampleApp/Startup.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,19 @@ public static Task Main(string[] args)
7373
options.Listen(IPAddress.Loopback, basePort + 1, listenOptions =>
7474
{
7575
listenOptions.UseHttps("testCert.pfx", "testPassword");
76-
listenOptions.UseConnectionLogging();
76+
});
77+
78+
options.ListenLocalhost(basePort + 2);
79+
80+
// Use the default certificate
81+
options.ListenLocalhost(basePort + 3, useHttps: true);
82+
83+
// Use the default certificate
84+
options.ListenPrefix($"https://*:{basePort + 4}");
85+
86+
options.ListenPrefix($"https://*:{basePort + 5}", listenOptions =>
87+
{
88+
listenOptions.UseHttps("testCert.pfx", "testPassword");
7789
});
7890

7991
options.UseSystemd();

src/Kestrel.Core/Internal/AddressBinder.cs

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.IO;
78
using System.Linq;
89
using System.Net;
@@ -11,6 +12,7 @@
1112
using Microsoft.AspNetCore.Hosting.Server.Features;
1213
using Microsoft.AspNetCore.Protocols;
1314
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
15+
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
1416
using Microsoft.Extensions.Logging;
1517

1618
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
@@ -109,9 +111,6 @@ protected internal static bool TryCreateIPEndPoint(ServerAddress address, out IP
109111
return true;
110112
}
111113

112-
private static Task BindEndpointAsync(IPEndPoint endpoint, AddressBindContext context)
113-
=> BindEndpointAsync(new ListenOptions(endpoint), context);
114-
115114
private static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
116115
{
117116
try
@@ -122,12 +121,13 @@ private static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindC
122121
{
123122
throw new IOException(CoreStrings.FormatEndpointAlreadyInUse(endpoint), ex);
124123
}
125-
126124
context.ListenOptions.Add(endpoint);
127125
}
128126

129-
private static async Task BindLocalhostAsync(ServerAddress address, AddressBindContext context, bool https)
127+
private static async Task BindLocalhostAsync(ListenOptions listenOptions, AddressBindContext context)
130128
{
129+
var address = ServerAddress.FromUrl(listenOptions.Prefix);
130+
Debug.Assert(string.Equals(address.Host, "localhost", StringComparison.OrdinalIgnoreCase));
131131
if (address.Port == 0)
132132
{
133133
throw new InvalidOperationException(CoreStrings.DynamicPortOnLocalhostNotSupported);
@@ -137,14 +137,9 @@ private static async Task BindLocalhostAsync(ServerAddress address, AddressBindC
137137

138138
try
139139
{
140-
var options = new ListenOptions(new IPEndPoint(IPAddress.Loopback, address.Port));
140+
var options = listenOptions.CloneAs(ListenType.IPEndPoint);
141+
options.IPEndPoint = new IPEndPoint(IPAddress.Loopback, address.Port);
141142
await BindEndpointAsync(options, context).ConfigureAwait(false);
142-
143-
if (https)
144-
{
145-
options.KestrelServerOptions = context.ServerOptions;
146-
context.DefaultHttpsProvider.ConfigureHttps(options);
147-
}
148143
}
149144
catch (Exception ex) when (!(ex is IOException))
150145
{
@@ -154,14 +149,9 @@ private static async Task BindLocalhostAsync(ServerAddress address, AddressBindC
154149

155150
try
156151
{
157-
var options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, address.Port));
152+
var options = listenOptions.CloneAs(ListenType.IPEndPoint);
153+
options.IPEndPoint = new IPEndPoint(IPAddress.IPv6Loopback, address.Port);
158154
await BindEndpointAsync(options, context).ConfigureAwait(false);
159-
160-
if (https)
161-
{
162-
options.KestrelServerOptions = context.ServerOptions;
163-
context.DefaultHttpsProvider.ConfigureHttps(options);
164-
}
165155
}
166156
catch (Exception ex) when (!(ex is IOException))
167157
{
@@ -179,69 +169,81 @@ private static async Task BindLocalhostAsync(ServerAddress address, AddressBindC
179169
context.Addresses.Add(address.ToString());
180170
}
181171

182-
private static async Task BindAddressAsync(string address, AddressBindContext context)
172+
private static Task BindAddressAsync(string address, AddressBindContext context)
173+
{
174+
return BindAddressAsync(new ListenOptions(ListenType.Prefix)
175+
{
176+
Prefix = address,
177+
KestrelServerOptions = context.ServerOptions,
178+
}, context);
179+
}
180+
181+
private static async Task BindAddressAsync(ListenOptions listenOptions, AddressBindContext context)
183182
{
184-
var parsedAddress = ServerAddress.FromUrl(address);
185-
var https = false;
183+
var parsedAddress = ServerAddress.FromUrl(listenOptions.Prefix);
184+
var addHttps = false;
186185

187186
if (parsedAddress.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
188187
{
189-
https = true;
188+
addHttps = true;
190189
}
191190
else if (!parsedAddress.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase))
192191
{
193-
throw new InvalidOperationException(CoreStrings.FormatUnsupportedAddressScheme(address));
192+
throw new InvalidOperationException(CoreStrings.FormatUnsupportedAddressScheme(listenOptions.Prefix));
193+
}
194+
195+
// Https may already be configured for this endpoint
196+
if (addHttps && !listenOptions.ConnectionAdapters.Any(f => f.IsHttps))
197+
{
198+
listenOptions.KestrelServerOptions = context.ServerOptions;
199+
context.DefaultHttpsProvider.ConfigureHttps(listenOptions);
194200
}
195201

196202
if (!string.IsNullOrEmpty(parsedAddress.PathBase))
197203
{
198204
throw new InvalidOperationException(CoreStrings.FormatConfigurePathBaseFromMethodCall($"{nameof(IApplicationBuilder)}.UsePathBase()"));
199205
}
200206

201-
ListenOptions options = null;
202207
if (parsedAddress.IsUnixPipe)
203208
{
204-
options = new ListenOptions(parsedAddress.UnixPipePath);
205-
await BindEndpointAsync(options, context).ConfigureAwait(false);
206-
context.Addresses.Add(options.GetDisplayName());
209+
listenOptions.Type = ListenType.SocketPath;
210+
listenOptions.SocketPath = parsedAddress.UnixPipePath;
211+
await BindEndpointAsync(listenOptions, context).ConfigureAwait(false);
212+
context.Addresses.Add(listenOptions.GetDisplayName());
207213
}
208214
else if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase))
209215
{
210216
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
211-
await BindLocalhostAsync(parsedAddress, context, https).ConfigureAwait(false);
217+
await BindLocalhostAsync(listenOptions, context).ConfigureAwait(false);
212218
}
213219
else
214220
{
215221
if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
216222
{
217-
options = new ListenOptions(endpoint);
218-
await BindEndpointAsync(options, context).ConfigureAwait(false);
223+
listenOptions.Type = ListenType.IPEndPoint;
224+
listenOptions.IPEndPoint = endpoint;
225+
await BindEndpointAsync(listenOptions, context).ConfigureAwait(false);
219226
}
220227
else
221228
{
222229
// when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
223230
try
224231
{
225-
options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Any, parsedAddress.Port));
226-
await BindEndpointAsync(options, context).ConfigureAwait(false);
232+
listenOptions.Type = ListenType.IPEndPoint;
233+
listenOptions.IPEndPoint = new IPEndPoint(IPAddress.IPv6Any, parsedAddress.Port);
234+
await BindEndpointAsync(listenOptions, context).ConfigureAwait(false);
227235
}
228236
catch (Exception ex) when (!(ex is IOException))
229237
{
230238
context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(parsedAddress.Port));
231239

232240
// for machines that do not support IPv6
233-
options = new ListenOptions(new IPEndPoint(IPAddress.Any, parsedAddress.Port));
234-
await BindEndpointAsync(options, context).ConfigureAwait(false);
241+
listenOptions.IPEndPoint = new IPEndPoint(IPAddress.Any, parsedAddress.Port);
242+
await BindEndpointAsync(listenOptions, context).ConfigureAwait(false);
235243
}
236244
}
237245

238-
context.Addresses.Add(options.GetDisplayName());
239-
}
240-
241-
if (https && options != null)
242-
{
243-
options.KestrelServerOptions = context.ServerOptions;
244-
context.DefaultHttpsProvider.ConfigureHttps(options);
246+
context.Addresses.Add(listenOptions.GetDisplayName());
245247
}
246248
}
247249

@@ -256,7 +258,11 @@ public async Task BindAsync(AddressBindContext context)
256258
{
257259
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress);
258260

259-
await BindLocalhostAsync(ServerAddress.FromUrl(Constants.DefaultServerAddress), context, https: false).ConfigureAwait(false);
261+
await BindLocalhostAsync(new ListenOptions(ListenType.Prefix)
262+
{
263+
Prefix = Constants.DefaultServerAddress,
264+
KestrelServerOptions = context.ServerOptions,
265+
}, context).ConfigureAwait(false);
260266
}
261267
}
262268

@@ -308,9 +314,16 @@ public virtual async Task BindAsync(AddressBindContext context)
308314
{
309315
foreach (var endpoint in _endpoints)
310316
{
311-
await BindEndpointAsync(endpoint, context).ConfigureAwait(false);
317+
if (endpoint.Type == ListenType.Prefix)
318+
{
319+
await BindAddressAsync(endpoint, context);
320+
}
321+
else
322+
{
323+
await BindEndpointAsync(endpoint, context).ConfigureAwait(false);
312324

313-
context.Addresses.Add(endpoint.GetDisplayName());
325+
context.Addresses.Add(endpoint.GetDisplayName());
326+
}
314327
}
315328
}
316329
}

src/Kestrel.Core/KestrelServerOptions.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Linq;
67
using System.Net;
78
using Microsoft.AspNetCore.Http;
89
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
@@ -105,6 +106,40 @@ public void Listen(IPEndPoint endPoint, Action<ListenOptions> configure)
105106
ListenOptions.Add(listenOptions);
106107
}
107108

109+
public void ListenPrefix(string prefix) => ListenPrefix(prefix, configure => { });
110+
111+
public void ListenPrefix(string prefix, Action<ListenOptions> configure)
112+
{
113+
if (string.IsNullOrEmpty(prefix))
114+
{
115+
throw new ArgumentNullException(nameof(prefix));
116+
}
117+
if (configure == null)
118+
{
119+
throw new ArgumentNullException(nameof(configure));
120+
}
121+
122+
var listenOptions = new ListenOptions(ListenType.Prefix)
123+
{
124+
Prefix = prefix,
125+
KestrelServerOptions = this,
126+
};
127+
configure(listenOptions);
128+
ListenOptions.Add(listenOptions);
129+
}
130+
131+
public void ListenLocalhost(int port) => ListenLocalhost(port, configure => { });
132+
133+
public void ListenLocalhost(int port, bool useHttps) => ListenLocalhost(port, useHttps, configure => { });
134+
135+
public void ListenLocalhost(int port, Action<ListenOptions> configure) => ListenLocalhost(port, false, options => { });
136+
137+
public void ListenLocalhost(int port, bool useHttps, Action<ListenOptions> configure)
138+
{
139+
var scheme = useHttps ? "https" : "http";
140+
ListenPrefix($"{scheme}://localhost:{port}", configure);
141+
}
142+
108143
/// <summary>
109144
/// Bind to given Unix domain socket path.
110145
/// </summary>

src/Kestrel.Core/ListenOptions.cs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,20 @@ public class ListenOptions : IEndPointInformation, IConnectionBuilder
2121
private FileHandleType _handleType;
2222
private readonly List<Func<ConnectionDelegate, ConnectionDelegate>> _components = new List<Func<ConnectionDelegate, ConnectionDelegate>>();
2323

24+
internal ListenOptions(ListenType type)
25+
{
26+
Type = type;
27+
}
28+
2429
internal ListenOptions(IPEndPoint endPoint)
30+
: this(ListenType.IPEndPoint)
2531
{
26-
Type = ListenType.IPEndPoint;
2732
IPEndPoint = endPoint;
2833
}
2934

3035
internal ListenOptions(string socketPath)
36+
: this(ListenType.SocketPath)
3137
{
32-
Type = ListenType.SocketPath;
3338
SocketPath = socketPath;
3439
}
3540

@@ -39,8 +44,8 @@ internal ListenOptions(ulong fileHandle)
3944
}
4045

4146
internal ListenOptions(ulong fileHandle, FileHandleType handleType)
47+
: this(ListenType.FileHandle)
4248
{
43-
Type = ListenType.FileHandle;
4449
FileHandle = fileHandle;
4550
switch (handleType)
4651
{
@@ -57,7 +62,7 @@ internal ListenOptions(ulong fileHandle, FileHandleType handleType)
5762
/// <summary>
5863
/// The type of interface being described: either an <see cref="IPEndPoint"/>, Unix domain socket path, or a file descriptor.
5964
/// </summary>
60-
public ListenType Type { get; }
65+
public ListenType Type { get; internal set; }
6166

6267
public FileHandleType HandleType
6368
{
@@ -92,17 +97,19 @@ public FileHandleType HandleType
9297
/// </summary>
9398
public IPEndPoint IPEndPoint { get; set; }
9499

100+
internal string Prefix { get; set; }
101+
95102
/// <summary>
96103
/// The absolute path to a Unix domain socket to bind to.
97104
/// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="ListenType.SocketPath"/>.
98105
/// </summary>
99-
public string SocketPath { get; }
106+
public string SocketPath { get; internal set; }
100107

101108
/// <summary>
102109
/// A file descriptor for the socket to open.
103110
/// Only set if the <see cref="ListenOptions"/> <see cref="Type"/> is <see cref="ListenType.FileHandle"/>.
104111
/// </summary>
105-
public ulong FileHandle { get; }
112+
public ulong FileHandle { get; internal set; }
106113

107114
/// <summary>
108115
/// Enables an <see cref="IConnectionAdapter"/> to resolve and use services registered by the application during startup.
@@ -148,6 +155,8 @@ internal string GetDisplayName()
148155

149156
switch (Type)
150157
{
158+
case ListenType.Prefix:
159+
return Prefix;
151160
case ListenType.IPEndPoint:
152161
return $"{scheme}://{IPEndPoint}";
153162
case ListenType.SocketPath:
@@ -182,5 +191,21 @@ public ConnectionDelegate Build()
182191

183192
return app;
184193
}
194+
195+
// primarily used for cloning localhost to two IPEndpoints
196+
internal ListenOptions CloneAs(ListenType type)
197+
{
198+
var options = new ListenOptions(type)
199+
{
200+
HandleType = HandleType,
201+
IPEndPoint = IPEndPoint,
202+
KestrelServerOptions = KestrelServerOptions,
203+
NoDelay = NoDelay,
204+
Prefix = Prefix,
205+
Protocols = Protocols,
206+
};
207+
options.ConnectionAdapters.AddRange(ConnectionAdapters);
208+
return options;
209+
}
185210
}
186211
}

src/Kestrel.Transport.Abstractions/Internal/ListenType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
88
/// </summary>
99
public enum ListenType
1010
{
11+
Prefix,
1112
IPEndPoint,
1213
SocketPath,
1314
FileHandle

test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public LibuvOutputConsumerTests()
4141
_bufferPool = new MemoryPool();
4242
_mockLibuv = new MockLibuv();
4343

44-
var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions(0));
44+
var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0));
4545
_libuvThread = new LibuvThread(libuvTransport, maxLoops: 1);
4646
_libuvThread.StartAsync().Wait();
4747
}

0 commit comments

Comments
 (0)