File: KestrelServerLimits.cs
Web Access
Project: src\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj (Microsoft.AspNetCore.Server.Kestrel.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Text.Json;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Core;
 
/// <summary>
/// Limits for <see cref="KestrelServer"/>.
/// </summary>
public class KestrelServerLimits
{
    // Matches the non-configurable default response buffer size for Kestrel in 1.0.0
    private long? _maxResponseBufferSize = 64 * 1024;
 
    // Matches the default client_max_body_size in nginx.
    // Also large enough that most requests should be under the limit.
    private long? _maxRequestBufferSize = 1024 * 1024;
 
    // Matches the default large_client_header_buffers in nginx.
    private int _maxRequestLineSize = 8 * 1024;
 
    // Matches the default large_client_header_buffers in nginx.
    private int _maxRequestHeadersTotalSize = 32 * 1024;
 
    // Matches the default maxAllowedContentLength in IIS (~28.6 MB)
    // https://www.iis.net/configreference/system.webserver/security/requestfiltering/requestlimits#005
    private long? _maxRequestBodySize = 30000000;
 
    // Matches the default LimitRequestFields in Apache httpd.
    private int _maxRequestHeaderCount = 100;
 
    // Slightly more than SocketHttpHandler's old PooledConnectionIdleTimeout of 2 minutes.
    // https://github.com/dotnet/runtime/issues/52267
    private TimeSpan _keepAliveTimeout = TimeSpan.FromSeconds(130);
 
    private TimeSpan _requestHeadersTimeout = TimeSpan.FromSeconds(30);
 
    // Unlimited connections are allowed by default.
    private long? _maxConcurrentConnections;
    private long? _maxConcurrentUpgradedConnections;
 
    /// <summary>
    /// Gets or sets the maximum size of the response buffer before write
    /// calls begin to block or return tasks that don't complete until the
    /// buffer size drops below the configured limit.
    /// Defaults to 65,536 bytes (64 KB).
    /// </summary>
    /// <remarks>
    /// When set to null, the size of the response buffer is unlimited.
    /// When set to zero, all write calls will block or return tasks that
    /// don't complete until the entire response buffer is flushed.
    /// </remarks>
    public long? MaxResponseBufferSize
    {
        get => _maxResponseBufferSize;
        set
        {
            if (value.HasValue && value.Value < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.NonNegativeNumberOrNullRequired);
            }
            _maxResponseBufferSize = value;
        }
    }
 
    /// <summary>
    /// Gets or sets the maximum size of the request buffer.
    /// Defaults to 1,048,576 bytes (1 MB).
    /// </summary>
    /// <remarks>
    /// When set to null, the size of the request buffer is unlimited.
    /// </remarks>
    public long? MaxRequestBufferSize
    {
        get => _maxRequestBufferSize;
        set
        {
            if (value.HasValue && value.Value <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveNumberOrNullRequired);
            }
            _maxRequestBufferSize = value;
        }
    }
 
    /// <summary>
    /// Gets or sets the maximum allowed size for the HTTP request line.
    /// Defaults to 8,192 bytes (8 KB).
    /// </summary>
    /// <remarks>
    /// For HTTP/2 and HTTP/3 this measures the total size of the required pseudo headers
    /// :method, :scheme, :authority, and :path.
    /// </remarks>
    public int MaxRequestLineSize
    {
        get => _maxRequestLineSize;
        set
        {
            if (value <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveNumberRequired);
            }
            _maxRequestLineSize = value;
        }
    }
 
    /// <summary>
    /// Gets or sets the maximum allowed size for the HTTP request headers.
    /// Defaults to 32,768 bytes (32 KB).
    /// </summary>
    /// <remarks>
    /// </remarks>
    public int MaxRequestHeadersTotalSize
    {
        get => _maxRequestHeadersTotalSize;
        set
        {
            if (value <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveNumberRequired);
            }
            _maxRequestHeadersTotalSize = value;
        }
    }
 
    /// <summary>
    /// Gets or sets the maximum allowed number of headers per HTTP request.
    /// Defaults to 100.
    /// </summary>
    /// <remarks>
    /// </remarks>
    public int MaxRequestHeaderCount
    {
        get => _maxRequestHeaderCount;
        set
        {
            if (value <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveNumberRequired);
            }
            _maxRequestHeaderCount = value;
        }
    }
 
    /// <summary>
    /// Gets or sets the maximum allowed size of any request body in bytes.
    /// When set to null, the maximum request body size is unlimited.
    /// This limit has no effect on upgraded connections which are always unlimited.
    /// This can be overridden per-request via <see cref="IHttpMaxRequestBodySizeFeature"/>.
    /// Defaults to 30,000,000 bytes, which is approximately 28.6MB.
    /// </summary>
    /// <remarks>
    /// </remarks>
    public long? MaxRequestBodySize
    {
        get => _maxRequestBodySize;
        set
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.NonNegativeNumberOrNullRequired);
            }
            _maxRequestBodySize = value;
        }
    }
 
    /// <summary>
    /// Gets or sets the keep-alive timeout.
    /// Defaults to 130 seconds.
    /// </summary>
    /// <remarks>
    /// </remarks>
    public TimeSpan KeepAliveTimeout
    {
        get => _keepAliveTimeout;
        set
        {
            if (value <= TimeSpan.Zero && value != Timeout.InfiniteTimeSpan)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveTimeSpanRequired);
            }
            _keepAliveTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
        }
    }
 
    /// <summary>
    /// Gets or sets the maximum amount of time the server will spend receiving request headers.
    /// Defaults to 30 seconds.
    /// </summary>
    /// <remarks>
    /// </remarks>
    public TimeSpan RequestHeadersTimeout
    {
        get => _requestHeadersTimeout;
        set
        {
            if (value <= TimeSpan.Zero && value != Timeout.InfiniteTimeSpan)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveTimeSpanRequired);
            }
            _requestHeadersTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
        }
    }
 
    /// <summary>
    /// Gets or sets the maximum number of open connections. When set to null, the number of connections is unlimited.
    /// <para>
    /// Defaults to null.
    /// </para>
    /// </summary>
    /// <remarks>
    /// <para>
    /// When a connection is upgraded to another protocol, such as WebSockets, its connection is counted against the
    /// <see cref="MaxConcurrentUpgradedConnections" /> limit instead of <see cref="MaxConcurrentConnections" />.
    /// </para>
    /// </remarks>
    public long? MaxConcurrentConnections
    {
        get => _maxConcurrentConnections;
        set
        {
            if (value.HasValue && value <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveNumberOrNullRequired);
            }
            _maxConcurrentConnections = value;
        }
    }
 
    /// <summary>
    /// Gets or sets the maximum number of open, upgraded connections. When set to null, the number of upgraded connections is unlimited.
    /// An upgraded connection is one that has been switched from HTTP to another protocol, such as WebSockets.
    /// <para>
    /// Defaults to null.
    /// </para>
    /// </summary>
    /// <remarks>
    /// <para>
    /// When a connection is upgraded to another protocol, such as WebSockets, its connection is counted against the
    /// <see cref="MaxConcurrentUpgradedConnections" /> limit instead of <see cref="MaxConcurrentConnections" />.
    /// </para>
    /// </remarks>
    public long? MaxConcurrentUpgradedConnections
    {
        get => _maxConcurrentUpgradedConnections;
        set
        {
            if (value.HasValue && value < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.NonNegativeNumberOrNullRequired);
            }
            _maxConcurrentUpgradedConnections = value;
        }
    }
 
    internal void Serialize(Utf8JsonWriter writer)
    {
        writer.WriteString(nameof(KeepAliveTimeout), KeepAliveTimeout.ToString());
 
        writer.WritePropertyName(nameof(MaxConcurrentConnections));
        if (MaxConcurrentConnections is null)
        {
            writer.WriteNullValue();
        }
        else
        {
            writer.WriteNumberValue(MaxConcurrentConnections.Value);
        }
 
        writer.WritePropertyName(nameof(MaxConcurrentUpgradedConnections));
        if (MaxConcurrentUpgradedConnections is null)
        {
            writer.WriteNullValue();
        }
        else
        {
            writer.WriteNumberValue(MaxConcurrentUpgradedConnections.Value);
        }
 
        writer.WritePropertyName(nameof(MaxRequestBodySize));
        if (MaxRequestBodySize is null)
        {
            writer.WriteNullValue();
        }
        else
        {
            writer.WriteNumberValue(MaxRequestBodySize.Value);
        }
 
        writer.WritePropertyName(nameof(MaxRequestBufferSize));
        if (MaxRequestBufferSize is null)
        {
            writer.WriteNullValue();
        }
        else
        {
            writer.WriteNumberValue(MaxRequestBufferSize.Value);
        }
 
        writer.WritePropertyName(nameof(MaxRequestHeaderCount));
        writer.WriteNumberValue(MaxRequestHeaderCount);
 
        writer.WritePropertyName(nameof(MaxRequestHeadersTotalSize));
        writer.WriteNumberValue(MaxRequestHeadersTotalSize);
 
        writer.WritePropertyName(nameof(MaxRequestLineSize));
        writer.WriteNumberValue(MaxRequestLineSize);
 
        writer.WritePropertyName(nameof(MaxResponseBufferSize));
        if (MaxResponseBufferSize is null)
        {
            writer.WriteNullValue();
        }
        else
        {
            writer.WriteNumberValue(MaxResponseBufferSize.Value);
        }
 
        writer.WriteString(nameof(MinRequestBodyDataRate), MinRequestBodyDataRate?.ToString());
        writer.WriteString(nameof(MinResponseDataRate), MinResponseDataRate?.ToString());
        writer.WriteString(nameof(RequestHeadersTimeout), RequestHeadersTimeout.ToString());
 
        // HTTP2
        writer.WritePropertyName(nameof(Http2));
        writer.WriteStartObject();
        Http2.Serialize(writer);
        writer.WriteEndObject();
 
        // HTTP3
        writer.WritePropertyName(nameof(Http3));
        writer.WriteStartObject();
        Http3.Serialize(writer);
        writer.WriteEndObject();
    }
 
    /// <summary>
    /// Limits only applicable to HTTP/2 connections.
    /// </summary>
    public Http2Limits Http2 { get; } = new Http2Limits();
 
    /// <summary>
    /// Limits only applicable to HTTP/3 connections.
    /// </summary>
    public Http3Limits Http3 { get; } = new Http3Limits();
 
    /// <summary>
    /// Gets or sets the request body minimum data rate in bytes/second.
    /// Setting this property to null indicates no minimum data rate should be enforced.
    /// This limit has no effect on upgraded connections which are always unlimited.
    /// This can be overridden per-request via <see cref="IHttpMinRequestBodyDataRateFeature"/>.
    /// Defaults to 240 bytes/second with a 5 second grace period.
    /// </summary>
    /// <remarks>
    /// </remarks>
    public MinDataRate? MinRequestBodyDataRate { get; set; } =
        // Matches the default IIS minBytesPerSecond
        new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(5));
 
    /// <summary>
    /// Gets or sets the response minimum data rate in bytes/second.
    /// Setting this property to null indicates no minimum data rate should be enforced.
    /// This limit has no effect on upgraded connections which are always unlimited.
    /// This can be overridden per-request via <see cref="IHttpMinResponseDataRateFeature"/>.
    /// <para>
    /// Defaults to 240 bytes/second with a 5 second grace period.
    /// </para>
    /// </summary>
    /// <remarks>
    /// <para>
    /// Contrary to the request body minimum data rate, this rate applies to the response status line and headers as well.
    /// </para>
    /// <para>
    /// This rate is enforced per write operation instead of being averaged over the life of the response. Whenever the server
    /// writes a chunk of data, a timer is set to the maximum of the grace period set in this property or the length of the write in
    /// bytes divided by the data rate (i.e. the maximum amount of time that write should take to complete with the specified data rate).
    /// The connection is aborted if the write has not completed by the time that timer expires.
    /// </para>
    /// </remarks>
    public MinDataRate? MinResponseDataRate { get; set; } =
        // Matches the default IIS minBytesPerSecond
        new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(5));
}