Skip to main content

actix_http/h2/
service.rs

1use std::{
2    future::Future,
3    marker::PhantomData,
4    mem, net,
5    pin::Pin,
6    rc::Rc,
7    task::{Context, Poll},
8};
9
10use actix_codec::{AsyncRead, AsyncWrite};
11use actix_service::{
12    fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
13};
14#[expect(deprecated, reason = "preserve the public ready future type")]
15use actix_utils::future::ready;
16use futures_core::{future::LocalBoxFuture, ready};
17use tokio::net::TcpStream;
18use tracing::{error, trace};
19
20use super::{dispatcher::Dispatcher, handshake_with_timeout, HandshakeWithTimeout};
21use crate::{
22    body::{BoxBody, MessageBody},
23    config::ServiceConfig,
24    error::DispatchError,
25    service::HttpFlow,
26    ConnectCallback, OnConnectData, Request, Response,
27};
28
29#[inline]
30fn desired_nodelay(tcp_nodelay: Option<bool>) -> Option<bool> {
31    tcp_nodelay
32}
33
34#[inline]
35fn set_nodelay(stream: &TcpStream, nodelay: bool) {
36    let _ = stream.set_nodelay(nodelay);
37}
38
39/// `ServiceFactory` implementation for HTTP/2 transport
40pub struct H2Service<T, S, B> {
41    srv: S,
42    cfg: ServiceConfig,
43    on_connect_ext: Option<Rc<ConnectCallback<T>>>,
44    _phantom: PhantomData<(T, B)>,
45}
46
47impl<T, S, B> H2Service<T, S, B>
48where
49    S: ServiceFactory<Request, Config = ()>,
50    S::Error: Into<Response<BoxBody>> + 'static,
51    S::Response: Into<Response<B>> + 'static,
52    <S::Service as Service<Request>>::Future: 'static,
53
54    B: MessageBody + 'static,
55{
56    /// Create new `H2Service` instance with config.
57    pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
58        cfg: ServiceConfig,
59        service: F,
60    ) -> Self {
61        H2Service {
62            cfg,
63            on_connect_ext: None,
64            srv: service.into_factory(),
65            _phantom: PhantomData,
66        }
67    }
68
69    /// Set on connect callback.
70    pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
71        self.on_connect_ext = f;
72        self
73    }
74}
75
76impl<S, B> H2Service<TcpStream, S, B>
77where
78    S: ServiceFactory<Request, Config = ()>,
79    S::Future: 'static,
80    S::Error: Into<Response<BoxBody>> + 'static,
81    S::Response: Into<Response<B>> + 'static,
82    <S::Service as Service<Request>>::Future: 'static,
83
84    B: MessageBody + 'static,
85{
86    /// Create plain TCP based service
87    #[expect(deprecated, reason = "preserve the public ready future type")]
88    pub fn tcp(
89        self,
90    ) -> impl ServiceFactory<
91        TcpStream,
92        Config = (),
93        Response = (),
94        Error = DispatchError,
95        InitError = S::InitError,
96    > {
97        let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
98
99        fn_factory(move || {
100            ready(Ok::<_, S::InitError>(fn_service(move |io: TcpStream| {
101                if let Some(nodelay) = tcp_nodelay {
102                    set_nodelay(&io, nodelay);
103                }
104                let peer_addr = io.peer_addr().ok();
105                ready(Ok::<_, DispatchError>((io, peer_addr)))
106            })))
107        })
108        .and_then(self)
109    }
110}
111
112#[cfg(feature = "openssl")]
113mod openssl {
114    use actix_tls::accept::{
115        openssl::{
116            reexports::{Error as SslError, SslAcceptor},
117            Acceptor, TlsStream,
118        },
119        TlsError,
120    };
121
122    use super::*;
123
124    impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
125    where
126        S: ServiceFactory<Request, Config = ()>,
127        S::Future: 'static,
128        S::Error: Into<Response<BoxBody>> + 'static,
129        S::Response: Into<Response<B>> + 'static,
130        <S::Service as Service<Request>>::Future: 'static,
131
132        B: MessageBody + 'static,
133    {
134        /// Create OpenSSL based service.
135        pub fn openssl(
136            self,
137            acceptor: SslAcceptor,
138        ) -> impl ServiceFactory<
139            TcpStream,
140            Config = (),
141            Response = (),
142            Error = TlsError<SslError, DispatchError>,
143            InitError = S::InitError,
144        > {
145            let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
146
147            Acceptor::new(acceptor)
148                .map_init_err(|_| {
149                    unreachable!("TLS acceptor service factory does not error on init")
150                })
151                .map_err(TlsError::into_service_error)
152                .map(move |io: TlsStream<TcpStream>| {
153                    if let Some(nodelay) = tcp_nodelay {
154                        set_nodelay(io.get_ref(), nodelay);
155                    }
156                    let peer_addr = io.get_ref().peer_addr().ok();
157                    (io, peer_addr)
158                })
159                .and_then(self.map_err(TlsError::Service))
160        }
161    }
162}
163
164#[cfg(feature = "rustls-0_20")]
165mod rustls_0_20 {
166    use std::io;
167
168    use actix_tls::accept::{
169        rustls::{reexports::ServerConfig, Acceptor, TlsStream},
170        TlsError,
171    };
172
173    use super::*;
174
175    impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
176    where
177        S: ServiceFactory<Request, Config = ()>,
178        S::Future: 'static,
179        S::Error: Into<Response<BoxBody>> + 'static,
180        S::Response: Into<Response<B>> + 'static,
181        <S::Service as Service<Request>>::Future: 'static,
182
183        B: MessageBody + 'static,
184    {
185        /// Create Rustls v0.20 based service.
186        pub fn rustls(
187            self,
188            mut config: ServerConfig,
189        ) -> impl ServiceFactory<
190            TcpStream,
191            Config = (),
192            Response = (),
193            Error = TlsError<io::Error, DispatchError>,
194            InitError = S::InitError,
195        > {
196            let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
197            let mut protos = vec![b"h2".to_vec()];
198            protos.extend_from_slice(&config.alpn_protocols);
199            config.alpn_protocols = protos;
200
201            Acceptor::new(config)
202                .map_init_err(|_| {
203                    unreachable!("TLS acceptor service factory does not error on init")
204                })
205                .map_err(TlsError::into_service_error)
206                .map(move |io: TlsStream<TcpStream>| {
207                    if let Some(nodelay) = tcp_nodelay {
208                        set_nodelay(io.get_ref().0, nodelay);
209                    }
210                    let peer_addr = io.get_ref().0.peer_addr().ok();
211                    (io, peer_addr)
212                })
213                .and_then(self.map_err(TlsError::Service))
214        }
215    }
216}
217
218#[cfg(feature = "rustls-0_21")]
219mod rustls_0_21 {
220    use std::io;
221
222    use actix_tls::accept::{
223        rustls_0_21::{reexports::ServerConfig, Acceptor, TlsStream},
224        TlsError,
225    };
226
227    use super::*;
228
229    impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
230    where
231        S: ServiceFactory<Request, Config = ()>,
232        S::Future: 'static,
233        S::Error: Into<Response<BoxBody>> + 'static,
234        S::Response: Into<Response<B>> + 'static,
235        <S::Service as Service<Request>>::Future: 'static,
236
237        B: MessageBody + 'static,
238    {
239        /// Create Rustls v0.21 based service.
240        pub fn rustls_021(
241            self,
242            mut config: ServerConfig,
243        ) -> impl ServiceFactory<
244            TcpStream,
245            Config = (),
246            Response = (),
247            Error = TlsError<io::Error, DispatchError>,
248            InitError = S::InitError,
249        > {
250            let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
251            let mut protos = vec![b"h2".to_vec()];
252            protos.extend_from_slice(&config.alpn_protocols);
253            config.alpn_protocols = protos;
254
255            Acceptor::new(config)
256                .map_init_err(|_| {
257                    unreachable!("TLS acceptor service factory does not error on init")
258                })
259                .map_err(TlsError::into_service_error)
260                .map(move |io: TlsStream<TcpStream>| {
261                    if let Some(nodelay) = tcp_nodelay {
262                        set_nodelay(io.get_ref().0, nodelay);
263                    }
264                    let peer_addr = io.get_ref().0.peer_addr().ok();
265                    (io, peer_addr)
266                })
267                .and_then(self.map_err(TlsError::Service))
268        }
269    }
270}
271
272#[cfg(feature = "rustls-0_22")]
273mod rustls_0_22 {
274    use std::io;
275
276    use actix_tls::accept::{
277        rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
278        TlsError,
279    };
280
281    use super::*;
282
283    impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
284    where
285        S: ServiceFactory<Request, Config = ()>,
286        S::Future: 'static,
287        S::Error: Into<Response<BoxBody>> + 'static,
288        S::Response: Into<Response<B>> + 'static,
289        <S::Service as Service<Request>>::Future: 'static,
290
291        B: MessageBody + 'static,
292    {
293        /// Create Rustls v0.22 based service.
294        pub fn rustls_0_22(
295            self,
296            mut config: ServerConfig,
297        ) -> impl ServiceFactory<
298            TcpStream,
299            Config = (),
300            Response = (),
301            Error = TlsError<io::Error, DispatchError>,
302            InitError = S::InitError,
303        > {
304            let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
305            let mut protos = vec![b"h2".to_vec()];
306            protos.extend_from_slice(&config.alpn_protocols);
307            config.alpn_protocols = protos;
308
309            Acceptor::new(config)
310                .map_init_err(|_| {
311                    unreachable!("TLS acceptor service factory does not error on init")
312                })
313                .map_err(TlsError::into_service_error)
314                .map(move |io: TlsStream<TcpStream>| {
315                    if let Some(nodelay) = tcp_nodelay {
316                        set_nodelay(io.get_ref().0, nodelay);
317                    }
318                    let peer_addr = io.get_ref().0.peer_addr().ok();
319                    (io, peer_addr)
320                })
321                .and_then(self.map_err(TlsError::Service))
322        }
323    }
324}
325
326#[cfg(feature = "rustls-0_23")]
327mod rustls_0_23 {
328    use std::io;
329
330    use actix_tls::accept::{
331        rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream},
332        TlsError,
333    };
334
335    use super::*;
336
337    impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
338    where
339        S: ServiceFactory<Request, Config = ()>,
340        S::Future: 'static,
341        S::Error: Into<Response<BoxBody>> + 'static,
342        S::Response: Into<Response<B>> + 'static,
343        <S::Service as Service<Request>>::Future: 'static,
344
345        B: MessageBody + 'static,
346    {
347        /// Create Rustls v0.23 based service.
348        pub fn rustls_0_23(
349            self,
350            mut config: ServerConfig,
351        ) -> impl ServiceFactory<
352            TcpStream,
353            Config = (),
354            Response = (),
355            Error = TlsError<io::Error, DispatchError>,
356            InitError = S::InitError,
357        > {
358            let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
359            let mut protos = vec![b"h2".to_vec()];
360            protos.extend_from_slice(&config.alpn_protocols);
361            config.alpn_protocols = protos;
362
363            Acceptor::new(config)
364                .map_init_err(|_| {
365                    unreachable!("TLS acceptor service factory does not error on init")
366                })
367                .map_err(TlsError::into_service_error)
368                .map(move |io: TlsStream<TcpStream>| {
369                    if let Some(nodelay) = tcp_nodelay {
370                        set_nodelay(io.get_ref().0, nodelay);
371                    }
372                    let peer_addr = io.get_ref().0.peer_addr().ok();
373                    (io, peer_addr)
374                })
375                .and_then(self.map_err(TlsError::Service))
376        }
377    }
378}
379
380impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
381where
382    T: AsyncRead + AsyncWrite + Unpin + 'static,
383
384    S: ServiceFactory<Request, Config = ()>,
385    S::Future: 'static,
386    S::Error: Into<Response<BoxBody>> + 'static,
387    S::Response: Into<Response<B>> + 'static,
388    <S::Service as Service<Request>>::Future: 'static,
389
390    B: MessageBody + 'static,
391{
392    type Response = ();
393    type Error = DispatchError;
394    type Config = ();
395    type Service = H2ServiceHandler<T, S::Service, B>;
396    type InitError = S::InitError;
397    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
398
399    fn new_service(&self, _: ()) -> Self::Future {
400        let service = self.srv.new_service(());
401        let cfg = self.cfg.clone();
402        let on_connect_ext = self.on_connect_ext.clone();
403
404        Box::pin(async move {
405            let service = service.await?;
406            Ok(H2ServiceHandler::new(cfg, on_connect_ext, service))
407        })
408    }
409}
410
411/// `Service` implementation for HTTP/2 transport
412pub struct H2ServiceHandler<T, S, B>
413where
414    S: Service<Request>,
415{
416    flow: Rc<HttpFlow<S, (), ()>>,
417    cfg: ServiceConfig,
418    on_connect_ext: Option<Rc<ConnectCallback<T>>>,
419    _phantom: PhantomData<B>,
420}
421
422impl<T, S, B> H2ServiceHandler<T, S, B>
423where
424    S: Service<Request>,
425    S::Error: Into<Response<BoxBody>> + 'static,
426    S::Future: 'static,
427    S::Response: Into<Response<B>> + 'static,
428    B: MessageBody + 'static,
429{
430    fn new(
431        cfg: ServiceConfig,
432        on_connect_ext: Option<Rc<ConnectCallback<T>>>,
433        service: S,
434    ) -> H2ServiceHandler<T, S, B> {
435        H2ServiceHandler {
436            flow: HttpFlow::new(service, (), None),
437            cfg,
438            on_connect_ext,
439            _phantom: PhantomData,
440        }
441    }
442}
443
444impl<T, S, B> Service<(T, Option<net::SocketAddr>)> for H2ServiceHandler<T, S, B>
445where
446    T: AsyncRead + AsyncWrite + Unpin,
447    S: Service<Request>,
448    S::Error: Into<Response<BoxBody>> + 'static,
449    S::Future: 'static,
450    S::Response: Into<Response<B>> + 'static,
451    B: MessageBody + 'static,
452{
453    type Response = ();
454    type Error = DispatchError;
455    type Future = H2ServiceHandlerResponse<T, S, B>;
456
457    fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
458        self.flow.service.poll_ready(cx).map_err(|err| {
459            let err = err.into();
460            error!("Service readiness error: {:?}", err);
461            DispatchError::Service(err)
462        })
463    }
464
465    fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
466        let on_connect_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
467
468        H2ServiceHandlerResponse {
469            state: State::Handshake(
470                Some(Rc::clone(&self.flow)),
471                Some(self.cfg.clone()),
472                addr,
473                on_connect_data,
474                handshake_with_timeout(io, &self.cfg),
475            ),
476        }
477    }
478}
479
480enum State<T, S: Service<Request>, B: MessageBody>
481where
482    T: AsyncRead + AsyncWrite + Unpin,
483    S::Future: 'static,
484{
485    Handshake(
486        Option<Rc<HttpFlow<S, (), ()>>>,
487        Option<ServiceConfig>,
488        Option<net::SocketAddr>,
489        OnConnectData,
490        HandshakeWithTimeout<T>,
491    ),
492    Established(Dispatcher<T, S, B, (), ()>),
493}
494
495pub struct H2ServiceHandlerResponse<T, S, B>
496where
497    T: AsyncRead + AsyncWrite + Unpin,
498    S: Service<Request>,
499    S::Error: Into<Response<BoxBody>> + 'static,
500    S::Future: 'static,
501    S::Response: Into<Response<B>> + 'static,
502    B: MessageBody + 'static,
503{
504    state: State<T, S, B>,
505}
506
507impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
508where
509    T: AsyncRead + AsyncWrite + Unpin,
510    S: Service<Request>,
511    S::Error: Into<Response<BoxBody>> + 'static,
512    S::Future: 'static,
513    S::Response: Into<Response<B>> + 'static,
514    B: MessageBody,
515{
516    type Output = Result<(), DispatchError>;
517
518    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
519        match self.state {
520            State::Handshake(
521                ref mut srv,
522                ref mut config,
523                ref peer_addr,
524                ref mut conn_data,
525                ref mut handshake,
526            ) => match ready!(Pin::new(handshake).poll(cx)) {
527                Ok((conn, timer)) => {
528                    let on_connect_data = mem::take(conn_data);
529
530                    self.state = State::Established(Dispatcher::new(
531                        conn,
532                        srv.take().unwrap(),
533                        config.take().unwrap(),
534                        *peer_addr,
535                        on_connect_data,
536                        timer,
537                    ));
538
539                    self.poll(cx)
540                }
541
542                Err(err) => {
543                    trace!("H2 handshake error: {}", err);
544                    Poll::Ready(Err(err))
545                }
546            },
547
548            State::Established(ref mut disp) => Pin::new(disp).poll(cx),
549        }
550    }
551}