Skip to main content

ntex/http/
service.rs

1use crate::error::{Error, IntoFailure};
2use crate::io::{Filter, Io, types};
3use crate::service::{IntoServiceFactory, RequestState, pipeline::PipelineFactory};
4use crate::{Ctx, Service, ServiceFactory};
5
6use super::error::{DispatchError, H2Error, ResponseError};
7use super::{Request, Response, config::DispatcherConfig, h1, h2};
8
9/// An HTTP/1.1 and HTTP/2 transport service.
10///
11/// The protocol is selected from
12/// [`HttpProtocol`](crate::http::HttpProtocol) metadata attached to the I/O
13/// stream, normally by a TLS ALPN acceptor. Connections without HTTP/2
14/// metadata are handled as HTTP/1.
15#[derive(derive_more::Debug)]
16#[debug("HttpService")]
17pub struct HttpService<F, Req: RequestState<Io<F>>, Err> {
18    sf: super::HttpPipeline<Req::State, Err>,
19    /// Without a control service the default action is applied to every event.
20    h1_ctl: Option<super::Ctl1Pipeline<Req::State, F, Err>>,
21    h2_ctl: super::Ctl2Pipeline<Req::State>,
22    config: DispatcherConfig,
23}
24
25impl<F, Req, Err> HttpService<F, Req, Err>
26where
27    F: Filter + 'static,
28    Req: RequestState<Io<F>>,
29    Req::State: Clone,
30    Err: ResponseError + 'static,
31{
32    #[must_use]
33    /// Creates a service that supports HTTP/1.1 and HTTP/2.
34    pub fn new<H>(sf: impl IntoServiceFactory<H, Req::State, Request>) -> Self
35    where
36        H: ServiceFactory<Req::State, Request, Error = Err> + 'static,
37        H::Res: Into<Response>,
38        H::InitError: IntoFailure,
39    {
40        HttpService {
41            sf: PipelineFactory::new(
42                sf.into_factory()
43                    .map(Into::into)
44                    .map_init_err(|e| DispatchError::Control(e.fail())),
45            ),
46            h1_ctl: None,
47            h2_ctl: PipelineFactory::new(h2::DefaultControlService),
48            config: DispatcherConfig::default(),
49        }
50    }
51
52    #[must_use]
53    /// Creates an HTTP/1-only service.
54    pub fn h1<H>(sf: impl IntoServiceFactory<H, Req::State, Request>) -> h1::H1Service<F, Req, Err>
55    where
56        H: ServiceFactory<Req::State, Request, Error = Err> + 'static,
57        H::Res: Into<Response>,
58        H::InitError: IntoFailure,
59    {
60        h1::H1Service::new(sf)
61    }
62
63    #[must_use]
64    /// Creates an HTTP/2-only service.
65    pub fn h2<H>(sf: impl IntoServiceFactory<H, Req::State, Request>) -> h2::H2Service<F, Req, Err>
66    where
67        H: ServiceFactory<Req::State, Request, Error = Err> + 'static,
68        H::Res: Into<Response>,
69        H::InitError: IntoFailure,
70    {
71        h2::H2Service::new(sf)
72    }
73}
74
75impl<F, Req, Err> HttpService<F, Req, Err>
76where
77    F: Filter,
78    Req: RequestState<Io<F>>,
79    Err: ResponseError + 'static,
80{
81    #[must_use]
82    /// Provides the HTTP/1 control service.
83    ///
84    /// The service receives the lifecycle events described by
85    /// [`h1::Control`]. Unless this method is called, the default action is
86    /// applied to each event without a service call.
87    pub fn h1_control<Ctl>(
88        self,
89        ctl: impl IntoServiceFactory<Ctl, Req::State, h1::Control<F, Err>>,
90    ) -> Self
91    where
92        Ctl: ServiceFactory<Req::State, h1::Control<F, Err>, Res = h1::ControlAck<F>> + 'static,
93        Ctl::Error: IntoFailure,
94        Ctl::InitError: IntoFailure,
95    {
96        HttpService {
97            sf: self.sf,
98            h1_ctl: Some(PipelineFactory::new(
99                ctl.into_factory()
100                    .map_err(|e| DispatchError::Service(e.fail()))
101                    .map_init_err(|e| DispatchError::Control(e.fail())),
102            )),
103            h2_ctl: self.h2_ctl,
104            config: self.config,
105        }
106    }
107
108    #[must_use]
109    /// Provides the HTTP/2 control service.
110    ///
111    /// A default service that acknowledges each HTTP/2 control event is used
112    /// unless this method is called.
113    pub fn h2_control<Ctl>(
114        self,
115        ctl: impl IntoServiceFactory<Ctl, Req::State, h2::Control<Error<H2Error>>>,
116    ) -> Self
117    where
118        Ctl:
119            ServiceFactory<Req::State, h2::Control<Error<H2Error>>, Res = h2::ControlAck> + 'static,
120        Ctl::Error: IntoFailure,
121        Ctl::InitError: IntoFailure,
122    {
123        HttpService {
124            sf: self.sf,
125            h1_ctl: self.h1_ctl,
126            h2_ctl: PipelineFactory::new(
127                ctl.into_factory()
128                    .map_err(|e| DispatchError::Service(e.fail()))
129                    .map_init_err(|e| DispatchError::Control(e.fail())),
130            ),
131            config: self.config,
132        }
133    }
134}
135
136impl<St, F, Req, Err> Service<St, Req> for HttpService<F, Req, Err>
137where
138    F: Filter + 'static,
139    Req: RequestState<Io<F>>,
140    Req::State: Clone,
141    Err: ResponseError + 'static,
142{
143    type Res = ();
144    type Error = DispatchError;
145
146    async fn call(&self, req: Req, _: Ctx<'_, Self, St>) -> Result<Self::Res, Self::Error> {
147        let (st, io) = req.unpack();
148
149        let svc = self.sf.create(st.clone()).await?;
150
151        let id = self.config.next_id();
152        let ioref = io.get_ref();
153        let (_guard, inflight) = self.config.insert_io(&ioref);
154
155        if io.query::<types::HttpProtocol>().get() == Some(types::HttpProtocol::Http2) {
156            log::trace!(
157                "{}: New http2 connection {id}, peer address {:?}, in-flight: {inflight}",
158                io.tag(),
159                io.query::<types::PeerAddr>().get(),
160            );
161            let ctl = self.h2_ctl.create(st).await?;
162
163            h2::handle(id, io.into(), svc, ctl).await
164        } else {
165            log::trace!(
166                "{}: New http1 connection {id}, peer address {:?}, in-flight: {inflight}",
167                io.tag(),
168                io.query::<types::PeerAddr>().get(),
169            );
170            let ctl = match &self.h1_ctl {
171                Some(ctl) => Some(ctl.create(st).await?),
172                None => None,
173            };
174
175            h1::handle_io(id, io, svc, ctl, self.config.clone()).await
176        }
177    }
178
179    async fn ready(&self, _: Ctx<'_, Self, St>) -> Result<(), Self::Error> {
180        Ok(())
181    }
182
183    async fn shutdown(&self, _: crate::Ctx<'_, Self, St>) {
184        // check inflight connections
185        let inflight = self.config.shutdown();
186        if inflight != 0 {
187            log::trace!("Shutting down service, in-flight connections: {inflight}");
188
189            self.config.wait_shutdown().await;
190            log::trace!("Shutting down is complected");
191        }
192    }
193}