3
3
4
4
using System ;
5
5
using System . Collections . Generic ;
6
+ using System . Diagnostics ;
6
7
using System . IO ;
7
8
using System . Linq ;
8
9
using System . Net ;
11
12
using Microsoft . AspNetCore . Hosting . Server . Features ;
12
13
using Microsoft . AspNetCore . Protocols ;
13
14
using Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Infrastructure ;
15
+ using Microsoft . AspNetCore . Server . Kestrel . Transport . Abstractions . Internal ;
14
16
using Microsoft . Extensions . Logging ;
15
17
16
18
namespace Microsoft . AspNetCore . Server . Kestrel . Core . Internal
@@ -109,140 +111,152 @@ protected internal static bool TryCreateIPEndPoint(ServerAddress address, out IP
109
111
return true ;
110
112
}
111
113
112
- private static Task BindEndpointAsync ( IPEndPoint endpoint , AddressBindContext context )
113
- => BindEndpointAsync ( new ListenOptions ( endpoint ) , context ) ;
114
-
115
- private static async Task BindEndpointAsync ( ListenOptions endpoint , AddressBindContext context )
114
+ private static ListenOptions ParseAddress ( string address , AddressBindContext context )
116
115
{
117
- try
116
+ var parsedAddress = ServerAddress . FromUrl ( address ) ;
117
+ var https = false ;
118
+
119
+ if ( parsedAddress . Scheme . Equals ( "https" , StringComparison . OrdinalIgnoreCase ) )
118
120
{
119
- await context . CreateBinding ( endpoint ) . ConfigureAwait ( false ) ;
121
+ https = true ;
120
122
}
121
- catch ( AddressInUseException ex )
123
+ else if ( ! parsedAddress . Scheme . Equals ( "http" , StringComparison . OrdinalIgnoreCase ) )
122
124
{
123
- throw new IOException ( CoreStrings . FormatEndpointAlreadyInUse ( endpoint ) , ex ) ;
125
+ throw new InvalidOperationException ( CoreStrings . FormatUnsupportedAddressScheme ( address ) ) ;
124
126
}
125
127
126
- context . ListenOptions . Add ( endpoint ) ;
128
+ if ( ! string . IsNullOrEmpty ( parsedAddress . PathBase ) )
129
+ {
130
+ throw new InvalidOperationException ( CoreStrings . FormatConfigurePathBaseFromMethodCall ( $ "{ nameof ( IApplicationBuilder ) } .UsePathBase()") ) ;
131
+ }
132
+
133
+ ListenOptions options = null ;
134
+ if ( parsedAddress . IsUnixPipe )
135
+ {
136
+ options = new ListenOptions ( parsedAddress . UnixPipePath ) ;
137
+ }
138
+ else if ( string . Equals ( parsedAddress . Host , "localhost" , StringComparison . OrdinalIgnoreCase ) )
139
+ {
140
+ if ( parsedAddress . Port == 0 )
141
+ {
142
+ throw new InvalidOperationException ( CoreStrings . DynamicPortOnLocalhostNotSupported ) ;
143
+ }
144
+
145
+ // "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
146
+ options = new ListenOptions ( ListenType . Localhost )
147
+ {
148
+ IPEndPoint = new IPEndPoint ( IPAddress . Loopback , parsedAddress . Port ) ,
149
+ } ;
150
+ }
151
+ else if ( TryCreateIPEndPoint ( parsedAddress , out var endpoint ) )
152
+ {
153
+ options = new ListenOptions ( endpoint ) ;
154
+ }
155
+ else
156
+ {
157
+ // when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
158
+ options = new ListenOptions ( ListenType . AnyIP ) { IPEndPoint = new IPEndPoint ( IPAddress . IPv6Any , parsedAddress . Port ) } ;
159
+ }
160
+
161
+ if ( https )
162
+ {
163
+ options . KestrelServerOptions = context . ServerOptions ;
164
+ context . DefaultHttpsProvider . ConfigureHttps ( options ) ;
165
+ }
166
+
167
+ return options ;
127
168
}
128
169
129
- private static async Task BindLocalhostAsync ( ServerAddress address , AddressBindContext context , bool https )
170
+ private static async Task BindAsync ( ListenOptions options , AddressBindContext context )
130
171
{
131
- if ( address . Port == 0 )
172
+ if ( options . Type == ListenType . SocketPath
173
+ || options . Type == ListenType . IPEndPoint )
174
+ {
175
+ await BindEndpointAsync ( options , context ) . ConfigureAwait ( false ) ;
176
+ context . Addresses . Add ( options . GetDisplayName ( ) ) ;
177
+ }
178
+ else if ( options . Type == ListenType . Localhost )
179
+ {
180
+ // "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
181
+ await BindLocalhostAsync ( options , context ) . ConfigureAwait ( false ) ;
182
+ }
183
+ else if ( options . Type == ListenType . AnyIP )
184
+ {
185
+ // when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
186
+ try
187
+ {
188
+ options = options . CloneAs ( ListenType . IPEndPoint ) ;
189
+ options . IPEndPoint = new IPEndPoint ( IPAddress . IPv6Any , options . IPEndPoint . Port ) ;
190
+ await BindEndpointAsync ( options , context ) . ConfigureAwait ( false ) ;
191
+ }
192
+ catch ( Exception ex ) when ( ! ( ex is IOException ) )
193
+ {
194
+ context . Logger . LogDebug ( CoreStrings . FormatFallbackToIPv4Any ( options . IPEndPoint . Port ) ) ;
195
+
196
+ // for machines that do not support IPv6
197
+ options . IPEndPoint = new IPEndPoint ( IPAddress . Any , options . IPEndPoint . Port ) ;
198
+ await BindEndpointAsync ( options , context ) . ConfigureAwait ( false ) ;
199
+ }
200
+ context . Addresses . Add ( options . GetDisplayName ( ) ) ;
201
+ }
202
+ else
132
203
{
133
- throw new InvalidOperationException ( CoreStrings . DynamicPortOnLocalhostNotSupported ) ;
204
+ throw new NotImplementedException ( options . Type . ToString ( ) ) ;
134
205
}
206
+ }
207
+
208
+ private static async Task BindLocalhostAsync ( ListenOptions options , AddressBindContext context )
209
+ {
210
+ Debug . Assert ( options . Type == ListenType . Localhost ) ;
135
211
136
212
var exceptions = new List < Exception > ( ) ;
137
213
138
214
try
139
215
{
140
- var options = new ListenOptions ( new IPEndPoint ( IPAddress . Loopback , address . Port ) ) ;
141
- await BindEndpointAsync ( options , context ) . ConfigureAwait ( false ) ;
142
-
143
- if ( https )
144
- {
145
- options . KestrelServerOptions = context . ServerOptions ;
146
- context . DefaultHttpsProvider . ConfigureHttps ( options ) ;
147
- }
216
+ var v4Options = options . CloneAs ( ListenType . IPEndPoint ) ;
217
+ v4Options . IPEndPoint = new IPEndPoint ( IPAddress . Loopback , v4Options . IPEndPoint . Port ) ;
218
+ await BindEndpointAsync ( v4Options , context ) . ConfigureAwait ( false ) ;
148
219
}
149
220
catch ( Exception ex ) when ( ! ( ex is IOException ) )
150
221
{
151
- context . Logger . LogWarning ( 0 , CoreStrings . NetworkInterfaceBindingFailed , address , "IPv4 loopback" , ex . Message ) ;
222
+ context . Logger . LogWarning ( 0 , CoreStrings . NetworkInterfaceBindingFailed , options . GetDisplayName ( ) , "IPv4 loopback" , ex . Message ) ;
152
223
exceptions . Add ( ex ) ;
153
224
}
154
225
155
226
try
156
227
{
157
- var options = new ListenOptions ( new IPEndPoint ( IPAddress . IPv6Loopback , address . Port ) ) ;
158
- await BindEndpointAsync ( options , context ) . ConfigureAwait ( false ) ;
159
-
160
- if ( https )
161
- {
162
- options . KestrelServerOptions = context . ServerOptions ;
163
- context . DefaultHttpsProvider . ConfigureHttps ( options ) ;
164
- }
228
+ var v6Options = options . CloneAs ( ListenType . IPEndPoint ) ;
229
+ v6Options . IPEndPoint = new IPEndPoint ( IPAddress . IPv6Loopback , v6Options . IPEndPoint . Port ) ;
230
+ await BindEndpointAsync ( v6Options , context ) . ConfigureAwait ( false ) ;
165
231
}
166
232
catch ( Exception ex ) when ( ! ( ex is IOException ) )
167
233
{
168
- context . Logger . LogWarning ( 0 , CoreStrings . NetworkInterfaceBindingFailed , address , "IPv6 loopback" , ex . Message ) ;
234
+ context . Logger . LogWarning ( 0 , CoreStrings . NetworkInterfaceBindingFailed , options . GetDisplayName ( ) , "IPv6 loopback" , ex . Message ) ;
169
235
exceptions . Add ( ex ) ;
170
236
}
171
237
172
238
if ( exceptions . Count == 2 )
173
239
{
174
- throw new IOException ( CoreStrings . FormatAddressBindingFailed ( address ) , new AggregateException ( exceptions ) ) ;
240
+ throw new IOException ( CoreStrings . FormatAddressBindingFailed ( options . GetDisplayName ( ) ) , new AggregateException ( exceptions ) ) ;
175
241
}
176
242
177
243
// If StartLocalhost doesn't throw, there is at least one listener.
178
244
// The port cannot change for "localhost".
179
- context . Addresses . Add ( address . ToString ( ) ) ;
245
+ context . Addresses . Add ( options . GetDisplayName ( ) ) ;
180
246
}
181
247
182
- private static async Task BindAddressAsync ( string address , AddressBindContext context )
248
+ private static async Task BindEndpointAsync ( ListenOptions endpoint , AddressBindContext context )
183
249
{
184
- var parsedAddress = ServerAddress . FromUrl ( address ) ;
185
- var https = false ;
186
-
187
- if ( parsedAddress . Scheme . Equals ( "https" , StringComparison . OrdinalIgnoreCase ) )
188
- {
189
- https = true ;
190
- }
191
- else if ( ! parsedAddress . Scheme . Equals ( "http" , StringComparison . OrdinalIgnoreCase ) )
192
- {
193
- throw new InvalidOperationException ( CoreStrings . FormatUnsupportedAddressScheme ( address ) ) ;
194
- }
195
-
196
- if ( ! string . IsNullOrEmpty ( parsedAddress . PathBase ) )
197
- {
198
- throw new InvalidOperationException ( CoreStrings . FormatConfigurePathBaseFromMethodCall ( $ "{ nameof ( IApplicationBuilder ) } .UsePathBase()") ) ;
199
- }
200
-
201
- ListenOptions options = null ;
202
- if ( parsedAddress . IsUnixPipe )
203
- {
204
- options = new ListenOptions ( parsedAddress . UnixPipePath ) ;
205
- await BindEndpointAsync ( options , context ) . ConfigureAwait ( false ) ;
206
- context . Addresses . Add ( options . GetDisplayName ( ) ) ;
207
- }
208
- else if ( string . Equals ( parsedAddress . Host , "localhost" , StringComparison . OrdinalIgnoreCase ) )
250
+ try
209
251
{
210
- // "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
211
- await BindLocalhostAsync ( parsedAddress , context , https ) . ConfigureAwait ( false ) ;
252
+ await context . CreateBinding ( endpoint ) . ConfigureAwait ( false ) ;
212
253
}
213
- else
254
+ catch ( AddressInUseException ex )
214
255
{
215
- if ( TryCreateIPEndPoint ( parsedAddress , out var endpoint ) )
216
- {
217
- options = new ListenOptions ( endpoint ) ;
218
- await BindEndpointAsync ( options , context ) . ConfigureAwait ( false ) ;
219
- }
220
- else
221
- {
222
- // when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
223
- try
224
- {
225
- options = new ListenOptions ( new IPEndPoint ( IPAddress . IPv6Any , parsedAddress . Port ) ) ;
226
- await BindEndpointAsync ( options , context ) . ConfigureAwait ( false ) ;
227
- }
228
- catch ( Exception ex ) when ( ! ( ex is IOException ) )
229
- {
230
- context . Logger . LogDebug ( CoreStrings . FormatFallbackToIPv4Any ( parsedAddress . Port ) ) ;
231
-
232
- // for machines that do not support IPv6
233
- options = new ListenOptions ( new IPEndPoint ( IPAddress . Any , parsedAddress . Port ) ) ;
234
- await BindEndpointAsync ( options , context ) . ConfigureAwait ( false ) ;
235
- }
236
- }
237
-
238
- context . Addresses . Add ( options . GetDisplayName ( ) ) ;
256
+ throw new IOException ( CoreStrings . FormatEndpointAlreadyInUse ( endpoint ) , ex ) ;
239
257
}
240
258
241
- if ( https && options != null )
242
- {
243
- options . KestrelServerOptions = context . ServerOptions ;
244
- context . DefaultHttpsProvider . ConfigureHttps ( options ) ;
245
- }
259
+ context . ListenOptions . Add ( endpoint ) ;
246
260
}
247
261
248
262
private interface IStrategy
@@ -256,7 +270,7 @@ public async Task BindAsync(AddressBindContext context)
256
270
{
257
271
context . Logger . LogDebug ( CoreStrings . BindingToDefaultAddress , Constants . DefaultServerAddress ) ;
258
272
259
- await BindLocalhostAsync ( ServerAddress . FromUrl ( Constants . DefaultServerAddress ) , context , https : false ) . ConfigureAwait ( false ) ;
273
+ await AddressBinder . BindAsync ( ParseAddress ( Constants . DefaultServerAddress , context ) , context ) . ConfigureAwait ( false ) ;
260
274
}
261
275
}
262
276
@@ -308,9 +322,7 @@ public virtual async Task BindAsync(AddressBindContext context)
308
322
{
309
323
foreach ( var endpoint in _endpoints )
310
324
{
311
- await BindEndpointAsync ( endpoint , context ) . ConfigureAwait ( false ) ;
312
-
313
- context . Addresses . Add ( endpoint . GetDisplayName ( ) ) ;
325
+ await AddressBinder . BindAsync ( endpoint , context ) . ConfigureAwait ( false ) ;
314
326
}
315
327
}
316
328
}
@@ -328,7 +340,8 @@ public virtual async Task BindAsync(AddressBindContext context)
328
340
{
329
341
foreach ( var address in _addresses )
330
342
{
331
- await BindAddressAsync ( address , context ) . ConfigureAwait ( false ) ;
343
+ var options = ParseAddress ( address , context ) ;
344
+ await AddressBinder . BindAsync ( options , context ) . ConfigureAwait ( false ) ;
332
345
}
333
346
}
334
347
}
0 commit comments