Welcome to mirror list, hosted at ThFree Co, Russian Federation.

KestrelServerOptions.cs « src « Core « Kestrel « Servers « src - github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: fc6322dd417f9e893525dfb23f3350eeb4a45249 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Certificates.Generation;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Server.Kestrel.Core;

/// <summary>
/// Provides programmatic configuration of Kestrel-specific features.
/// </summary>
public class KestrelServerOptions
{
    internal const string DisableHttp1LineFeedTerminatorsSwitchKey = "Microsoft.AspNetCore.Server.Kestrel.DisableHttp1LineFeedTerminators";

    // internal to fast-path header decoding when RequestHeaderEncodingSelector is unchanged.
    internal static readonly Func<string, Encoding?> DefaultHeaderEncodingSelector = _ => null;

    private Func<string, Encoding?> _requestHeaderEncodingSelector = DefaultHeaderEncodingSelector;

    private Func<string, Encoding?> _responseHeaderEncodingSelector = DefaultHeaderEncodingSelector;

    // The following two lists configure the endpoints that Kestrel should listen to. If both lists are empty, the "urls" config setting (e.g. UseUrls) is used.
    internal List<ListenOptions> CodeBackedListenOptions { get; } = new List<ListenOptions>();
    internal List<ListenOptions> ConfigurationBackedListenOptions { get; } = new List<ListenOptions>();
    internal IEnumerable<ListenOptions> ListenOptions => CodeBackedListenOptions.Concat(ConfigurationBackedListenOptions);

    // For testing and debugging.
    internal List<ListenOptions> OptionsInUse { get; } = new List<ListenOptions>();

    /// <summary>
    /// Gets or sets whether the <c>Server</c> header should be included in each response.
    /// </summary>
    /// <remarks>
    /// Defaults to true.
    /// </remarks>
    public bool AddServerHeader { get; set; } = true;

    /// <summary>
    /// Gets or sets a value that controls whether dynamic compression of response headers is allowed.
    /// For more information about the security considerations of HPack dynamic header compression, visit
    /// <see href="https://tools.ietf.org/html/rfc7541#section-7"/>.
    /// </summary>
    /// <remarks>
    /// Defaults to true.
    /// </remarks>
    public bool AllowResponseHeaderCompression { get; set; } = true;

    /// <summary>
    /// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="HttpContext.Request"/> and <see cref="HttpContext.Response"/>
    /// </summary>
    /// <remarks>
    /// Defaults to false.
    /// </remarks>
    public bool AllowSynchronousIO { get; set; }

    /// <summary>
    /// Gets or sets a value that controls how the `:scheme` field for HTTP/2 and HTTP/3 requests is validated.
    /// <para>
    /// If <c>false</c> then the `:scheme` field for HTTP/2 and HTTP/3 requests must exactly match the transport (e.g. https for TLS
    /// connections, http for non-TLS). If <c>true</c> then the `:scheme` field for HTTP/2 and HTTP/3 requests can be set to alternate values
    /// and this will be reflected by `HttpRequest.Scheme`. The Scheme must still be valid according to
    /// <see href="https://datatracker.ietf.org/doc/html/rfc3986/#section-3.1"/>. Only enable this when working with a trusted proxy. This can be used in
    /// scenarios such as proxies converting from alternate protocols. See <see href="https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3"/>.
    /// Applications that enable this should validate an expected scheme is provided before using it.
    /// </para>
    /// </summary>
    /// <remarks>
    /// Defaults to <c>false</c>.
    /// </remarks>
    public bool AllowAlternateSchemes { get; set; }

    /// <summary>
    /// Gets or sets a value that controls whether the string values materialized
    /// will be reused across requests; if they match, or if the strings will always be reallocated.
    /// </summary>
    /// <remarks>
    /// Defaults to false.
    /// </remarks>
    public bool DisableStringReuse { get; set; }

