Skip to main content

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}