diff --git a/docs/api/client-options.md b/docs/api/client-options.md index a23b342c..6375113b 100644 --- a/docs/api/client-options.md +++ b/docs/api/client-options.md @@ -11,9 +11,8 @@ public sealed class TurboClientOptions public Http2ClientOptions Http2 { get; init; } = new(); // HTTP/2 settings public Http3ClientOptions Http3 { get; init; } = new(); // HTTP/3 settings - // Body buffering - public long? MaxStreamedResponseBodySize { get; set; } // null = unlimited; cap on a streamed response body - public int ResponseBodyBufferThreshold { get; set; } = 64 * 1024; // 64 KB; bodies below this are buffered in memory, at/above streamed + // Body buffering (response buffering threshold lives on Http1.MaxBufferedResponseBodySize) + public long? MaxStreamedResponseBodySize { get; set; } // null = unlimited; cap on a streamed response body public int RequestBodyChunkSize { get; set; } = 16 * 1024; // 16 KB; chunk size when streaming a request body // Connection pool @@ -28,9 +27,11 @@ public sealed class TurboClientOptions public X509CertificateCollection? ClientCertificates { get; set; } public SslProtocols EnabledSslProtocols { get; set; } = SslProtocols.None; - // Socket options + // Socket and buffer options public int? SocketSendBufferSize { get; set; } public int? SocketReceiveBufferSize { get; set; } + public int ReceiveBufferHint { get; set; } = 64 * 1024; // 64 KB; internal receive buffer size hint + public int MinimumSegmentSize { get; set; } = 16 * 1024; // 16 KB; minimum segment size of the internal buffer pool // Proxy public bool UseProxy { get; set; } = true; @@ -70,6 +71,7 @@ See [Connection Pooling guide](/client/connection-pooling) for pool lifecycle de ```csharp public sealed class Http1ClientOptions { + public int MaxBufferedResponseBodySize { get; set; } = 64 * 1024; // 64 KB; bodies up to this size are buffered in memory, larger are streamed public int MaxConnectionsPerServer { get; set; } = 6; public int MaxPipelineDepth { get; set; } = 16; public int MaxResponseHeadersLength { get; set; } = 64; // KB @@ -84,6 +86,7 @@ public sealed class Http1ClientOptions | Property | Default | Description | |----------|---------|-------------| +| `MaxBufferedResponseBodySize` | `64 * 1024` (64 KB) | Response bodies up to this size are buffered fully in memory; larger bodies are exposed as a streaming pipe | | `MaxConnectionsPerServer` | `6` | Max concurrent TCP connections per host | | `MaxPipelineDepth` | `16` | Max pipelined requests per connection | | `MaxResponseHeadersLength` | `64` (KB) | Max total response header block size | @@ -102,14 +105,17 @@ public sealed class Http2ClientOptions public int MaxConnectionsPerServer { get; set; } = 6; public int MaxConcurrentStreams { get; set; } = 100; public int InitialConnectionWindowSize { get; set; } = 64 * 1024 * 1024; // 64 MB - public int InitialStreamWindowSize { get; set; } = 65535; + public int InitialStreamWindowSize { get; set; } = 1 * 1024 * 1024; // 1 MB public int MaxStreamWindowSize { get; set; } = 16 * 1024 * 1024; // 16 MB public double WindowScaleThresholdMultiplier { get; set; } = 1.0; public bool EnableAdaptiveWindowScaling { get; set; } = true; public int MaxFrameSize { get; set; } = 64 * 1024; // 64 KB public int HeaderTableSize { get; set; } = 64 * 1024; // 64 KB public int MaxResponseHeaderListSize { get; set; } = 64 * 1024; // 64 KB; max total size of response header list + public long MaxBufferedRequestBodySize { get; set; } = 64 * 1024; // 64 KB; bodies up to this size are serialized inline, larger are streamed + public long MaxRequestBodyBufferSize { get; set; } = 64 * 1024; // 64 KB; outbound body bytes buffered per stream before the encoder pauses public int MaxReconnectAttempts { get; set; } = 3; + public int MaxReconnectBufferSize { get; set; } = 64; // max requests buffered during reconnection public TimeSpan KeepAlivePingDelay { get; set; } = Timeout.InfiniteTimeSpan; public TimeSpan KeepAlivePingTimeout { get; set; } = TimeSpan.FromSeconds(20); public HttpKeepAlivePingPolicy KeepAlivePingPolicy { get; set; } = HttpKeepAlivePingPolicy.Always; @@ -121,14 +127,17 @@ public sealed class Http2ClientOptions | `MaxConnectionsPerServer` | `6` | Max concurrent TCP connections per host | | `MaxConcurrentStreams` | `100` | Max concurrent streams per connection | | `InitialConnectionWindowSize` | `64 * 1024 * 1024` (64 MB) | Connection-level flow control window | -| `InitialStreamWindowSize` | `65535` | Initial per-stream flow control window | +| `InitialStreamWindowSize` | `1 * 1024 * 1024` (1 MB) | Initial per-stream flow control window; grows up to `MaxStreamWindowSize` under adaptive scaling | | `MaxStreamWindowSize` | `16 * 1024 * 1024` (16 MB) | Maximum per-stream flow control window | | `WindowScaleThresholdMultiplier` | `1.0` | RTT multiplier controlling when to scale the stream window | | `EnableAdaptiveWindowScaling` | `true` | Grow the stream receive window based on observed throughput | | `MaxFrameSize` | `64 * 1024` (64 KB) | Max frame payload size | | `HeaderTableSize` | `64 * 1024` (64 KB) | HPACK dynamic table size | | `MaxResponseHeaderListSize` | `64 * 1024` (64 KB) | Max total size of the response header list | +| `MaxBufferedRequestBodySize` | `64 * 1024` (64 KB) | Request bodies up to this size are serialized inline; larger bodies are streamed in chunks with backpressure | +| `MaxRequestBodyBufferSize` | `64 * 1024` (64 KB) | Max outbound body bytes buffered per stream before the body encoder pauses | | `MaxReconnectAttempts` | `3` | Max reconnect attempts on connection drop | +| `MaxReconnectBufferSize` | `64` | Max requests buffered during reconnection | | `KeepAlivePingDelay` | `infinite` | Delay before sending keep-alive PING | | `KeepAlivePingTimeout` | `20 s` | Timeout for PING acknowledgment | | `KeepAlivePingPolicy` | `Always` | When to send keep-alive PINGs | @@ -196,6 +205,8 @@ options.ClientCertificates = new X509CertificateCollection |----------|---------|-------------| | `SocketSendBufferSize` | `null` (system default) | OS socket send buffer size in bytes | | `SocketReceiveBufferSize` | `null` (system default) | OS socket receive buffer size in bytes | +| `ReceiveBufferHint` | `64 * 1024` (64 KB) | Size hint for the internal receive buffer; larger values reduce read syscalls at the cost of memory | +| `MinimumSegmentSize` | `16 * 1024` (16 KB) | Minimum segment size of the internal buffer pool | ## Proxy Options @@ -216,7 +227,7 @@ options.ClientCertificates = new X509CertificateCollection | Property | Default | Description | |----------|---------|-------------| -| `ResponseBodyBufferThreshold` | `64 * 1024` (64 KB) | Response bodies below this threshold are buffered fully in memory; at or above it the body is streamed. Shared across all protocol versions. | +| `Http1.MaxBufferedResponseBodySize` | `64 * 1024` (64 KB) | HTTP/1.x response bodies up to this size are buffered fully in memory; larger bodies are exposed as a streaming pipe | | `MaxStreamedResponseBodySize` | `null` (unlimited) | Cap on a streamed response body; `null` means no limit | | `RequestBodyChunkSize` | `16 * 1024` (16 KB) | Chunk size used when streaming a request body | diff --git a/docs/api/client.md b/docs/api/client.md index 404c1763..f6f70dcd 100644 --- a/docs/api/client.md +++ b/docs/api/client.md @@ -85,7 +85,7 @@ See [HTTP/2 & Multiplexing guide](/client/http2) for multiplexing details. ### Timeout -Per-request timeout applied by `SendAsync`. Defaults to 60 seconds. Does not affect the channel-based API: +Per-request timeout. Defaults to 60 seconds. `SendAsync` enforces it directly; requests submitted via the channel-based API get the same timeout injected as a default when no cancellation token is set on the request: ```csharp client.Timeout = TimeSpan.FromSeconds(30); @@ -114,7 +114,7 @@ Requests are matched to responses in submission order (HTTP/1.x) or by stream ID ### CancelPendingRequests -Cancels all in-flight `SendAsync` calls and clears the pending request map. Does not affect the channel-based API: +Cancels all in-flight `SendAsync` calls, clears the pending request map, and drains (disposes) any responses already buffered in the `Responses` channel: ```csharp // Cancel everything in-flight (e.g., on application shutdown) diff --git a/docs/api/feature-options.md b/docs/api/feature-options.md index 7bb53ce1..b75c683e 100644 --- a/docs/api/feature-options.md +++ b/docs/api/feature-options.md @@ -36,7 +36,8 @@ See [Automatic Retries guide](/client/retries) for which methods and status code public sealed class CacheOptions { public int MaxEntries { get; set; } = 1000; - public long MaxBodySize { get; set; } = 50 * 1024 * 1024; // 50 MiB + public long MaxBodySize { get; set; } = 50 * 1024 * 1024; // 50 MiB + public long MaxTotalSize { get; set; } = 256 * 1024 * 1024; // 256 MiB public bool SharedCache { get; set; } } ``` @@ -44,7 +45,8 @@ public sealed class CacheOptions | Property | Default | Description | |----------|---------|-------------| | `MaxEntries` | `1000` | Max number of responses in the cache | -| `MaxBodySize` | `50 * 1024 * 1024` (50 MiB) | Max total size of cached response bodies | +| `MaxBodySize` | `50 * 1024 * 1024` (50 MiB) | Max body size of a single stored response; larger responses are not cached | +| `MaxTotalSize` | `256 * 1024 * 1024` (256 MiB) | Max total size of all cached response bodies combined; least-recently-used entries are evicted when exceeded | | `SharedCache` | `false` | Whether this is a shared cache (affecting `Cache-Control` directives) | ```csharp diff --git a/docs/api/server.md b/docs/api/server.md index 879befbf..21bda9aa 100644 --- a/docs/api/server.md +++ b/docs/api/server.md @@ -60,9 +60,10 @@ public sealed class TurboServerOptions TimeSpan HandlerTimeout { get; set; } // default: 30s TimeSpan HandlerGracePeriod { get; set; } // default: 5s - int RequestBodyBufferThreshold { get; set; } // default: 64 * 1024 TimeSpan BodyConsumptionTimeout { get; set; } // default: 30s int ResponseBodyChunkSize { get; set; } // default: 16 * 1024 + int MaxOutboundCoalesceCount { get; set; } // default: 32 (frames merged up to factor × 16 KiB per transport write) + bool AllowResponseHeaderCompression { get; set; } // default: true (disable to mitigate CRIME/BREACH-style attacks) Http1ServerOptions Http1 { get; } Http2ServerOptions Http2 { get; } @@ -96,11 +97,11 @@ public sealed class TurboServerOptions public sealed class TurboServerLimits { int MaxConcurrentConnections { get; set; } // default: 0 (unlimited) - int MaxConcurrentRequests { get; set; } // default: 0 (unlimited) - int MinRequestGuarantee { get; set; } // default: 10 - long MaxRequestBodySize { get; set; } // default: 30 * 1024 * 1024 + long MaxRequestBodySize { get; set; } // default: 30,000,000 (~28.6 MiB, matching Kestrel) int MaxRequestHeaderCount { get; set; } // default: 100 int MaxRequestHeadersTotalSize { get; set; } // default: 32 * 1024 + long MaxResponseBufferSize { get; set; } // default: 64 * 1024 (per-stream response write buffer) + long? MaxRequestBufferSize { get; set; } // default: 1 MiB (transport input buffer before backpressure; null = unlimited) int MaxResetStreamsPerWindow { get; set; } // default: 200 (HTTP/2 Rapid Reset / CVE-2023-44487 mitigation; 0 = disabled) TimeSpan KeepAliveTimeout { get; set; } // default: 130s TimeSpan RequestHeadersTimeout { get; set; } // default: 30s @@ -121,6 +122,7 @@ public sealed class TurboListenOptions(IPAddress address, ushort port) IPAddress Address { get; } ushort Port { get; } HttpProtocols Protocols { get; set; } // default: Http1AndHttp2 + TransportBufferOptions? Transport { get; set; } // default: null (protocol-optimized defaults) void UseHttps(); void UseHttps(X509Certificate2 certificate); @@ -135,6 +137,47 @@ public sealed class TurboListenOptions(IPAddress address, ushort port) --- +## Transport Buffer Options + +Controls backpressure thresholds on the read/write pipes between the OS socket and the HTTP pipeline. Applied per-connection for TCP and per-stream for QUIC. Set via `TurboListenOptions.Transport`; leaving it `null` uses protocol-optimized defaults. Assigning an instance replaces the protocol defaults entirely (no per-property fallback), so set `InputPauseThreshold` and `InputResumeThreshold` explicitly — they have no initializer. + +```csharp +public sealed class TransportBufferOptions +{ + long InputPauseThreshold { get; set; } // bytes buffered on the read pipe before the OS socket is paused + long InputResumeThreshold { get; set; } // buffered byte count at which reading resumes (must be < pause threshold) + long OutputPauseThreshold { get; set; } // default: 64 * 1024 — bytes buffered on the write pipe before the HTTP pipeline is paused + long OutputResumeThreshold { get; set; } // default: 32 * 1024 + int MinimumSegmentSize { get; set; } // minimum pipe buffer segment size +} +``` + +Protocol-specific defaults when `Transport` is `null`: + +| Property | TCP (one pipe per connection) | QUIC (one pipe per stream) | +|----------|------------------------------|----------------------------| +| `InputPauseThreshold` | 1 MiB | 64 KiB | +| `InputResumeThreshold` | 512 KiB | 32 KiB | +| `OutputPauseThreshold` | 64 KiB | 64 KiB | +| `OutputResumeThreshold` | 32 KiB | 32 KiB | +| `MinimumSegmentSize` | 16 KiB | 4 KiB | + +```csharp +options.Listen(IPAddress.Any, 8080, listen => +{ + listen.Transport = new TransportBufferOptions + { + InputPauseThreshold = 2 * 1024 * 1024, + InputResumeThreshold = 1024 * 1024, + OutputPauseThreshold = 64 * 1024, + OutputResumeThreshold = 32 * 1024, + MinimumSegmentSize = 16 * 1024 + }; +}); +``` + +--- + ## HTTPS Options ```csharp @@ -178,6 +221,7 @@ public sealed class Http1ServerOptions int MaxRequestTargetLength { get; set; } // default: 8 * 1024 int MaxPipelinedRequests { get; set; } // default: 16 int MaxChunkExtensionLength { get; set; } // default: 4 * 1024 + int MaxBufferedRequestBodySize { get; set; } // default: 64 * 1024 (bodies up to this size buffered in memory, larger streamed) TimeSpan BodyReadTimeout { get; set; } // default: 30s int? MaxHeaderListSize { get; set; } // default: null (uses Limits.MaxRequestHeadersTotalSize) long? MaxRequestBodySize { get; set; } // default: null (uses Limits) @@ -200,10 +244,15 @@ public sealed class Http2ServerOptions int MaxConcurrentStreams { get; set; } // default: 100 int InitialConnectionWindowSize { get; set; } // default: 1 * 1024 * 1024 int InitialStreamWindowSize { get; set; } // default: 768 * 1024 + int MaxStreamWindowSize { get; set; } // default: 8 * 1024 * 1024 (adaptive scaling upper bound) + double WindowScaleThresholdMultiplier { get; set; } // default: 1.0 + bool EnableAdaptiveWindowScaling { get; set; } // default: true (BDP-based receive-window growth) int MaxFrameSize { get; set; } // default: 16 * 1024 int HeaderTableSize { get; set; } // default: 4 * 1024 int? MaxHeaderListSize { get; set; } // default: null (uses Limits.MaxRequestHeadersTotalSize) - long MaxResponseBufferSize { get; set; } // default: 64 * 1024 + long? MaxResponseBufferSize { get; set; } // default: null (uses Limits.MaxResponseBufferSize) + TimeSpan KeepAlivePingDelay { get; set; } // default: infinite (server-initiated keep-alive PINGs disabled) + TimeSpan KeepAlivePingTimeout { get; set; } // default: 20s (max wait for PING ACK before closing) long? MaxRequestBodySize { get; set; } // default: null (uses Limits) TimeSpan? KeepAliveTimeout { get; set; } // default: null (uses Limits) TimeSpan? RequestHeadersTimeout { get; set; } // default: null (uses Limits) @@ -225,6 +274,7 @@ public sealed class Http3ServerOptions int? MaxHeaderListSize { get; set; } // default: null (uses Limits.MaxRequestHeadersTotalSize) int QpackMaxTableCapacity { get; set; } // default: 0 int QpackBlockedStreams { get; set; } // default: 100 + long? MaxResponseBufferSize { get; set; } // default: null (uses Limits.MaxResponseBufferSize) long? MaxRequestBodySize { get; set; } // default: null (uses Limits) TimeSpan? KeepAliveTimeout { get; set; } // default: null (uses Limits) TimeSpan? RequestHeadersTimeout { get; set; } // default: null (uses Limits) diff --git a/docs/client/configuration.md b/docs/client/configuration.md index a5befb78..ebaf5926 100644 --- a/docs/client/configuration.md +++ b/docs/client/configuration.md @@ -88,7 +88,7 @@ options.PooledConnectionLifetime = TimeSpan.FromMinutes(10); | Property | Type | Default | Description | | ------------------------------- | ------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `ResponseBodyBufferThreshold` | `int` | `64 * 1024` (64 KB) | Response bodies below this size are buffered fully in memory; at or above it the body is streamed (shared across all protocol versions) | +| `Http1.MaxBufferedResponseBodySize` | `int` | `64 * 1024` (64 KB) | HTTP/1.x response bodies up to this size are buffered fully in memory; larger bodies are exposed as a streaming pipe | | `MaxStreamedResponseBodySize` | `long?` | `null` | Cap on a streamed response body; `null` = unlimited | | `RequestBodyChunkSize` | `int` | `16 * 1024` (16 KB) | Chunk size used when streaming a request body to the server | @@ -120,7 +120,7 @@ options.Http1.MaxPipelineDepth = 32; | `Http2.MaxConnectionsPerServer` | `int` | `6` | Maximum concurrent HTTP/2 connections per host | | `Http2.MaxConcurrentStreams` | `int` | `100` | Maximum concurrent streams per connection | | `Http2.InitialConnectionWindowSize` | `int` | `64 * 1024 * 1024` (64 MiB) | Initial flow-control window for the whole connection | -| `Http2.InitialStreamWindowSize` | `int` | `65535` | Initial flow-control window per stream | +| `Http2.InitialStreamWindowSize` | `int` | `1 * 1024 * 1024` (1 MiB) | Initial flow-control window per stream (grows up to `MaxStreamWindowSize`) | | `Http2.MaxStreamWindowSize` | `int` | `16 * 1024 * 1024` (16 MiB) | Upper bound for adaptive stream window growth | | `Http2.WindowScaleThresholdMultiplier` | `double` | `1.0` | RTT multiplier that triggers a window-size increase | | `Http2.EnableAdaptiveWindowScaling` | `bool` | `true` | Automatically grow receive windows based on measured RTT | diff --git a/docs/server/configuration.md b/docs/server/configuration.md index 05176518..c87559f0 100644 --- a/docs/server/configuration.md +++ b/docs/server/configuration.md @@ -16,9 +16,10 @@ builder.Host.UseTurboHttp(options => | `HandlerTimeout` | `TimeSpan` | 30s | Maximum time for a request handler to complete | | `HandlerGracePeriod` | `TimeSpan` | 5s | Extra time after handler timeout before force-closing | | `GracefulShutdownTimeout` | `TimeSpan` | 30s | Time to drain connections during shutdown | -| `RequestBodyBufferThreshold` | `int` | 64 * 1024 | Request body buffer size before streaming | | `BodyConsumptionTimeout` | `TimeSpan` | 30s | Time for the app to consume the request body | | `ResponseBodyChunkSize` | `int` | 16 * 1024 | Chunk size for response body writes | +| `MaxOutboundCoalesceCount` | `int` | 32 | Coalesce factor for outbound writes — frames are merged up to factor × 16 KiB per transport write | +| `AllowResponseHeaderCompression` | `bool` | true | Whether response headers may use Huffman compression (HPACK/QPACK); disable to mitigate CRIME/BREACH-style attacks | ## Connection Limits @@ -27,9 +28,9 @@ Access via `options.Limits`. | Property | Type | Default | Description | |----------|------|---------|-------------| | `MaxConcurrentConnections` | `int` | 0 (unlimited) | Maximum concurrent connections | -| `MaxConcurrentRequests` | `int` | 0 (unlimited) | Maximum concurrent in-flight requests across all connections | -| `MinRequestGuarantee` | `int` | 10 | Minimum requests admitted even when the concurrency cap is reached | -| `MaxRequestBodySize` | `long` | 30 * 1024 * 1024 | Global max request body size | +| `MaxRequestBodySize` | `long` | 30,000,000 (~28.6 MiB) | Global max request body size (matches Kestrel) | +| `MaxResponseBufferSize` | `long` | 64 * 1024 | Maximum per-stream response write buffer | +| `MaxRequestBufferSize` | `long?` | 1 MiB | Transport input buffer before backpressure is applied (`null` = unlimited) | | `MaxRequestHeaderCount` | `int` | 100 | Maximum request headers | | `MaxRequestHeadersTotalSize` | `int` | 32 * 1024 | Maximum total header bytes | | `MaxResetStreamsPerWindow` | `int` | 200 | Maximum HTTP/2 stream resets tolerated in a sliding window before the connection is closed (Rapid Reset / CVE-2023-44487 mitigation). Set to 0 to disable. | @@ -50,6 +51,7 @@ Access via `options.Http1`. | `MaxRequestTargetLength` | `int` | 8192 | Maximum bytes for the request target (URL) | | `MaxPipelinedRequests` | `int` | 16 | Maximum queued pipelined requests | | `MaxChunkExtensionLength` | `int` | 4096 | Maximum bytes for chunk extensions | +| `MaxBufferedRequestBodySize` | `int` | 64 * 1024 | Request bodies up to this size are buffered fully in memory; larger bodies are exposed as a streaming pipe | | `BodyReadTimeout` | `TimeSpan` | 30s | Timeout for reading request body | | `MaxHeaderListSize` | `int?` | null (uses global) | Max total header bytes (null = uses `Limits.MaxRequestHeadersTotalSize`) | | `MaxRequestBodySize` | `long?` | null (uses global) | HTTP/1.x-specific body size limit | @@ -68,13 +70,18 @@ Access via `options.Http2`. |----------|------|---------|-------------| | `MaxConcurrentStreams` | `int` | 100 | Maximum concurrent streams per connection | | `InitialConnectionWindowSize` | `int` | 1 * 1024 * 1024 | Connection-level flow control window | -| `InitialStreamWindowSize` | `int` | 768 * 1024 | Per-stream flow control window | +| `InitialStreamWindowSize` | `int` | 768 * 1024 | Per-stream flow control window (starting point for adaptive scaling) | +| `MaxStreamWindowSize` | `int` | 8 * 1024 * 1024 | Upper bound for adaptive per-stream window growth | +| `WindowScaleThresholdMultiplier` | `double` | 1.0 | Threshold multiplier for adaptive window growth; higher values grow less eagerly | +| `EnableAdaptiveWindowScaling` | `bool` | true | Grow the per-stream receive window based on measured throughput and RTT | | `MaxFrameSize` | `int` | 16 * 1024 | Maximum HTTP/2 frame payload size | | `MaxHeaderListSize` | `int?` | null (uses global) | Max total header bytes (null = uses `Limits.MaxRequestHeadersTotalSize`) | | `HeaderTableSize` | `int` | 4 * 1024 | HPACK dynamic table size | -| `MaxResponseBufferSize` | `long` | 64 * 1024 | Response buffering before backpressure | +| `MaxResponseBufferSize` | `long?` | null (uses global) | Response buffering before backpressure (null = uses `Limits.MaxResponseBufferSize`) | | `MaxRequestBodySize` | `long?` | null (uses global) | HTTP/2-specific body size limit | | `KeepAliveTimeout` | `TimeSpan?` | null (uses global) | Connection idle timeout | +| `KeepAlivePingDelay` | `TimeSpan` | infinite (disabled) | Idle time after the last received frame before the server sends a keep-alive PING | +| `KeepAlivePingTimeout` | `TimeSpan` | 20s | Max wait for a PING ACK before the connection is closed | | `RequestHeadersTimeout` | `TimeSpan?` | null (uses global) | Time to receive request headers | | `MinRequestBodyDataRate` | `double?` | null (uses global) | Minimum body bytes/sec | | `MinRequestBodyDataRateGracePeriod` | `TimeSpan?` | null (uses global) | Grace period before enforcing body rate | @@ -91,6 +98,7 @@ Access via `options.Http3`. | `MaxHeaderListSize` | `int?` | null (uses global) | Max total header bytes (null = uses `Limits.MaxRequestHeadersTotalSize`) | | `QpackMaxTableCapacity` | `int` | 0 | QPACK dynamic table capacity (0 = static only) | | `QpackBlockedStreams` | `int` | 100 | Maximum concurrent QPACK-blocked streams | +| `MaxResponseBufferSize` | `long?` | null (uses global) | Per-stream response write buffer (null = uses `Limits.MaxResponseBufferSize`) | | `MaxRequestBodySize` | `long?` | null (uses global) | HTTP/3-specific body size limit | | `KeepAliveTimeout` | `TimeSpan?` | null (uses global) | Connection idle timeout | | `RequestHeadersTimeout` | `TimeSpan?` | null (uses global) | Time to receive request headers | @@ -99,6 +107,35 @@ Access via `options.Http3`. | `MinResponseDataRate` | `double?` | null (uses global) | Minimum response bytes/sec | | `MinResponseDataRateGracePeriod` | `TimeSpan?` | null (uses global) | Grace period before enforcing response rate | +## Transport Buffers + +Per-endpoint backpressure thresholds for the pipes between the OS socket and the HTTP pipeline. Set via `TurboListenOptions.Transport`; when left `null`, protocol-optimized defaults apply (TCP buffers one pipe per connection, QUIC one pipe per stream). + +| Property | Type | TCP Default | QUIC Default | Description | +|----------|------|-------------|--------------|-------------| +| `InputPauseThreshold` | `long` | 1 MiB | 64 KiB | Bytes buffered on the read pipe before the OS socket is paused | +| `InputResumeThreshold` | `long` | 512 KiB | 32 KiB | Buffered byte count at which reading resumes | +| `OutputPauseThreshold` | `long` | 64 KiB | 64 KiB | Bytes buffered on the write pipe before the HTTP pipeline is paused | +| `OutputResumeThreshold` | `long` | 32 KiB | 32 KiB | Buffered byte count at which writing resumes | +| `MinimumSegmentSize` | `int` | 16 KiB | 4 KiB | Minimum pipe buffer segment size | + +```csharp +options.Listen(IPAddress.Any, 8080, listen => +{ + listen.Transport = new TransportBufferOptions + { + InputPauseThreshold = 2 * 1024 * 1024, + InputResumeThreshold = 1024 * 1024 + }; +}); +``` + +::: warning +Assigning `Transport` replaces the protocol defaults entirely — there is no per-property fallback. `InputPauseThreshold` and `InputResumeThreshold` have no initializer, so always set both explicitly; the output thresholds and `MinimumSegmentSize` fall back to the class initializers (64 KiB / 32 KiB / 16 KiB) if omitted. +::: + +See the [Server API reference](/api/server#transport-buffer-options) for details. + ## Example: Full Configuration ```csharp diff --git a/docs/server/hosting.md b/docs/server/hosting.md index c9c6dd41..6b8da995 100644 --- a/docs/server/hosting.md +++ b/docs/server/hosting.md @@ -178,9 +178,9 @@ builder.Host.UseTurboHttp(options => ```csharp builder.Host.UseTurboHttp(options => { - // Buffer size before reading request body into memory - // Larger uploads are streamed - options.RequestBodyBufferThreshold = 64 * 1024; // 64 KB + // Max request body size buffered fully in memory (HTTP/1.x) + // Larger bodies are exposed as a streaming pipe with backpressure + options.Http1.MaxBufferedRequestBodySize = 64 * 1024; // 64 KB // Chunk size when writing response body options.ResponseBodyChunkSize = 16 * 1024; // 16 KB diff --git a/docs/server/performance.md b/docs/server/performance.md index dbe7d716..d27c6730 100644 --- a/docs/server/performance.md +++ b/docs/server/performance.md @@ -31,10 +31,10 @@ Higher values improve throughput for clients sending many parallel requests. Low ### Request Body Buffer ```csharp -options.RequestBodyBufferThreshold = 128 * 1024; // 128 KB +options.Http1.MaxBufferedRequestBodySize = 128 * 1024; // 128 KB ``` -Default is 64 KB. Request bodies smaller than this threshold are buffered in memory. Larger bodies stream directly to the application. +Default is 64 KB. HTTP/1.x request bodies up to this size are buffered fully in memory. Larger bodies are exposed to the application as a streaming pipe with backpressure. - **Increase** for APIs that commonly receive medium-sized payloads (64-256 KB) - **Decrease** for memory-constrained environments or very large upload workloads