    /// <summary>
    /// Controls whether to return the "Alt-Svc" header from an HTTP/2 or lower response for HTTP/3.
    /// </summary>
    /// <remarks>
    /// Defaults to false.
    /// </remarks>
    [Obsolete($"This property is obsolete and will be removed in a future version. It no longer has any impact on runtime behavior. Use {nameof(Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions)}.{nameof(Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.DisableAltSvcHeader)} to configure \"Alt-Svc\" behavior.", error: true)]
    public bool EnableAltSvc { get; set; }

    /// <summary>
    /// Gets or sets a callback that returns the <see cref="Encoding"/> to decode the value for the specified request header name,
    /// or <see langword="null"/> to use the default <see cref="UTF8Encoding"/>.
    /// </summary>
    public Func<string, Encoding?> RequestHeaderEncodingSelector
    {
        get => _requestHeaderEncodingSelector;
        set => _requestHeaderEncodingSelector = value ?? throw new ArgumentNullException(nameof(value));
    }

    /// <summary>
    /// Gets or sets a callback that returns the <see cref="Encoding"/> to encode the value for the specified response header
    /// or trailer name, or <see langword="null"/> to use the default <see cref="ASCIIEncoding"/>.
    /// </summary>
    public Func<string, Encoding?> ResponseHeaderEncodingSelector
    {
        get => _responseHeaderEncodingSelector;
        set => _responseHeaderEncodingSelector = value ?? throw new ArgumentNullException(nameof(value));
    }

    /// <summary>
    /// Enables the Listen options callback to resolve and use services registered by the application during startup.
    /// Typically initialized by UseKestrel().
    /// </summary>
    public IServiceProvider ApplicationServices { get; set; } = default!; // This should typically be set

    /// <summary>
    /// Provides access to request limit options.
    /// </summary>
    public KestrelServerLimits Limits { get; } = new KestrelServerLimits();

    /// <summary>
    /// Provides a configuration source where endpoints will be loaded from on server start.
    /// The default is <see langword="null"/>.
    /// </summary>
    public KestrelConfigurationLoader? ConfigurationLoader { get; set; }

    /// <summary>
    /// A default configuration action for all endpoints. Use for Listen, configuration, the default url, and URLs.
    /// </summary>
    private Action<ListenOptions> EndpointDefaults { get; set; } = _ => { };

    /// <summary>
    /// A default configuration action for all https endpoints.
    /// </summary>
    private Action<HttpsConnectionAdapterOptions> HttpsDefaults { get; set; } = _ => { };

    /// <summary>
    /// The default server certificate for https endpoints. This is applied lazily after HttpsDefaults and user options.
    /// </summary>
    internal X509Certificate2? DefaultCertificate { get; set; }

    /// <summary>
    /// Has the default dev certificate load been attempted?
    /// </summary>
    internal bool IsDevCertLoaded { get; set; }

    /// <summary>
    /// Internal AppContext switch to toggle the WebTransport and HTTP/3 datagrams experiemental features.
    /// </summary>
    private bool? _enableWebTransportAndH3Datagrams;
    internal bool EnableWebTransportAndH3Datagrams
    {
        get
        {
            if (!_enableWebTransportAndH3Datagrams.HasValue)
            {
                _enableWebTransportAndH3Datagrams = AppContext.TryGetSwitch("Microsoft.AspNetCore.Server.Kestrel.Experimental.WebTransportAndH3Datagrams", out var enabled) && enabled;
            }

            return _enableWebTransportAndH3Datagrams.Value;
        }
        set => _enableWebTransportAndH3Datagrams = value;
    }

    /// <summary>
    /// Internal AppContext switch to toggle whether a request line can end with LF only instead of CR/LF.
    /// </summary>
    private bool? _disableHttp1LineFeedTerminators;
    internal bool DisableHttp1LineFeedTerminators
    {
        get
        {
            if (!_disableHttp1LineFeedTerminators.HasValue)
            {
                _disableHttp1LineFeedTerminators = AppContext.TryGetSwitch(DisableHttp1LineFeedTerminatorsSwitchKey, out var disabled) && disabled;
            }

            return _disableHttp1LineFeedTerminators.Value;
        }
        set => _disableHttp1LineFeedTerminators = value;
    }

