Skip to main content

hyper/proto/h2/
server.rs

1use std::error::Error as StdError;
2use std::future::Future;
3use std::pin::Pin;
4use std::task::{Context, Poll};
5use std::time::Duration;
6
7use bytes::Bytes;
8use futures_core::ready;
9use h2::server::{Connection, Handshake, SendResponse};
10use h2::{Reason, RecvStream};
11use http::{Method, Request};
12use pin_project_lite::pin_project;
13
14use super::{ping, PipeToSendStream, SendBuf};
15use crate::body::{Body, Incoming as IncomingBody};
16use crate::common::date;
17use crate::common::io::Compat;
18use crate::common::time::Time;
19use crate::ext::Protocol;
20use crate::headers;
21use crate::proto::h2::ping::Recorder;
22use crate::proto::Dispatched;
23use crate::rt::bounds::{Http2ServerConnExec, Http2UpgradedExec};
24use crate::rt::{Read, Write};
25use crate::service::HttpService;
26
27use crate::upgrade::{OnUpgrade, Pending, Upgraded};
28use crate::Response;
29
30// Our defaults are chosen for the "majority" case, which usually are not
31// resource constrained, and so the spec default of 64kb can be too limiting
32// for performance.
33//
34// At the same time, a server more often has multiple clients connected, and
35// so is more likely to use more resources than a client would.
36const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024; // 1mb
37const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024; // 1mb
38const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb
39const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 400; // 400kb
40const DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: u32 = 1024 * 16; // 16kb
41const DEFAULT_MAX_LOCAL_ERROR_RESET_STREAMS: usize = 1024;
42
43#[derive(Clone, Debug)]
44pub(crate) struct Config {
45    pub(crate) adaptive_window: bool,
46    pub(crate) initial_conn_window_size: u32,
47    pub(crate) initial_stream_window_size: u32,
48    pub(crate) max_frame_size: u32,
49    pub(crate) enable_connect_protocol: bool,
50    pub(crate) max_concurrent_streams: Option<u32>,
51    pub(crate) max_pending_accept_reset_streams: Option<usize>,
52    pub(crate) max_local_error_reset_streams: Option<usize>,
53    pub(crate) keep_alive_interval: Option<Duration>,
54    pub(crate) keep_alive_timeout: Duration,
55    pub(crate) max_send_buffer_size: usize,
56    pub(crate) header_table_size: Option<u32>,
57    pub(crate) max_header_list_size: u32,
58    pub(crate) date_header: bool,
59}
60
61impl Default for Config {
62    fn default() -> Config {
63        Config {
64            adaptive_window: false,
65            initial_conn_window_size: DEFAULT_CONN_WINDOW,
66            initial_stream_window_size: DEFAULT_STREAM_WINDOW,
67            max_frame_size: DEFAULT_MAX_FRAME_SIZE,
68            enable_connect_protocol: false,
69            max_concurrent_streams: Some(200),
70            max_pending_accept_reset_streams: None,
71            max_local_error_reset_streams: Some(DEFAULT_MAX_LOCAL_ERROR_RESET_STREAMS),
72            header_table_size: None,
73            keep_alive_interval: None,
74            keep_alive_timeout: Duration::from_secs(20),
75            max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE,
76            max_header_list_size: DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE,
77            date_header: true,
78        }
79    }
80}
81
82pin_project! {
83    pub(crate) struct Server<T, S, B, E>
84    where
85        S: HttpService<IncomingBody>,
86        B: Body,
87    {
88        exec: E,
89        timer: Time,
90        service: S,
91        state: State<T, B>,
92        date_header: bool,
93        close_pending: bool
94    }
95}
96
97//#[expect(clippy::large_enum_variant, reason = "the whole future is boxed")]
98#[allow(clippy::large_enum_variant)]
99enum State<T, B>
100where
101    B: Body,
102{
103    Handshaking {
104        ping_config: ping::Config,
105        hs: Handshake<Compat<T>, SendBuf<B::Data>>,
106    },
107    Serving(Serving<T, B>),
108}
109
110struct Serving<T, B>
111where
112    B: Body,
113{
114    ping: Option<(ping::Recorder, ping::Ponger)>,
115    conn: Connection<Compat<T>, SendBuf<B::Data>>,
116    closing: Option<crate::Error>,
117    date_header: bool,
118}
119
120impl<T, S, B, E> Server<T, S, B, E>
121where
122    T: Read + Write + Unpin,
123    S: HttpService<IncomingBody, ResBody = B>,
124    S::Error: Into<Box<dyn StdError + Send + Sync>>,
125    B: Body + 'static,
126    E: Http2ServerConnExec<S::Future, B>,
127{
128    pub(crate) fn new(
129        io: T,
130        service: S,
131        config: &Config,
132        exec: E,
133        timer: Time,
134    ) -> Server<T, S, B, E> {
135        let mut builder = h2::server::Builder::default();
136        builder
137            .initial_window_size(config.initial_stream_window_size)
138            .initial_connection_window_size(config.initial_conn_window_size)
139            .max_frame_size(config.max_frame_size)
140            .max_header_list_size(config.max_header_list_size)
141            .max_local_error_reset_streams(config.max_local_error_reset_streams)
142            .max_send_buffer_size(config.max_send_buffer_size);
143        if let Some(max) = config.max_concurrent_streams {
144            builder.max_concurrent_streams(max);
145        }
146        if let Some(max) = config.max_pending_accept_reset_streams {
147            builder.max_pending_accept_reset_streams(max);
148        }
149        if let Some(size) = config.header_table_size {
150            builder.header_table_size(size);
151        }
152        if config.enable_connect_protocol {
153            builder.enable_connect_protocol();
154        }
155        let handshake = builder.handshake(Compat::new(io));
156
157        let bdp = if config.adaptive_window {
158            Some(config.initial_stream_window_size)
159        } else {
160            None
161        };
162
163        let ping_config = ping::Config {
164            bdp_initial_window: bdp,
165            keep_alive_interval: config.keep_alive_interval,
166            keep_alive_timeout: config.keep_alive_timeout,
167            // If keep-alive is enabled for servers, always enabled while
168            // idle, so it can more aggressively close dead connections.
169            keep_alive_while_idle: true,
170        };
171
172        Server {
173            exec,
174            timer,
175            state: State::Handshaking {
176                ping_config,
177                hs: handshake,
178            },
179            service,
180            date_header: config.date_header,
181            close_pending: false,
182        }
183    }
184
185    pub(crate) fn graceful_shutdown(&mut self) {
186        trace!("graceful_shutdown");
187        match &mut self.state {
188            State::Handshaking { .. } => {
189                self.close_pending = true;
190            }
191            State::Serving(srv) => {
192                if srv.closing.is_none() {
193                    srv.conn.graceful_shutdown();
194                }
195            }
196        }
197    }
198}
199
200impl<T, S, B, E> Future for Server<T, S, B, E>
201where
202    T: Read + Write + Unpin,
203    S: HttpService<IncomingBody, ResBody = B>,
204    S::Error: Into<Box<dyn StdError + Send + Sync>>,
205    B: Body + 'static,
206    E: Http2ServerConnExec<S::Future, B>,
207{
208    type Output = crate::Result<Dispatched>;
209
210    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
211        let me = &mut *self;
212        loop {
213            let next = match &mut me.state {
214                State::Handshaking { hs, ping_config } => {
215                    let mut conn = ready!(Pin::new(hs).poll(cx).map_err(crate::Error::new_h2))?;
216                    let ping = if ping_config.is_enabled() {
217                        let pp = conn.ping_pong().expect("conn.ping_pong");
218                        Some(ping::channel(pp, ping_config.clone(), me.timer.clone()))
219                    } else {
220                        None
221                    };
222                    State::Serving(Serving {
223                        ping,
224                        conn,
225                        closing: None,
226                        date_header: me.date_header,
227                    })
228                }
229                State::Serving(srv) => {
230                    // graceful_shutdown was called before handshaking finished,
231                    if me.close_pending && srv.closing.is_none() {
232                        srv.conn.graceful_shutdown();
233                    }
234                    ready!(srv.poll_server(cx, &mut me.service, &mut me.exec))?;
235                    return Poll::Ready(Ok(Dispatched::Shutdown));
236                }
237            };
238            me.state = next;
239        }
240    }
241}
242
243impl<T, B> Serving<T, B>
244where
245    T: Read + Write + Unpin,
246    B: Body + 'static,
247{
248    fn poll_server<S, E>(
249        &mut self,
250        cx: &mut Context<'_>,
251        service: &mut S,
252        exec: &mut E,
253    ) -> Poll<crate::Result<()>>
254    where
255        S: HttpService<IncomingBody, ResBody = B>,
256        S::Error: Into<Box<dyn StdError + Send + Sync>>,
257        E: Http2ServerConnExec<S::Future, B>,
258    {
259        if self.closing.is_none() {
260            loop {
261                self.poll_ping(cx);
262
263                match ready!(self.conn.poll_accept(cx)) {
264                    Some(Ok((req, mut respond))) => {
265                        trace!("incoming request");
266                        let content_length = headers::content_length_parse_all(req.headers());
267                        let ping = self
268                            .ping
269                            .as_ref()
270                            .map(|ping| ping.0.clone())
271                            .unwrap_or_else(ping::disabled);
272
273                        // Record the headers received
274                        ping.record_non_data();
275
276                        let is_connect = req.method() == Method::CONNECT;
277                        let (mut parts, stream) = req.into_parts();
278                        let (mut req, connect_parts) = if !is_connect {
279                            (
280                                Request::from_parts(
281                                    parts,
282                                    IncomingBody::h2(stream, content_length.into(), ping),
283                                ),
284                                None,
285                            )
286                        } else {
287                            if content_length.map_or(false, |len| len != 0) {
288                                warn!("h2 connect request with non-zero body not supported");
289                                respond.send_reset(h2::Reason::INTERNAL_ERROR);
290                                return Poll::Ready(Ok(()));
291                            }
292                            let (pending, upgrade) = crate::upgrade::pending();
293                            debug_assert!(parts.extensions.get::<OnUpgrade>().is_none());
294                            parts.extensions.insert(upgrade);
295                            (
296                                Request::from_parts(parts, IncomingBody::empty()),
297                                Some(ConnectParts {
298                                    pending,
299                                    ping,
300                                    recv_stream: stream,
301                                }),
302                            )
303                        };
304
305                        if let Some(protocol) = req.extensions_mut().remove::<h2::ext::Protocol>() {
306                            req.extensions_mut().insert(Protocol::from_inner(protocol));
307                        }
308
309                        let fut = H2Stream::new(
310                            service.call(req),
311                            connect_parts,
312                            respond,
313                            self.date_header,
314                            exec.clone(),
315                        );
316
317                        exec.execute_h2stream(fut);
318                    }
319                    Some(Err(e)) => {
320                        return Poll::Ready(Err(crate::Error::new_h2(e)));
321                    }
322                    None => {
323                        // no more incoming streams...
324                        if let Some((ping, _)) = &self.ping {
325                            ping.ensure_not_timed_out()?;
326                        }
327
328                        trace!("incoming connection complete");
329                        return Poll::Ready(Ok(()));
330                    }
331                }
332            }
333        }
334
335        debug_assert!(
336            self.closing.is_some(),
337            "poll_server broke loop without closing"
338        );
339
340        ready!(self.conn.poll_closed(cx).map_err(crate::Error::new_h2))?;
341
342        Poll::Ready(Err(self.closing.take().expect("polled after error")))
343    }
344
345    fn poll_ping(&mut self, cx: &mut Context<'_>) {
346        if let Some((_, estimator)) = &mut self.ping {
347            match estimator.poll(cx) {
348                Poll::Ready(ping::Ponged::SizeUpdate(wnd)) => {
349                    self.conn.set_target_window_size(wnd);
350                    let _ = self.conn.set_initial_window_size(wnd);
351                }
352                Poll::Ready(ping::Ponged::KeepAliveTimedOut) => {
353                    debug!("keep-alive timed out, closing connection");
354                    self.conn.abrupt_shutdown(h2::Reason::NO_ERROR);
355                }
356                Poll::Pending => {}
357            }
358        }
359    }
360}
361
362pin_project! {
363    #[allow(missing_debug_implementations)]
364    pub struct H2Stream<F, B, E>
365    where
366        B: Body,
367    {
368        reply: SendResponse<SendBuf<B::Data>>,
369        #[pin]
370        state: H2StreamState<F, B>,
371        date_header: bool,
372        exec: E,
373    }
374}
375
376pin_project! {
377    #[project = H2StreamStateProj]
378    enum H2StreamState<F, B>
379    where
380        B: Body,
381    {
382        Service {
383            #[pin]
384            fut: F,
385            connect_parts: Option<ConnectParts>,
386        },
387        Body {
388            #[pin]
389            pipe: PipeToSendStream<B>,
390        },
391    }
392}
393
394struct ConnectParts {
395    pending: Pending,
396    ping: Recorder,
397    recv_stream: RecvStream,
398}
399
400impl<F, B, E> H2Stream<F, B, E>
401where
402    B: Body,
403{
404    fn new(
405        fut: F,
406        connect_parts: Option<ConnectParts>,
407        respond: SendResponse<SendBuf<B::Data>>,
408        date_header: bool,
409        exec: E,
410    ) -> H2Stream<F, B, E> {
411        H2Stream {
412            reply: respond,
413            state: H2StreamState::Service { fut, connect_parts },
414            date_header,
415            exec,
416        }
417    }
418}
419
420macro_rules! reply {
421    ($me:expr, $res:expr, $eos:expr) => {{
422        match $me.reply.send_response($res, $eos) {
423            Ok(tx) => tx,
424            Err(e) => {
425                debug!("send response error: {}", e);
426                $me.reply.send_reset(Reason::INTERNAL_ERROR);
427                return Poll::Ready(Err(crate::Error::new_h2(e)));
428            }
429        }
430    }};
431}
432
433impl<F, B, Ex, E> H2Stream<F, B, Ex>
434where
435    F: Future<Output = Result<Response<B>, E>>,
436    B: Body,
437    B::Data: 'static,
438    B::Error: Into<Box<dyn StdError + Send + Sync>>,
439    Ex: Http2UpgradedExec<B::Data>,
440    E: Into<Box<dyn StdError + Send + Sync>>,
441{
442    fn poll2(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<crate::Result<()>> {
443        let mut me = self.as_mut().project();
444        loop {
445            let next = match me.state.as_mut().project() {
446                H2StreamStateProj::Service {
447                    fut: h,
448                    connect_parts,
449                } => {
450                    let res = match h.poll(cx) {
451                        Poll::Ready(Ok(r)) => r,
452                        Poll::Pending => {
453                            // Response is not yet ready, so we want to check if the client has sent a
454                            // RST_STREAM frame which would cancel the current request.
455                            if let Poll::Ready(reason) =
456                                me.reply.poll_reset(cx).map_err(crate::Error::new_h2)?
457                            {
458                                debug!("stream received RST_STREAM: {:?}", reason);
459                                return Poll::Ready(Err(crate::Error::new_h2(reason.into())));
460                            }
461                            return Poll::Pending;
462                        }
463                        Poll::Ready(Err(e)) => {
464                            let err = crate::Error::new_user_service(e);
465                            warn!("http2 service errored: {}", err);
466                            me.reply.send_reset(err.h2_reason());
467                            return Poll::Ready(Err(err));
468                        }
469                    };
470
471                    let (head, body) = res.into_parts();
472                    let mut res = ::http::Response::from_parts(head, ());
473                    super::strip_connection_headers(
474                        res.headers_mut(),
475                        super::MessageKind::Response,
476                    );
477
478                    // set Date header if it isn't already set if instructed
479                    if *me.date_header {
480                        res.headers_mut()
481                            .entry(::http::header::DATE)
482                            .or_insert_with(date::update_and_header_value);
483                    }
484
485                    if let Some(connect_parts) = connect_parts.take() {
486                        if res.status().is_success() {
487                            if headers::content_length_parse_all(res.headers())
488                                .map_or(false, |len| len != 0)
489                            {
490                                warn!("h2 successful response to CONNECT request with body not supported");
491                                me.reply.send_reset(h2::Reason::INTERNAL_ERROR);
492                                return Poll::Ready(Err(crate::Error::new_user_header()));
493                            }
494                            if res
495                                .headers_mut()
496                                .remove(::http::header::CONTENT_LENGTH)
497                                .is_some()
498                            {
499                                warn!("successful response to CONNECT request disallows content-length header");
500                            }
501                            let send_stream = reply!(me, res, false);
502                            let (h2_up, up_task) = super::upgrade::pair(
503                                send_stream,
504                                connect_parts.recv_stream,
505                                connect_parts.ping,
506                            );
507                            connect_parts
508                                .pending
509                                .fulfill(Upgraded::new(h2_up, Bytes::new()));
510                            self.exec.execute_upgrade(up_task);
511                            return Poll::Ready(Ok(()));
512                        }
513                    }
514
515                    if !body.is_end_stream() {
516                        // automatically set Content-Length from body...
517                        if let Some(len) = body.size_hint().exact() {
518                            headers::set_content_length_if_missing(res.headers_mut(), len);
519                        }
520
521                        let body_tx = reply!(me, res, false);
522                        H2StreamState::Body {
523                            pipe: PipeToSendStream::new(body, body_tx),
524                        }
525                    } else {
526                        reply!(me, res, true);
527                        return Poll::Ready(Ok(()));
528                    }
529                }
530                H2StreamStateProj::Body { pipe } => {
531                    return pipe.poll(cx);
532                }
533            };
534            me.state.set(next);
535        }
536    }
537}
538
539impl<F, B, Ex, E> Future for H2Stream<F, B, Ex>
540where
541    F: Future<Output = Result<Response<B>, E>>,
542    B: Body,
543    B::Data: 'static,
544    B::Error: Into<Box<dyn StdError + Send + Sync>>,
545    Ex: Http2UpgradedExec<B::Data>,
546    E: Into<Box<dyn StdError + Send + Sync>>,
547{
548    type Output = ();
549
550    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
551        self.poll2(cx).map(|res| {
552            if let Err(_e) = res {
553                debug!("stream error: {}", _e);
554            }
555        })
556    }
557}