File: Http2Limits.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.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Core;
 
/// <summary>
/// Limits only applicable to HTTP/2 connections.
/// </summary>
public class Http2Limits
{
    private int _maxStreamsPerConnection = 100;
    private int _headerTableSize = (int)Http2PeerSettings.DefaultHeaderTableSize;
    private int _maxFrameSize = (int)Http2PeerSettings.DefaultMaxFrameSize;
    private int _maxRequestHeaderFieldSize = 32 * 1024; // Matches MaxRequestHeadersTotalSize
    private int _initialConnectionWindowSize = 1024 * 1024; // Equal to SocketTransportOptions.MaxReadBufferSize and larger than any one single stream.
    private int _initialStreamWindowSize = 768 * 1024; // Larger than the default 64kb and able to use most (3/4ths) of the connection window by itself.
    private TimeSpan _keepAlivePingDelay = TimeSpan.MaxValue;
    private TimeSpan _keepAlivePingTimeout = TimeSpan.FromSeconds(20);
 
    /// <summary>
    /// Limits the number of concurrent request streams per HTTP/2 connection. Excess streams will be refused.
    /// <para>
    /// Value must be greater than 0, defaults to 100 streams.
    /// </para>
    /// </summary>
    public int MaxStreamsPerConnection
    {
        get => _maxStreamsPerConnection;
        set
        {
            if (value <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired);
            }
 
            _maxStreamsPerConnection = value;
        }
    }
 
    /// <summary>
    /// Limits the size of the header compression tables, in octets, the HPACK encoder and decoder on the server can use.
    /// <para>
    /// Value must be greater than or equal to 0, defaults to 4096 octets (4 KiB).
    /// </para>
    /// </summary>
    public int HeaderTableSize
    {
        get => _headerTableSize;
        set
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanOrEqualToZeroRequired);
            }
 
            _headerTableSize = value;
        }
    }
 
    /// <summary>
    /// Indicates the size of the largest frame payload that is allowed to be received, in octets. The size must be between 2^14 and 2^24-1.
    /// <para>
    /// Value must be between 2^14 and 2^24, defaults to 2^14 octets (16 KiB).
    /// </para>
    /// </summary>
    public int MaxFrameSize
    {
        get => _maxFrameSize;
        set
        {
            if (value < Http2PeerSettings.MinAllowedMaxFrameSize || value > Http2PeerSettings.MaxAllowedMaxFrameSize)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.FormatArgumentOutOfRange(Http2PeerSettings.MinAllowedMaxFrameSize, Http2PeerSettings.MaxAllowedMaxFrameSize));
            }
 
            _maxFrameSize = value;
        }
    }
 
    /// <summary>
    /// Indicates the size of the maximum allowed size of a request header field sequence, in octets. This limit applies to both name and value sequences in their compressed and uncompressed representations.
    /// <para>
    /// Value must be greater than 0, defaults to 2^14 octets (16 KiB).
    /// </para>
    /// </summary>
    public int MaxRequestHeaderFieldSize
    {
        get => _maxRequestHeaderFieldSize;
        set
        {
            if (value <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired);
            }
 
            _maxRequestHeaderFieldSize = value;
        }
    }
 
    /// <summary>
    /// Indicates how much request body data, in bytes, the server is willing to receive and buffer at a time aggregated across all
    /// requests (streams) per connection. Note requests are also limited by <see cref="InitialStreamWindowSize"/>
    /// <para>
    /// Value must be greater than or equal to 64 KiB and less than 2 GiB, defaults to 1 MiB.
    /// </para>
    /// </summary>
    public int InitialConnectionWindowSize
    {
        get => _initialConnectionWindowSize;
        set
        {
            if (value < Http2PeerSettings.DefaultInitialWindowSize || value > Http2PeerSettings.MaxWindowSize)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value,
                    CoreStrings.FormatArgumentOutOfRange(Http2PeerSettings.DefaultInitialWindowSize, Http2PeerSettings.MaxWindowSize));
            }
 
            _initialConnectionWindowSize = value;
        }
    }
 
    /// <summary>
    /// Indicates how much request body data, in bytes, the server is willing to receive and buffer at a time per stream.
    /// Note connections are also limited by <see cref="InitialConnectionWindowSize"/>. There must be space in both the stream
    /// window and connection window for a client to upload request body data.
    /// <para>
    /// Value must be greater than or equal to 64 KiB and less than 2 GiB, defaults to 768 KiB.
    /// </para>
    /// </summary>
    public int InitialStreamWindowSize
    {
        get => _initialStreamWindowSize;
        set
        {
            if (value < Http2PeerSettings.DefaultInitialWindowSize || value > Http2PeerSettings.MaxWindowSize)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value,
                    CoreStrings.FormatArgumentOutOfRange(Http2PeerSettings.DefaultInitialWindowSize, Http2PeerSettings.MaxWindowSize));
            }
 
            _initialStreamWindowSize = value;
        }
    }
 
    /// <summary>
    /// Gets or sets the keep alive ping delay. The server will send a keep alive ping to the client if it
    /// doesn't receive any frames on a connection for this period of time. This property is used together with
    /// <see cref="KeepAlivePingTimeout"/> to close broken connections.
    /// <para>
    /// Delay value must be greater than or equal to 1 second. Set to <see cref="TimeSpan.MaxValue"/> to
    /// disable the keep alive ping.
    /// Defaults to <see cref="TimeSpan.MaxValue"/>.
    /// </para>
    /// </summary>
    public TimeSpan KeepAlivePingDelay
    {
        get => _keepAlivePingDelay;
        set
        {
            // Keep alive uses Kestrel's system clock which has a 1 second resolution. Time is greater or equal to clock resolution.
            if (value < Heartbeat.Interval && value != Timeout.InfiniteTimeSpan)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.FormatArgumentTimeSpanGreaterOrEqual(Heartbeat.Interval));
            }
 
            _keepAlivePingDelay = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
        }
    }
 
    /// <summary>
    /// Gets or sets the keep alive ping timeout. Keep alive pings are sent when a period of inactivity exceeds
    /// the configured <see cref="KeepAlivePingDelay"/> value. The server will close the connection if it
    /// doesn't receive any frames within the timeout.
    /// <para>
    /// Timeout must be greater than or equal to 1 second. Set to <see cref="TimeSpan.MaxValue"/> to
    /// disable the keep alive ping timeout.
    /// Defaults to 20 seconds.
    /// </para>
    /// </summary>
    public TimeSpan KeepAlivePingTimeout
    {
        get => _keepAlivePingTimeout;
        set
        {
            // Keep alive uses Kestrel's system clock which has a 1 second resolution. Time is greater or equal to clock resolution.
            if (value < Heartbeat.Interval && value != Timeout.InfiniteTimeSpan)
            {
                throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.FormatArgumentTimeSpanGreaterOrEqual(Heartbeat.Interval));
            }
 
            _keepAlivePingTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
        }
    }
 
    internal void Serialize(Utf8JsonWriter writer)
    {
        writer.WritePropertyName(nameof(MaxStreamsPerConnection));
        writer.WriteNumberValue(MaxStreamsPerConnection);
 
        writer.WritePropertyName(nameof(HeaderTableSize));
        writer.WriteNumberValue(HeaderTableSize);
 
        writer.WritePropertyName(nameof(MaxFrameSize));
        writer.WriteNumberValue(MaxFrameSize);
 
        writer.WritePropertyName(nameof(MaxRequestHeaderFieldSize));
        writer.WriteNumberValue(MaxRequestHeaderFieldSize);
 
        writer.WritePropertyName(nameof(InitialConnectionWindowSize));
        writer.WriteNumberValue(InitialConnectionWindowSize);
 
        writer.WritePropertyName(nameof(InitialStreamWindowSize));
        writer.WriteNumberValue(InitialStreamWindowSize);
 
        writer.WriteString(nameof(KeepAlivePingDelay), KeepAlivePingDelay.ToString());
        writer.WriteString(nameof(KeepAlivePingTimeout), KeepAlivePingTimeout.ToString());
    }
}