    /// <summary>
    /// Specifies a configuration Action to run for each newly created endpoint. Calling this again will replace
    /// the prior action.
    /// </summary>
    public void ConfigureEndpointDefaults(Action<ListenOptions> configureOptions)
    {
        EndpointDefaults = configureOptions ?? throw new ArgumentNullException(nameof(configureOptions));
    }

    internal void ApplyEndpointDefaults(ListenOptions listenOptions)
    {
        listenOptions.KestrelServerOptions = this;
        ConfigurationLoader?.ApplyEndpointDefaults(listenOptions);
        EndpointDefaults(listenOptions);
    }

    /// <summary>
    /// Specifies a configuration Action to run for each newly created https endpoint. Calling this again will replace
    /// the prior action.
    /// </summary>
    public void ConfigureHttpsDefaults(Action<HttpsConnectionAdapterOptions> configureOptions)
    {
        HttpsDefaults = configureOptions ?? throw new ArgumentNullException(nameof(configureOptions));
    }

    internal void ApplyHttpsDefaults(HttpsConnectionAdapterOptions httpsOptions)
    {
        ConfigurationLoader?.ApplyHttpsDefaults(httpsOptions);
        HttpsDefaults(httpsOptions);
    }

    internal void ApplyDefaultCert(HttpsConnectionAdapterOptions httpsOptions)
    {
        if (httpsOptions.ServerCertificate != null || httpsOptions.ServerCertificateSelector != null)
        {
            return;
        }

        EnsureDefaultCert();

        httpsOptions.ServerCertificate = DefaultCertificate;
    }

    internal void Serialize(Utf8JsonWriter writer)
    {
        writer.WritePropertyName(nameof(AllowSynchronousIO));
        writer.WriteBooleanValue(AllowSynchronousIO);

        writer.WritePropertyName(nameof(AddServerHeader));
        writer.WriteBooleanValue(AddServerHeader);

        writer.WritePropertyName(nameof(AllowAlternateSchemes));
        writer.WriteBooleanValue(AllowAlternateSchemes);

        writer.WritePropertyName(nameof(AllowResponseHeaderCompression));
        writer.WriteBooleanValue(AllowResponseHeaderCompression);

        writer.WritePropertyName(nameof(IsDevCertLoaded));
        writer.WriteBooleanValue(IsDevCertLoaded);

        writer.WriteString(nameof(RequestHeaderEncodingSelector), RequestHeaderEncodingSelector == DefaultHeaderEncodingSelector ? "default" : "configured");
        writer.WriteString(nameof(ResponseHeaderEncodingSelector), ResponseHeaderEncodingSelector == DefaultHeaderEncodingSelector ? "default" : "configured");

        // Limits
        writer.WritePropertyName(nameof(Limits));
        writer.WriteStartObject();
        Limits.Serialize(writer);
        writer.WriteEndObject();

        // ListenOptions
        writer.WritePropertyName(nameof(ListenOptions));
        writer.WriteStartArray();
        foreach (var listenOptions in OptionsInUse)
        {
            writer.WriteStartObject();
            writer.WriteString("Address", listenOptions.GetDisplayName());
            writer.WritePropertyName(nameof(listenOptions.IsTls));
            writer.WriteBooleanValue(listenOptions.IsTls);
            writer.WriteString(nameof(listenOptions.Protocols), listenOptions.Protocols.ToString());
            writer.WriteEndObject();
        }
        writer.WriteEndArray();
    }

