Skip to main content

trillium_http/
http_config.rs

1use fieldwork::Fieldwork;
2
3/// # Performance and security parameters for trillium-http.
4///
5/// Trillium's http implementation is built with sensible defaults, but applications differ in usage
6/// and this escape hatch allows an application to be tuned. It is best to tune these parameters in
7/// context of realistic benchmarks for your application.
8///
9/// Long term, trillium may export several standard defaults for different constraints and
10/// application types. In the distant future, these may turn into initial values and trillium will
11/// tune itself based on values seen at runtime.
12#[derive(Clone, Copy, Debug, Fieldwork)]
13#[fieldwork(get, get_mut, set, with, without)]
14// `HttpConfig` is a user-facing tuning struct with documented per-field setters; the natural
15// shape is one field per knob. Bundling bools into an enum or bitflags would make the getter/
16// setter surface worse for callers.
17#[allow(clippy::struct_excessive_bools)]
18pub struct HttpConfig {
19    /// The maximum length allowed before the http body begins for a given request.
20    ///
21    /// **Default**: `8kb` in bytes
22    ///
23    /// **Unit**: Byte count
24    pub(crate) head_max_len: usize,
25
26    /// The maximum length of a received body
27    ///
28    /// This limit applies regardless of whether the body is read all at once or streamed
29    /// incrementally, and regardless of transfer encoding (chunked or fixed-length). The correct
30    /// value will be application dependent.
31    ///
32    /// **Default**: `10mb` in bytes
33    ///
34    /// **Unit**: Byte count
35    pub(crate) received_body_max_len: u64,
36
37    #[cfg(not(feature = "parse"))]
38    #[field = false] // this one is private for now
39    pub(crate) max_headers: usize,
40
41    /// The initial buffer allocated for the response.
42    ///
43    /// Ideally this would be exactly the length of the combined response headers and body, if the
44    /// body is short. If the value is shorter than the headers plus the body, multiple transport
45    /// writes will be performed, and if the value is longer, unnecessary memory will be allocated
46    /// for each conn. Although a tcp packet can be up to 64kb, it is probably better to use a
47    /// value less than 1.5kb.
48    ///
49    /// **Default**: `512`
50    ///
51    /// **Unit**: byte count
52    pub(crate) response_buffer_len: usize,
53
54    /// Maximum size the response buffer may grow to absorb backpressure.
55    ///
56    /// When the transport cannot accept data as fast as the response body is produced, the buffer
57    /// absorbs the remainder up to this limit. Once the limit is reached, writes apply
58    /// backpressure to the body source. This prevents a slow client from causing unbounded memory
59    /// growth.
60    ///
61    /// **Default**: `2mb` in bytes
62    ///
63    /// **Unit**: byte count
64    pub(crate) response_buffer_max_len: usize,
65
66    /// The initial buffer allocated for the request headers.
67    ///
68    /// Ideally this is the length of the request headers. It will grow nonlinearly until
69    /// `max_head_len` or the end of the headers are reached, whichever happens first.
70    ///
71    /// **Default**: `128`
72    ///
73    /// **Unit**: byte count
74    pub(crate) request_buffer_initial_len: usize,
75
76    /// The number of response headers to allocate space for on conn creation.
77    ///
78    /// Headers will grow on insertion when they reach this size.
79    ///
80    /// **Default**: `16`
81    ///
82    /// **Unit**: Header count
83    pub(crate) response_header_initial_capacity: usize,
84
85    /// A sort of cooperative task yielding knob.
86    ///
87    /// Decreasing this number will improve tail latencies at a slight cost to total throughput for
88    /// fast clients. This will have more of an impact on servers that spend a lot of time in IO
89    /// compared to app handlers.
90    ///
91    /// **Default**: `16`
92    ///
93    /// **Unit**: the number of consecutive `Poll::Ready` async writes to perform before yielding
94    /// the task back to the runtime.
95    pub(crate) copy_loops_per_yield: usize,
96
97    /// The initial buffer capacity allocated when reading a chunked http body to bytes or string.
98    ///
99    /// Ideally this would be the size of the http body, which is highly application dependent. As
100    /// with other initial buffer lengths, further allocation will be performed until the necessary
101    /// length is achieved. A smaller number will result in more vec resizing, and a larger number
102    /// will result in unnecessary allocation.
103    ///
104    /// **Default**: `128`
105    ///
106    /// **Unit**: byte count
107    pub(crate) received_body_initial_len: usize,
108
109    /// Maximum size to pre-allocate based on content-length for buffering a complete request body
110    ///
111    /// When we receive a fixed-length (not chunked-encoding) body that is smaller than this size,
112    /// we can allocate a buffer with exactly the right size before we receive the body.  However,
113    /// if this is unbounded, malicious clients can issue headers with large content-length and
114    /// then keep the connection open without sending any bytes, allowing them to allocate
115    /// memory faster than their bandwidth usage. This does not limit the ability to receive
116    /// fixed-length bodies larger than this, but the memory allocation will grow as with
117    /// chunked bodies. Note that this has no impact on chunked bodies. If this is set higher
118    /// than the `received_body_max_len`, this parameter has no effect. This parameter only
119    /// impacts [`ReceivedBody::read_string`](crate::ReceivedBody::read_string) and
120    /// [`ReceivedBody::read_bytes`](crate::ReceivedBody::read_bytes).
121    ///
122    /// **Default**: `1mb` in bytes
123    ///
124    /// **Unit**: Byte count
125    pub(crate) received_body_max_preallocate: usize,
126
127    /// The maximum size of a field section (header block) the peer may send in HTTP/3
128    ///
129    /// This is a protocol-level setting and is communicated to the peer.
130    ///
131    /// **Default**: 8kb
132    ///
133    /// **Unit**: Byte count
134    pub(crate) h3_max_field_section_size: u64,
135
136    /// Maximum capacity of the QPACK dynamic table for HTTP/3 header compression.
137    ///
138    /// Advertised to peers as `SETTINGS_QPACK_MAX_TABLE_CAPACITY`. Peers may use a dynamic table
139    /// up to this size to compress request headers. Set to `0` to disable dynamic table
140    /// compression entirely.
141    ///
142    /// **Default**: 4096 bytes
143    ///
144    /// **Unit**: Byte count
145    pub(crate) h3_max_table_capacity: usize,
146
147    /// Maximum number of HTTP/3 request streams that may be blocked waiting for dynamic table
148    /// updates.
149    ///
150    /// Advertised to peers as `SETTINGS_QPACK_BLOCKED_STREAMS`. A value of `0` prevents peers
151    /// from sending header blocks that reference table entries not yet seen by this decoder.
152    ///
153    /// **Default**: 100
154    ///
155    /// **Unit**: Stream count
156    pub(crate) h3_blocked_streams: usize,
157
158    /// Per-connection ring size for the QPACK encoder's recently-seen-pair predictor.
159    ///
160    /// The predictor lets the encoder defer dynamic-table inserts until a `(name, value)`
161    /// pair has been seen at least once on the connection — first sighting emits a
162    /// literal, subsequent sightings within the ring's retention window invest in an
163    /// insert so future sections can index it. A larger ring catches repetitions across
164    /// more intervening header lines (good for header-heavy reverse proxies); a smaller
165    /// ring forgets faster (fine for tiny APIs).
166    ///
167    /// The predictor is consulted once per emitted header line via a u32 hash compare;
168    /// cost grows linearly with `size` but is dominated by the per-line hash, so
169    /// oversizing here is cheap.
170    ///
171    /// **Default**: 64
172    ///
173    /// **Unit**: Pair count
174    pub(crate) h3_qpack_recent_pairs_size: usize,
175
176    /// whether [datagrams](https://www.rfc-editor.org/rfc/rfc9297.html) are enabled for HTTP/3
177    ///
178    /// This is a protocol-level setting and is communicated to the peer as well as enforced.
179    ///
180    /// **Default**: false
181    pub(crate) h3_datagrams_enabled: bool,
182
183    /// whether [webtransport](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3)
184    /// (`draft-ietf-webtrans-http3`) is enabled for HTTP/3
185    ///
186    /// This is a protocol-level setting and is communicated to the peer. You do not need to
187    /// manually configure this if using
188    /// [`trillium-webtransport`](https://docs.rs/trillium-webtransport)
189    ///
190    /// **Default**: false
191    pub(crate) webtransport_enabled: bool,
192
193    /// whether to panic when a response header with an invalid value (containing `\r`, `\n`, or
194    /// `\0`) is encountered.
195    ///
196    /// Invalid header values are always skipped to prevent header injection. When this is `true`,
197    /// Trillium will additionally panic, surfacing the bug loudly. When `false`, the skip is only
198    /// logged (to the `log` backend) at error level.
199    ///
200    /// **Default**: `true` when compiled with `debug_assertions` (i.e. debug builds), `false` in
201    /// release builds. Override to `true` in release if you want strict production behavior, or to
202    /// `false` in debug if you prefer not to panic during development.
203    pub(crate) panic_on_invalid_response_headers: bool,
204}
205
206impl HttpConfig {
207    /// Default Config
208    pub const DEFAULT: Self = HttpConfig {
209        response_buffer_len: 512,
210        response_buffer_max_len: 2 * 1024 * 1024,
211        request_buffer_initial_len: 128,
212        head_max_len: 8 * 1024,
213        #[cfg(not(feature = "parse"))]
214        max_headers: 128,
215        response_header_initial_capacity: 16,
216        copy_loops_per_yield: 16,
217        received_body_max_len: 10 * 1024 * 1024,
218        received_body_initial_len: 128,
219        received_body_max_preallocate: 1024 * 1024,
220        h3_max_field_section_size: 8 * 1024,
221        h3_max_table_capacity: 4096,
222        h3_blocked_streams: 100,
223        h3_qpack_recent_pairs_size: 64,
224        h3_datagrams_enabled: false,
225        webtransport_enabled: false,
226        panic_on_invalid_response_headers: cfg!(debug_assertions),
227    };
228}
229
230impl Default for HttpConfig {
231    fn default() -> Self {
232        HttpConfig::DEFAULT
233    }
234}