wreq_proto/proto/http2.rs
1//! HTTP/2 protocol implementation and utilities.
2
3pub(crate) mod client;
4pub(crate) mod ping;
5
6use std::{
7 future::Future,
8 io::{self, Cursor, IoSlice},
9 pin::Pin,
10 task::{ready, Context, Poll},
11 time::Duration,
12};
13
14use bytes::{Buf, Bytes};
15use http::{
16 header::{HeaderName, CONNECTION, TE, TRANSFER_ENCODING, UPGRADE},
17 HeaderMap,
18};
19pub use http2::frame::{
20 Priorities, PrioritiesBuilder, Priority, PseudoId, PseudoOrder, Setting, SettingId,
21 SettingsOrder, SettingsOrderBuilder, StreamDependency, StreamId,
22};
23use http2::{Reason, RecvStream, SendStream};
24use http_body::Body;
25use pin_project_lite::pin_project;
26use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
27
28use crate::{error::BoxError, Error, Result};
29
30/// Default initial stream window size defined in HTTP2 spec.
31const SPEC_WINDOW_SIZE: u32 = 65_535;
32
33// Our defaults are chosen for the "majority" case, which usually are not
34// resource constrained, and so the spec default of 64kb can be too limiting
35// for performance.
36const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024 * 5; // 5mb
37const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024 * 2; // 2mb
38const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb
39const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 1024; // 1mb
40const DEFAULT_MAX_HEADER_LIST_SIZE: u32 = 1024 * 16; // 16kb
41
42// The maximum number of concurrent streams that the client is allowed to open
43// before it receives the initial SETTINGS frame from the server.
44// This default value is derived from what the HTTP/2 spec recommends as the
45// minimum value that endpoints advertise to their peers. It means that using
46// this value will minimize the chance of the failure where the local endpoint
47// attempts to open too many streams and gets rejected by the remote peer with
48// the `REFUSED_STREAM` error.
49const DEFAULT_INITIAL_MAX_SEND_STREAMS: usize = 100;
50
51// List of connection headers from RFC 9110 Section 7.6.1
52//
53// TE headers are allowed in HTTP/2 requests as long as the value is "trailers", so they're
54// tested separately.
55static CONNECTION_HEADERS: [HeaderName; 4] = [
56 HeaderName::from_static("keep-alive"),
57 HeaderName::from_static("proxy-connection"),
58 TRANSFER_ENCODING,
59 UPGRADE,
60];
61
62fn strip_connection_headers(headers: &mut HeaderMap, is_request: bool) {
63 for header in &CONNECTION_HEADERS {
64 if headers.remove(header).is_some() {
65 warn!("Connection header illegal in HTTP/2: {}", header.as_str());
66 }
67 }
68
69 if is_request {
70 if headers
71 .get(TE)
72 .is_some_and(|te_header| te_header != "trailers")
73 {
74 warn!("TE headers not set to \"trailers\" are illegal in HTTP/2 requests");
75 headers.remove(TE);
76 }
77 } else if headers.remove(TE).is_some() {
78 warn!("TE headers illegal in HTTP/2 responses");
79 }
80
81 if let Some(header) = headers.remove(CONNECTION) {
82 warn!(
83 "Connection header illegal in HTTP/2: {}",
84 CONNECTION.as_str()
85 );
86
87 if let Ok(header_contents) = header.to_str() {
88 // A `Connection` header may have a comma-separated list of names of other headers that
89 // are meant for only this specific connection.
90 //
91 // Iterate these names and remove them as headers. Connection-specific headers are
92 // forbidden in HTTP2, as that information has been moved into frame types of the h2
93 // protocol.
94 for name in header_contents.split(',') {
95 let name = name.trim();
96 headers.remove(name);
97 }
98 }
99 }
100}
101
102struct Peeked<D> {
103 data: D,
104 is_eos: bool,
105}
106
107// body adapters used by both Client
108pin_project! {
109 pub(crate) struct PipeToSendStream<S>
110 where
111 S: Body,
112 {
113 #[pin]
114 stream: S,
115 body_tx: SendStream<SendBuf<S::Data>>,
116 data_done: bool,
117 // A data chunk that has been polled from the body but is still waiting
118 // for stream-level capacity before it can be shipped. Stored here so
119 // it survives across `Poll::Pending` returns from `poll_capacity`; if
120 // we left the chunk in a local, it would be dropped on every repoll.
121 buffered_data: Option<Peeked<S::Data>>,
122 }
123}
124
125impl<S> PipeToSendStream<S>
126where
127 S: Body,
128{
129 #[inline]
130 fn new(stream: S, body_tx: SendStream<SendBuf<S::Data>>) -> PipeToSendStream<S> {
131 PipeToSendStream {
132 stream,
133 body_tx,
134 data_done: false,
135 buffered_data: None,
136 }
137 }
138
139 #[inline]
140 fn send_reset(self: Pin<&mut Self>, reason: http2::Reason) {
141 self.project().body_tx.send_reset(reason);
142 }
143}
144
145impl<S> Future for PipeToSendStream<S>
146where
147 S: Body,
148 S::Error: Into<BoxError>,
149{
150 type Output = Result<()>;
151
152 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
153 let mut me = self.project();
154 loop {
155 // Register for RST_STREAM notification while we wait for the next
156 // body chunk or for send capacity, so the task wakes up if the
157 // peer resets the stream.
158 if let Poll::Ready(reason) = me
159 .body_tx
160 .poll_reset(cx)
161 .map_err(crate::Error::new_body_write)?
162 {
163 debug!("stream received RST_STREAM: {:?}", reason);
164 return Poll::Ready(Err(Error::new_body_write(::http2::Error::from(reason))));
165 }
166
167 // If a previously-polled chunk is still waiting for stream-level
168 // send capacity, drive that to completion before touching the
169 // body again.
170 if me.buffered_data.is_some() {
171 while me.body_tx.capacity() == 0 {
172 match ready!(me.body_tx.poll_capacity(cx)) {
173 Some(Ok(0)) => {}
174 Some(Ok(_)) => break,
175 Some(Err(e)) => {
176 return Poll::Ready(Err(Error::new_body_write(e)));
177 }
178 None => {
179 // None means the stream is no longer in a
180 // streaming state, we either finished it
181 // somehow, or the remote reset us.
182 return Poll::Ready(Err(Error::new_body_write(
183 "send stream capacity unexpectedly closed",
184 )));
185 }
186 }
187 }
188
189 let peeked = me.buffered_data.take().expect("checked is_some above");
190 let buf = SendBuf::Buf(peeked.data);
191 me.body_tx
192 .send_data(buf, peeked.is_eos)
193 .map_err(Error::new_body_write)?;
194
195 if peeked.is_eos {
196 return Poll::Ready(Ok(()));
197 }
198 continue;
199 }
200
201 // Poll for the next body frame *before* reserving any connection
202 // flow-control capacity. Reserving capacity speculatively (even a
203 // single byte) pins that capacity on the connection-level window,
204 // which can deadlock a second stream when talking to peers that
205 // only emit WINDOW_UPDATE once their receive window is fully
206 // exhausted. See https://github.com/hyperium/hyper/issues/4003.
207 match ready!(me.stream.as_mut().poll_frame(cx)) {
208 Some(Ok(frame)) => {
209 if frame.is_data() {
210 let chunk = frame.into_data().unwrap_or_else(|_| unreachable!());
211 let is_eos = me.stream.is_end_stream();
212 let len = chunk.remaining();
213 trace!("send body chunk: {} bytes, eos={}", len, is_eos);
214
215 if len == 0 {
216 // Zero-length data frames need no capacity; send
217 // them straight through so trailing empty frames
218 // (e.g. an explicit end-of-stream marker) are
219 // delivered.
220 let buf = SendBuf::Buf(chunk);
221 me.body_tx
222 .send_data(buf, is_eos)
223 .map_err(Error::new_body_write)?;
224
225 if is_eos {
226 return Poll::Ready(Ok(()));
227 }
228 continue;
229 }
230
231 // Reserve exactly the chunk size so we never pin more
232 // connection-level flow-control window than we are
233 // about to consume. Stash the chunk in `self` so it
234 // survives the upcoming `poll_capacity` wait even if
235 // it returns `Poll::Pending`.
236 me.body_tx.reserve_capacity(len);
237 *me.buffered_data = Some(Peeked {
238 data: chunk,
239 is_eos,
240 });
241 } else if frame.is_trailers() {
242 // no more DATA, so give any capacity back
243 me.body_tx.reserve_capacity(0);
244 me.body_tx
245 .send_trailers(frame.into_trailers().unwrap_or_else(|_| unreachable!()))
246 .map_err(Error::new_body_write)?;
247 return Poll::Ready(Ok(()));
248 } else {
249 trace!("discarding unknown frame");
250 // loop again
251 }
252 }
253 Some(Err(e)) => return Poll::Ready(Err(me.body_tx.on_user_err(e))),
254 None => {
255 // no more frames means we're done here
256 // but at this point, we haven't sent an EOS DATA, or
257 // any trailers, so send an empty EOS DATA.
258 return Poll::Ready(me.body_tx.send_eos_frame());
259 }
260 }
261 }
262 }
263}
264
265trait SendStreamExt {
266 fn on_user_err<E>(&mut self, err: E) -> Error
267 where
268 E: Into<BoxError>;
269
270 fn send_eos_frame(&mut self) -> Result<()>;
271}
272
273impl<B: Buf> SendStreamExt for SendStream<SendBuf<B>> {
274 fn on_user_err<E>(&mut self, err: E) -> Error
275 where
276 E: Into<BoxError>,
277 {
278 let err = Error::new_user_body(err);
279 debug!("send body user stream error: {}", err);
280 self.send_reset(err.h2_reason());
281 err
282 }
283
284 fn send_eos_frame(&mut self) -> Result<()> {
285 trace!("send body eos");
286 self.send_data(SendBuf::None, true)
287 .map_err(Error::new_body_write)
288 }
289}
290
291#[repr(usize)]
292enum SendBuf<B> {
293 Buf(B),
294 Cursor(Cursor<Box<[u8]>>),
295 None,
296}
297
298impl<B: Buf> Buf for SendBuf<B> {
299 #[inline]
300 fn remaining(&self) -> usize {
301 match *self {
302 Self::Buf(ref b) => b.remaining(),
303 Self::Cursor(ref c) => Buf::remaining(c),
304 Self::None => 0,
305 }
306 }
307
308 #[inline]
309 fn chunk(&self) -> &[u8] {
310 match *self {
311 Self::Buf(ref b) => b.chunk(),
312 Self::Cursor(ref c) => c.chunk(),
313 Self::None => &[],
314 }
315 }
316
317 #[inline]
318 fn advance(&mut self, cnt: usize) {
319 match *self {
320 Self::Buf(ref mut b) => b.advance(cnt),
321 Self::Cursor(ref mut c) => c.advance(cnt),
322 Self::None => {}
323 }
324 }
325
326 fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize {
327 match *self {
328 Self::Buf(ref b) => b.chunks_vectored(dst),
329 Self::Cursor(ref c) => c.chunks_vectored(dst),
330 Self::None => 0,
331 }
332 }
333}
334
335struct H2Upgraded<B>
336where
337 B: Buf,
338{
339 ping: ping::Recorder,
340 send_stream: SendStream<SendBuf<B>>,
341 recv_stream: RecvStream,
342 buf: Bytes,
343}
344
345impl<B> AsyncRead for H2Upgraded<B>
346where
347 B: Buf,
348{
349 fn poll_read(
350 mut self: Pin<&mut Self>,
351 cx: &mut Context<'_>,
352 read_buf: &mut ReadBuf<'_>,
353 ) -> Poll<io::Result<()>> {
354 if self.buf.is_empty() {
355 self.buf = loop {
356 match ready!(self.recv_stream.poll_data(cx)) {
357 None => return Poll::Ready(Ok(())),
358 Some(Ok(buf)) if buf.is_empty() && !self.recv_stream.is_end_stream() => {
359 continue;
360 }
361 Some(Ok(buf)) => {
362 self.ping.record_data(buf.len());
363 break buf;
364 }
365 Some(Err(e)) => {
366 return Poll::Ready(match e.reason() {
367 Some(Reason::NO_ERROR) | Some(Reason::CANCEL) => Ok(()),
368 Some(Reason::STREAM_CLOSED) => {
369 Err(io::Error::new(io::ErrorKind::BrokenPipe, e))
370 }
371 _ => Err(h2_to_io_error(e)),
372 });
373 }
374 }
375 };
376 }
377 let cnt = std::cmp::min(self.buf.len(), read_buf.remaining());
378 read_buf.put_slice(&self.buf[..cnt]);
379 self.buf.advance(cnt);
380 let _ = self.recv_stream.flow_control().release_capacity(cnt);
381 Poll::Ready(Ok(()))
382 }
383}
384
385impl<B> AsyncWrite for H2Upgraded<B>
386where
387 B: Buf,
388{
389 fn poll_write(
390 mut self: Pin<&mut Self>,
391 cx: &mut Context<'_>,
392 buf: &[u8],
393 ) -> Poll<io::Result<usize>> {
394 if buf.is_empty() {
395 return Poll::Ready(Ok(0));
396 }
397 self.send_stream.reserve_capacity(buf.len());
398
399 // We ignore all errors returned by `poll_capacity` and `write`, as we
400 // will get the correct from `poll_reset` anyway.
401 let cnt = match ready!(self.send_stream.poll_capacity(cx)) {
402 None => Some(0),
403 Some(Ok(cnt)) => self
404 .send_stream
405 .send_data(SendBuf::Cursor(Cursor::new(buf[..cnt].into())), false)
406 .ok()
407 .map(|()| cnt),
408 Some(Err(_)) => None,
409 };
410
411 if let Some(cnt) = cnt {
412 return Poll::Ready(Ok(cnt));
413 }
414
415 Poll::Ready(Err(h2_to_io_error(
416 match ready!(self.send_stream.poll_reset(cx)) {
417 Ok(Reason::NO_ERROR) | Ok(Reason::CANCEL) | Ok(Reason::STREAM_CLOSED) => {
418 return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()));
419 }
420 Ok(reason) => reason.into(),
421 Err(e) => e,
422 },
423 )))
424 }
425
426 #[inline]
427 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
428 Poll::Ready(Ok(()))
429 }
430
431 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
432 if self
433 .send_stream
434 .send_data(SendBuf::Cursor(Cursor::new([].into())), true)
435 .is_ok()
436 {
437 return Poll::Ready(Ok(()));
438 }
439
440 Poll::Ready(Err(h2_to_io_error(
441 match ready!(self.send_stream.poll_reset(cx)) {
442 Ok(Reason::NO_ERROR) => return Poll::Ready(Ok(())),
443 Ok(Reason::CANCEL) | Ok(Reason::STREAM_CLOSED) => {
444 return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()));
445 }
446 Ok(reason) => reason.into(),
447 Err(e) => e,
448 },
449 )))
450 }
451}
452
453fn h2_to_io_error(e: http2::Error) -> std::io::Error {
454 if e.is_io() {
455 e.into_io()
456 .expect("[BUG] http2::Error::is_io() is true, but into_io() failed")
457 } else {
458 std::io::Error::other(e)
459 }
460}
461
462/// Builder for `Http2Options`.
463#[must_use]
464#[derive(Debug)]
465pub struct Http2OptionsBuilder {
466 opts: Http2Options,
467}
468
469/// Options for tuning HTTP/2 connections.
470///
471/// `Http2Options` lets you adjust how HTTP/2 works—stream limits, window sizes, frame and header
472/// settings, and more. Most fields are optional and have sensible defaults. See each field for
473/// details.
474#[non_exhaustive]
475#[derive(Debug, Clone)]
476pub struct Http2Options {
477 /// Whether to use adaptive flow control.
478 pub adaptive_window: bool,
479
480 /// The initial stream ID for the connection.
481 pub initial_stream_id: Option<u32>,
482
483 /// The initial window size for HTTP/2 connection-level flow control.
484 pub initial_conn_window_size: u32,
485
486 /// The initial window size for HTTP/2 streams.
487 pub initial_window_size: u32,
488
489 /// The initial maximum number of locally initiated (send) streams.
490 pub initial_max_send_streams: usize,
491
492 /// The maximum frame size to use for HTTP/2.
493 pub max_frame_size: Option<u32>,
494
495 /// The interval for HTTP/2 keep-alive ping frames.
496 pub keep_alive_interval: Option<Duration>,
497
498 /// The timeout for receiving an acknowledgement of the keep-alive ping.
499 pub keep_alive_timeout: Duration,
500
501 /// Whether HTTP/2 keep-alive should apply while the connection is idle.
502 pub keep_alive_while_idle: bool,
503
504 /// The maximum number of concurrent locally reset streams.
505 pub max_concurrent_reset_streams: Option<usize>,
506
507 /// The maximum size of the send buffer for HTTP/2 streams.
508 pub max_send_buffer_size: usize,
509
510 /// The maximum number of concurrent streams initiated by the remote peer.
511 pub max_concurrent_streams: Option<u32>,
512
513 /// The maximum size of the header list.
514 pub max_header_list_size: Option<u32>,
515
516 /// The maximum number of pending accept reset streams.
517 pub max_pending_accept_reset_streams: Option<usize>,
518
519 /// Whether to enable push promises.
520 pub enable_push: Option<bool>,
521
522 /// The header table size for HPACK compression.
523 pub header_table_size: Option<u32>,
524
525 /// Whether to enable the CONNECT protocol.
526 pub enable_connect_protocol: Option<bool>,
527
528 /// Whether to disable RFC 7540 Stream Priorities.
529 pub no_rfc7540_priorities: Option<bool>,
530
531 /// The HTTP/2 pseudo-header field order for outgoing HEADERS frames.
532 pub headers_pseudo_order: Option<PseudoOrder>,
533
534 /// The stream dependency for the outgoing HEADERS frame.
535 pub headers_stream_dependency: Option<StreamDependency>,
536
537 /// The order of settings parameters in the initial SETTINGS frame.
538 pub settings_order: Option<SettingsOrder>,
539
540 /// The list of PRIORITY frames to be sent after connection establishment.
541 pub priorities: Option<Priorities>,
542}
543
544impl Http2OptionsBuilder {
545 /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2
546 /// stream-level flow control.
547 ///
548 /// Passing `None` will do nothing.
549 ///
550 /// If not set, crate::core: will use a default.
551 ///
552 /// [spec]: https://httpwg.org/specs/rfc9113.html#SETTINGS_INITIAL_WINDOW_SIZE
553 #[inline]
554 pub fn initial_window_size(mut self, sz: impl Into<Option<u32>>) -> Self {
555 if let Some(sz) = sz.into() {
556 self.opts.adaptive_window = false;
557 self.opts.initial_window_size = sz;
558 }
559 self
560 }
561
562 /// Sets the max connection-level flow control for HTTP2
563 ///
564 /// Passing `None` will do nothing.
565 ///
566 /// If not set, crate::core: will use a default.
567 #[inline]
568 pub fn initial_connection_window_size(mut self, sz: impl Into<Option<u32>>) -> Self {
569 if let Some(sz) = sz.into() {
570 self.opts.adaptive_window = false;
571 self.opts.initial_conn_window_size = sz;
572 }
573 self
574 }
575
576 /// Sets the initial maximum of locally initiated (send) streams.
577 ///
578 /// This value will be overwritten by the value included in the initial
579 /// SETTINGS frame received from the peer as part of a [connection preface].
580 ///
581 /// Passing `None` will do nothing.
582 ///
583 /// If not set, crate::core: will use a default.
584 ///
585 /// [connection preface]: https://httpwg.org/specs/rfc9113.html#preface
586 #[inline]
587 pub fn initial_max_send_streams(mut self, initial: impl Into<Option<usize>>) -> Self {
588 if let Some(initial) = initial.into() {
589 self.opts.initial_max_send_streams = initial;
590 }
591 self
592 }
593
594 /// Sets the initial stream id for the connection.
595 #[inline]
596 pub fn initial_stream_id(mut self, id: impl Into<Option<u32>>) -> Self {
597 if let Some(id) = id.into() {
598 self.opts.initial_stream_id = Some(id);
599 }
600 self
601 }
602
603 /// Sets whether to use an adaptive flow control.
604 ///
605 /// Enabling this will override the limits set in
606 /// `initial_stream_window_size` and
607 /// `initial_connection_window_size`.
608 #[inline]
609 pub fn adaptive_window(mut self, enabled: bool) -> Self {
610 self.opts.adaptive_window = enabled;
611 if enabled {
612 self.opts.initial_window_size = SPEC_WINDOW_SIZE;
613 self.opts.initial_conn_window_size = SPEC_WINDOW_SIZE;
614 }
615 self
616 }
617
618 /// Sets the maximum frame size to use for HTTP2.
619 ///
620 /// Default is currently 16KB, but can change.
621 #[inline]
622 pub fn max_frame_size(mut self, sz: impl Into<Option<u32>>) -> Self {
623 if let Some(sz) = sz.into() {
624 self.opts.max_frame_size = Some(sz);
625 }
626 self
627 }
628
629 /// Sets the max size of received header frames.
630 ///
631 /// Default is currently 16KB, but can change.
632 #[inline]
633 pub fn max_header_list_size(mut self, max: u32) -> Self {
634 self.opts.max_header_list_size = Some(max);
635 self
636 }
637
638 /// Sets the header table size.
639 ///
640 /// This setting informs the peer of the maximum size of the header compression
641 /// table used to encode header blocks, in octets. The encoder may select any value
642 /// equal to or less than the header table size specified by the sender.
643 ///
644 /// The default value of crate `h2` is 4,096.
645 #[inline]
646 pub fn header_table_size(mut self, size: impl Into<Option<u32>>) -> Self {
647 if let Some(size) = size.into() {
648 self.opts.header_table_size = Some(size);
649 }
650 self
651 }
652
653 /// Sets the maximum number of concurrent streams.
654 ///
655 /// The maximum concurrent streams setting only controls the maximum number
656 /// of streams that can be initiated by the remote peer. In other words,
657 /// when this setting is set to 100, this does not limit the number of
658 /// concurrent streams that can be created by the caller.
659 ///
660 /// It is recommended that this value be no smaller than 100, so as to not
661 /// unnecessarily limit parallelism. However, any value is legal, including
662 /// 0. If `max` is set to 0, then the remote will not be permitted to
663 /// initiate streams.
664 ///
665 /// Note that streams in the reserved state, i.e., push promises that have
666 /// been reserved but the stream has not started, do not count against this
667 /// setting.
668 ///
669 /// Also note that if the remote *does* exceed the value set here, it is not
670 /// a protocol level error. Instead, the `h2` library will immediately reset
671 /// the stream.
672 ///
673 /// See [Section 5.1.2] in the HTTP/2 spec for more details.
674 ///
675 /// [Section 5.1.2]: https://http2.github.io/http2-spec/#rfc.section.5.1.2
676 #[inline]
677 pub fn max_concurrent_streams(mut self, max: impl Into<Option<u32>>) -> Self {
678 if let Some(max) = max.into() {
679 self.opts.max_concurrent_streams = Some(max);
680 }
681 self
682 }
683
684 /// Sets an interval for HTTP2 Ping frames should be sent to keep a
685 /// connection alive.
686 ///
687 /// Pass `None` to disable HTTP2 keep-alive.
688 ///
689 /// Default is currently disabled.
690 #[inline]
691 pub fn keep_alive_interval(mut self, interval: impl Into<Option<Duration>>) -> Self {
692 self.opts.keep_alive_interval = interval.into();
693 self
694 }
695
696 /// Sets a timeout for receiving an acknowledgement of the keep-alive ping.
697 ///
698 /// If the ping is not acknowledged within the timeout, the connection will
699 /// be closed. Does nothing if `keep_alive_interval` is disabled.
700 ///
701 /// Default is 20 seconds.
702 #[inline]
703 pub fn keep_alive_timeout(mut self, timeout: Duration) -> Self {
704 self.opts.keep_alive_timeout = timeout;
705 self
706 }
707
708 /// Sets whether HTTP2 keep-alive should apply while the connection is idle.
709 ///
710 /// If disabled, keep-alive pings are only sent while there are open
711 /// request/responses streams. If enabled, pings are also sent when no
712 /// streams are active. Does nothing if `keep_alive_interval` is
713 /// disabled.
714 ///
715 /// Default is `false`.
716 #[inline]
717 pub fn keep_alive_while_idle(mut self, enabled: bool) -> Self {
718 self.opts.keep_alive_while_idle = enabled;
719 self
720 }
721
722 /// Enables and disables the push feature for HTTP2.
723 ///
724 /// Passing `None` will do nothing.
725 #[inline]
726 pub fn enable_push(mut self, opt: bool) -> Self {
727 self.opts.enable_push = Some(opt);
728 self
729 }
730
731 /// Sets the enable connect protocol.
732 #[inline]
733 pub fn enable_connect_protocol(mut self, opt: bool) -> Self {
734 self.opts.enable_connect_protocol = Some(opt);
735 self
736 }
737
738 /// Disable RFC 7540 Stream Priorities (set to `true` to disable).
739 /// [RFC 9218]: <https://www.rfc-editor.org/rfc/rfc9218.html#section-2.1>
740 #[inline]
741 pub fn no_rfc7540_priorities(mut self, opt: bool) -> Self {
742 self.opts.no_rfc7540_priorities = Some(opt);
743 self
744 }
745
746 /// Sets the maximum number of HTTP2 concurrent locally reset streams.
747 ///
748 /// See the documentation of [`http2::client::Builder::max_concurrent_reset_streams`] for more
749 /// details.
750 ///
751 /// The default value is determined by the `h2` crate.
752 ///
753 /// [`http2::client::Builder::max_concurrent_reset_streams`]: https://docs.rs/h2/client/struct.Builder.html#method.max_concurrent_reset_streams
754 #[inline]
755 pub fn max_concurrent_reset_streams(mut self, max: usize) -> Self {
756 self.opts.max_concurrent_reset_streams = Some(max);
757 self
758 }
759
760 /// Set the maximum write buffer size for each HTTP/2 stream.
761 ///
762 /// Default is currently 1MB, but may change.
763 ///
764 /// # Panics
765 ///
766 /// The value must be no larger than `u32::MAX`.
767 #[inline]
768 pub fn max_send_buf_size(mut self, max: usize) -> Self {
769 assert!(max <= u32::MAX as usize);
770 self.opts.max_send_buffer_size = max;
771 self
772 }
773
774 /// Configures the maximum number of pending reset streams allowed before a GOAWAY will be sent.
775 ///
776 /// See <https://github.com/hyperium/hyper/issues/2877> for more information.
777 #[inline]
778 pub fn max_pending_accept_reset_streams(mut self, max: impl Into<Option<usize>>) -> Self {
779 if let Some(max) = max.into() {
780 self.opts.max_pending_accept_reset_streams = Some(max);
781 }
782 self
783 }
784
785 /// Sets the stream dependency and weight for the outgoing HEADERS frame.
786 ///
787 /// This configures the priority of the stream by specifying its dependency and weight,
788 /// as defined by the HTTP/2 priority mechanism. This can be used to influence how the
789 /// server allocates resources to this stream relative to others.
790 #[inline]
791 pub fn headers_stream_dependency<T>(mut self, stream_dependency: T) -> Self
792 where
793 T: Into<Option<StreamDependency>>,
794 {
795 if let Some(stream_dependency) = stream_dependency.into() {
796 self.opts.headers_stream_dependency = Some(stream_dependency);
797 }
798 self
799 }
800
801 /// Sets the HTTP/2 pseudo-header field order for outgoing HEADERS frames.
802 ///
803 /// This determines the order in which pseudo-header fields (such as `:method`, `:scheme`, etc.)
804 /// are encoded in the HEADERS frame. Customizing the order may be useful for interoperability
805 /// or testing purposes.
806 #[inline]
807 pub fn headers_pseudo_order<T>(mut self, headers_pseudo_order: T) -> Self
808 where
809 T: Into<Option<PseudoOrder>>,
810 {
811 if let Some(headers_pseudo_order) = headers_pseudo_order.into() {
812 self.opts.headers_pseudo_order = Some(headers_pseudo_order);
813 }
814 self
815 }
816
817 /// Sets the order of settings parameters in the initial SETTINGS frame.
818 ///
819 /// This determines the order in which settings are sent during the HTTP/2 handshake.
820 /// Customizing the order may be useful for testing or protocol compliance.
821 #[inline]
822 pub fn settings_order<T>(mut self, settings_order: T) -> Self
823 where
824 T: Into<Option<SettingsOrder>>,
825 {
826 if let Some(settings_order) = settings_order.into() {
827 self.opts.settings_order = Some(settings_order);
828 }
829 self
830 }
831
832 /// Sets the list of PRIORITY frames to be sent immediately after the connection is established,
833 /// but before the first request is sent.
834 ///
835 /// This allows you to pre-configure the HTTP/2 stream dependency tree by specifying a set of
836 /// PRIORITY frames that will be sent as part of the connection preface. This can be useful for
837 /// optimizing resource allocation or testing custom stream prioritization strategies.
838 ///
839 /// Each `Priority` in the list must have a valid (non-zero) stream ID. Any priority with a
840 /// stream ID of zero will be ignored.
841 #[inline]
842 pub fn priorities<T>(mut self, priorities: T) -> Self
843 where
844 T: Into<Option<Priorities>>,
845 {
846 if let Some(priorities) = priorities.into() {
847 self.opts.priorities = Some(priorities);
848 }
849 self
850 }
851
852 /// Builds the `Http2Options` instance.
853 #[inline]
854 pub fn build(self) -> Http2Options {
855 self.opts
856 }
857}
858
859impl Http2Options {
860 /// Creates a new `Http2OptionsBuilder` instance.
861 pub fn builder() -> Http2OptionsBuilder {
862 // Reset optional frame size and header size settings to None to allow explicit
863 // customization This ensures users can configure these via builder methods without
864 // being constrained by defaults
865 Http2OptionsBuilder {
866 opts: Http2Options {
867 max_frame_size: None,
868 max_header_list_size: None,
869 ..Default::default()
870 },
871 }
872 }
873}
874
875impl Default for Http2Options {
876 #[inline]
877 fn default() -> Self {
878 Http2Options {
879 adaptive_window: false,
880 initial_stream_id: None,
881 initial_conn_window_size: DEFAULT_CONN_WINDOW,
882 initial_window_size: DEFAULT_STREAM_WINDOW,
883 initial_max_send_streams: DEFAULT_INITIAL_MAX_SEND_STREAMS,
884 max_frame_size: Some(DEFAULT_MAX_FRAME_SIZE),
885 max_header_list_size: Some(DEFAULT_MAX_HEADER_LIST_SIZE),
886 keep_alive_interval: None,
887 keep_alive_timeout: Duration::from_secs(20),
888 keep_alive_while_idle: false,
889 max_concurrent_reset_streams: None,
890 max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE,
891 max_pending_accept_reset_streams: None,
892 header_table_size: None,
893 max_concurrent_streams: None,
894 enable_push: None,
895 enable_connect_protocol: None,
896 no_rfc7540_priorities: None,
897 settings_order: None,
898 headers_pseudo_order: None,
899 headers_stream_dependency: None,
900 priorities: None,
901 }
902 }
903}