    private void EnsureDefaultCert()
    {
        if (DefaultCertificate == null && !IsDevCertLoaded)
        {
            IsDevCertLoaded = true; // Only try once
            var logger = ApplicationServices!.GetRequiredService<ILogger<KestrelServer>>();
            try
            {
                DefaultCertificate = CertificateManager.Instance.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true)
                    .FirstOrDefault();

                if (DefaultCertificate != null)
                {
                    var status = CertificateManager.Instance.CheckCertificateState(DefaultCertificate, interactive: false);
                    if (!status.Success)
                    {
                        // Display a warning indicating to the user that a prompt might appear and provide instructions on what to do in that
                        // case. The underlying implementation of this check is specific to Mac OS and is handled within CheckCertificateState.
                        // Kestrel must NEVER cause a UI prompt on a production system. We only attempt this here because Mac OS is not supported
                        // in production.
                        Debug.Assert(status.FailureMessage != null, "Status with a failure result must have a message.");
                        logger.DeveloperCertificateFirstRun(status.FailureMessage);

                        // Prevent binding to HTTPS if the certificate is not valid (avoid the prompt)
                        DefaultCertificate = null;
                    }
                    else if (!CertificateManager.Instance.IsTrusted(DefaultCertificate))
                    {
                        logger.DeveloperCertificateNotTrusted();
                    }
                }
                else
                {
                    logger.UnableToLocateDevelopmentCertificate();
                }
            }
            catch
            {
                logger.UnableToLocateDevelopmentCertificate();
            }
        }
    }

    /// <summary>
    /// Creates a configuration loader for setting up Kestrel.
    /// </summary>
    /// <returns>A <see cref="KestrelConfigurationLoader"/> for configuring endpoints.</returns>
    public KestrelConfigurationLoader Configure() => Configure(new ConfigurationBuilder().Build());

    /// <summary>
    /// Creates a configuration loader for setting up Kestrel that takes an <see cref="IConfiguration"/> as input.
    /// This configuration must be scoped to the configuration section for Kestrel.
    /// Call <see cref="Configure(IConfiguration, bool)"/> to enable dynamic endpoint binding updates.
    /// </summary>
    /// <param name="config">The configuration section for Kestrel.</param>
    /// <returns>A <see cref="KestrelConfigurationLoader"/> for further endpoint configuration.</returns>
    public KestrelConfigurationLoader Configure(IConfiguration config) => Configure(config, reloadOnChange: false);

    /// <summary>
    /// Creates a configuration loader for setting up Kestrel that takes an <see cref="IConfiguration"/> as input.
    /// This configuration must be scoped to the configuration section for Kestrel.
    /// </summary>
    /// <param name="config">The configuration section for Kestrel.</param>
    /// <param name="reloadOnChange">
    /// If <see langword="true"/>, Kestrel will dynamically update endpoint bindings when configuration changes.
    /// This will only reload endpoints defined in the "Endpoints" section of your <paramref name="config"/>. Endpoints defined in code will not be reloaded.
    /// </param>
    /// <returns>A <see cref="KestrelConfigurationLoader"/> for further endpoint configuration.</returns>
    public KestrelConfigurationLoader Configure(IConfiguration config, bool reloadOnChange)
    {
        if (ApplicationServices is null)
        {
            throw new InvalidOperationException($"{nameof(ApplicationServices)} must not be null. This is normally set automatically via {nameof(IConfigureOptions<KestrelServerOptions>)}.");
        }

        var hostEnvironment = ApplicationServices.GetRequiredService<IHostEnvironment>();
        var logger = ApplicationServices.GetRequiredService<ILogger<KestrelServer>>();
        var httpsLogger = ApplicationServices.GetRequiredService<ILogger<HttpsConnectionMiddleware>>();

        var loader = new KestrelConfigurationLoader(this, config, hostEnvironment, reloadOnChange, logger, httpsLogger);
        ConfigurationLoader = loader;
        return loader;
    }

    /// <summary>
    /// Bind to given IP address and port.
    /// </summary>
    public void Listen(IPAddress address, int port)
    {
        Listen(address, port, _ => { });
    }

    /// <summary>
    /// Bind to given IP address and port.
    /// The callback configures endpoint-specific settings.
    /// </summary>
    public void Listen(IPAddress address, int port, Action<ListenOptions> configure)
    {
        ArgumentNullException.ThrowIfNull(address);

        Listen(new IPEndPoint(address, port), configure);
    }

    /// <summary>
    /// Bind to the given IP endpoint.
    /// </summary>
    public void Listen(IPEndPoint endPoint)
    {
        Listen((EndPoint)endPoint);
    }

    /// <summary>
    /// Bind to the given endpoint.
    /// </summary>
    /// <param name="endPoint"></param>
    public void Listen(EndPoint endPoint)
    {
        Listen(endPoint, _ => { });
    }

    /// <summary>
    /// Bind to given IP address and port.
    /// The callback configures endpoint-specific settings.
    /// </summary>
    public void Listen(IPEndPoint endPoint, Action<ListenOptions> configure)
    {
        Listen((EndPoint)endPoint, configure);
    }

    /// <summary>
    /// Bind to the given endpoint.
    /// The callback configures endpoint-specific settings.
    /// </summary>
    public void Listen(EndPoint endPoint, Action<ListenOptions> configure)
    {
        ArgumentNullException.ThrowIfNull(endPoint);
        ArgumentNullException.ThrowIfNull(configure);

        var listenOptions = new ListenOptions(endPoint);
        ApplyEndpointDefaults(listenOptions);
        configure(listenOptions);
        CodeBackedListenOptions.Add(listenOptions);
    }

    /// <summary>
    /// Listens on ::1 and 127.0.0.1 with the given port. Requesting a dynamic port by specifying 0 is not supported
    /// for this type of endpoint.
    /// </summary>
    public void ListenLocalhost(int port) => ListenLocalhost(port, options => { });

    /// <summary>
    /// Listens on ::1 and 127.0.0.1 with the given port. Requesting a dynamic port by specifying 0 is not supported
    /// for this type of endpoint.
    /// </summary>
    public void ListenLocalhost(int port, Action<ListenOptions> configure)
    {
        ArgumentNullException.ThrowIfNull(configure);

        var listenOptions = new LocalhostListenOptions(port);
        ApplyEndpointDefaults(listenOptions);
        configure(listenOptions);
        CodeBackedListenOptions.Add(listenOptions);
    }

    /// <summary>
    /// Listens on all IPs using IPv6 [::], or IPv4 0.0.0.0 if IPv6 is not supported.
    /// </summary>
    public void ListenAnyIP(int port) => ListenAnyIP(port, options => { });

    /// <summary>
    /// Listens on all IPs using IPv6 [::], or IPv4 0.0.0.0 if IPv6 is not supported.
    /// </summary>
    public void ListenAnyIP(int port, Action<ListenOptions> configure)
    {
        ArgumentNullException.ThrowIfNull(configure);

        var listenOptions = new AnyIPListenOptions(port);
        ApplyEndpointDefaults(listenOptions);
        configure(listenOptions);
        CodeBackedListenOptions.Add(listenOptions);
    }

    /// <summary>
    /// Bind to given Unix domain socket path.
    /// </summary>
    public void ListenUnixSocket(string socketPath)
    {
        ListenUnixSocket(socketPath, _ => { });
    }

    /// <summary>
    /// Bind to given Unix domain socket path.
    /// Specify callback to configure endpoint-specific settings.
    /// </summary>
    public void ListenUnixSocket(string socketPath, Action<ListenOptions> configure)
    {
        ArgumentNullException.ThrowIfNull(socketPath);

        if (!Path.IsPathRooted(socketPath))
        {
            throw new ArgumentException(CoreStrings.UnixSocketPathMustBeAbsolute, nameof(socketPath));
        }
        ArgumentNullException.ThrowIfNull(configure);

        var listenOptions = new ListenOptions(socketPath);
        ApplyEndpointDefaults(listenOptions);
        configure(listenOptions);
        CodeBackedListenOptions.Add(listenOptions);
    }

    /// <summary>
    /// Open a socket file descriptor.
    /// </summary>
    public void ListenHandle(ulong handle)
    {
        ListenHandle(handle, _ => { });
    }

    /// <summary>
    /// Open a socket file descriptor.
    /// The callback configures endpoint-specific settings.
    /// </summary>
    public void ListenHandle(ulong handle, Action<ListenOptions> configure)
    {
        ArgumentNullException.ThrowIfNull(configure);

        var listenOptions = new ListenOptions(handle);
        ApplyEndpointDefaults(listenOptions);
        configure(listenOptions);
        CodeBackedListenOptions.Add(listenOptions);
    }
}