xitca_web/
server.rs

1use std::{fmt, future::Future, sync::Arc, time::Duration};
2
3use futures_core::stream::Stream;
4use xitca_http::{
5    HttpServiceBuilder,
6    body::RequestBody,
7    config::{DEFAULT_HEADER_LIMIT, DEFAULT_READ_BUF_LIMIT, DEFAULT_WRITE_BUF_LIMIT, HttpServiceConfig},
8};
9use xitca_server::{Builder, ServerFuture, net::IntoListener};
10use xitca_service::ServiceExt;
11
12use crate::{
13    bytes::Bytes,
14    http::{Request, RequestExt, Response},
15    service::{Service, ready::ReadyService},
16};
17
18/// multi protocol handling http server
19pub struct HttpServer<
20    S,
21    const HEADER_LIMIT: usize = DEFAULT_HEADER_LIMIT,
22    const READ_BUF_LIMIT: usize = DEFAULT_READ_BUF_LIMIT,
23    const WRITE_BUF_LIMIT: usize = DEFAULT_WRITE_BUF_LIMIT,
24> {
25    service: Arc<S>,
26    builder: Builder,
27    config: HttpServiceConfig<HEADER_LIMIT, READ_BUF_LIMIT, WRITE_BUF_LIMIT>,
28}
29
30impl<S> HttpServer<S>
31where
32    S: Send + Sync + 'static,
33{
34    pub fn serve(service: S) -> Self {
35        Self {
36            service: Arc::new(service),
37            builder: Builder::new(),
38            config: HttpServiceConfig::default(),
39        }
40    }
41}
42
43impl<S, const HEADER_LIMIT: usize, const READ_BUF_LIMIT: usize, const WRITE_BUF_LIMIT: usize>
44    HttpServer<S, HEADER_LIMIT, READ_BUF_LIMIT, WRITE_BUF_LIMIT>
45where
46    S: Send + Sync + 'static,
47{
48    /// Set number of threads dedicated to accepting connections.
49    ///
50    /// Default set to 1.
51    ///
52    /// # Panics:
53    /// When receive 0 as number of server thread.
54    pub fn server_threads(mut self, num: usize) -> Self {
55        self.builder = self.builder.server_threads(num);
56        self
57    }
58
59    /// Set number of workers to start.
60    ///
61    /// Default set to available logical cpu as workers count.
62    ///
63    /// # Panics:
64    /// When received 0 as number of worker thread.
65    pub fn worker_threads(mut self, num: usize) -> Self {
66        self.builder = self.builder.worker_threads(num);
67        self
68    }
69
70    /// Set max number of threads for each worker's blocking task thread pool.
71    ///
72    /// One thread pool is set up **per worker**; not shared across workers.
73    pub fn worker_max_blocking_threads(mut self, num: usize) -> Self {
74        self.builder = self.builder.worker_max_blocking_threads(num);
75        self
76    }
77
78    /// Disable signal listening.
79    ///
80    /// `tokio::signal` is used for listening and it only functions in tokio runtime 1.x.
81    /// Disabling it would enable server runs in other async runtimes.
82    pub fn disable_signal(mut self) -> Self {
83        self.builder = self.builder.disable_signal();
84        self
85    }
86
87    pub fn backlog(mut self, num: u32) -> Self {
88        self.builder = self.builder.backlog(num);
89        self
90    }
91
92    /// Disable vectored write even when IO is able to perform it.
93    ///
94    /// This is beneficial when dealing with small size of response body.
95    pub fn disable_vectored_write(mut self) -> Self {
96        self.config = self.config.disable_vectored_write();
97        self
98    }
99
100    /// Change keep alive duration for Http/1 connection.
101    ///
102    /// Connection kept idle for this duration would be closed.
103    pub fn keep_alive_timeout(mut self, dur: Duration) -> Self {
104        self.config = self.config.keep_alive_timeout(dur);
105        self
106    }
107
108    /// Change request timeout for Http/1 connection.
109    ///
110    /// Connection can not finish it's request for this duration would be closed.
111    ///
112    /// This timeout is also used in Http/2 connection handshake phrase.
113    pub fn request_head_timeout(mut self, dur: Duration) -> Self {
114        self.config = self.config.request_head_timeout(dur);
115        self
116    }
117
118    /// Change tls accept timeout for Http/1 and Http/2 connection.
119    ///
120    /// Connection can not finish tls handshake for this duration would be closed.
121    pub fn tls_accept_timeout(mut self, dur: Duration) -> Self {
122        self.config = self.config.tls_accept_timeout(dur);
123        self
124    }
125
126    /// Change max size for request head.
127    ///
128    /// Request has a bigger head than it would be reject with error.
129    /// Request body has a bigger continuous read would be force to yield.
130    ///
131    /// Default to 1mb.
132    pub fn max_read_buf_size<const READ_BUF_LIMIT_2: usize>(
133        self,
134    ) -> HttpServer<S, HEADER_LIMIT, READ_BUF_LIMIT_2, WRITE_BUF_LIMIT> {
135        self.mutate_const_generic::<HEADER_LIMIT, READ_BUF_LIMIT_2, WRITE_BUF_LIMIT>()
136    }
137
138    /// Change max size for write buffer size.
139    ///
140    /// When write buffer hit limit it would force a drain write to Io stream until it's empty
141    /// (or connection closed by error or remote peer).
142    ///
143    /// Default to 408kb.
144    pub fn max_write_buf_size<const WRITE_BUF_LIMIT_2: usize>(
145        self,
146    ) -> HttpServer<S, HEADER_LIMIT, READ_BUF_LIMIT, WRITE_BUF_LIMIT_2> {
147        self.mutate_const_generic::<HEADER_LIMIT, READ_BUF_LIMIT, WRITE_BUF_LIMIT_2>()
148    }
149
150    /// Change max header fields for one request.
151    ///
152    /// Default to 64.
153    pub fn max_request_headers<const HEADER_LIMIT_2: usize>(
154        self,
155    ) -> HttpServer<S, HEADER_LIMIT_2, READ_BUF_LIMIT, WRITE_BUF_LIMIT> {
156        self.mutate_const_generic::<HEADER_LIMIT_2, READ_BUF_LIMIT, WRITE_BUF_LIMIT>()
157    }
158
159    #[doc(hidden)]
160    pub fn on_worker_start<FS, Fut>(mut self, on_start: FS) -> Self
161    where
162        FS: Fn() -> Fut + Send + Sync + 'static,
163        Fut: Future + Send + 'static,
164    {
165        self.builder = self.builder.on_worker_start(on_start);
166        self
167    }
168
169    #[cfg(not(target_family = "wasm"))]
170    pub fn bind<A, ResB, BE>(mut self, addr: A) -> std::io::Result<Self>
171    where
172        A: std::net::ToSocketAddrs,
173        S: Service + 'static,
174        S::Response: ReadyService + Service<Request<RequestExt<RequestBody>>, Response = Response<ResB>> + 'static,
175        S::Error: fmt::Debug,
176        <S::Response as Service<Request<RequestExt<RequestBody>>>>::Error: fmt::Debug,
177        ResB: Stream<Item = Result<Bytes, BE>> + 'static,
178        BE: fmt::Debug + 'static,
179    {
180        let config = self.config;
181        let service = self.service.clone().enclosed(HttpServiceBuilder::with_config(config));
182        self.builder = self.builder.bind("xitca-web", addr, service)?;
183        Ok(self)
184    }
185
186    pub fn listen<ResB, BE, L>(mut self, listener: L) -> std::io::Result<Self>
187    where
188        S: Service + 'static,
189        S::Response: ReadyService + Service<Request<RequestExt<RequestBody>>, Response = Response<ResB>> + 'static,
190        S::Error: fmt::Debug,
191        <S::Response as Service<Request<RequestExt<RequestBody>>>>::Error: fmt::Debug,
192        ResB: Stream<Item = Result<Bytes, BE>> + 'static,
193        BE: fmt::Debug + 'static,
194        L: IntoListener + 'static,
195    {
196        let config = self.config;
197        let service = self.service.clone().enclosed(HttpServiceBuilder::with_config(config));
198        self.builder = self.builder.listen("xitca-web", listener, service);
199        Ok(self)
200    }
201
202    #[cfg(feature = "openssl")]
203    pub fn bind_openssl<A: std::net::ToSocketAddrs, ResB, BE>(
204        mut self,
205        addr: A,
206        mut builder: xitca_tls::openssl::ssl::SslAcceptorBuilder,
207    ) -> std::io::Result<Self>
208    where
209        S: Service + 'static,
210        S::Response: ReadyService + Service<Request<RequestExt<RequestBody>>, Response = Response<ResB>> + 'static,
211        S::Error: fmt::Debug,
212        <S::Response as Service<Request<RequestExt<RequestBody>>>>::Error: fmt::Debug,
213        ResB: Stream<Item = Result<Bytes, BE>> + 'static,
214        BE: fmt::Debug + 'static,
215    {
216        let config = self.config;
217
218        const H11: &[u8] = b"\x08http/1.1";
219
220        const H2: &[u8] = b"\x02h2";
221
222        builder.set_alpn_select_callback(|_, protocols| {
223            if protocols.windows(3).any(|window| window == H2) {
224                #[cfg(feature = "http2")]
225                {
226                    Ok(b"h2")
227                }
228                #[cfg(not(feature = "http2"))]
229                Err(xitca_tls::openssl::ssl::AlpnError::ALERT_FATAL)
230            } else if protocols.windows(9).any(|window| window == H11) {
231                Ok(b"http/1.1")
232            } else {
233                Err(xitca_tls::openssl::ssl::AlpnError::NOACK)
234            }
235        });
236
237        #[cfg(not(feature = "http2"))]
238        let protos = H11.iter().cloned().collect::<Vec<_>>();
239
240        #[cfg(feature = "http2")]
241        let protos = H11.iter().chain(H2).cloned().collect::<Vec<_>>();
242
243        builder.set_alpn_protos(&protos)?;
244
245        let acceptor = builder.build();
246
247        let service = self
248            .service
249            .clone()
250            .enclosed(HttpServiceBuilder::with_config(config).openssl(acceptor));
251
252        self.builder = self.builder.bind("xitca-web-openssl", addr, service)?;
253
254        Ok(self)
255    }
256
257    #[cfg(feature = "rustls")]
258    pub fn bind_rustls<A: std::net::ToSocketAddrs, ResB, BE>(
259        mut self,
260        addr: A,
261        #[cfg_attr(not(all(feature = "http1", feature = "http2")), allow(unused_mut))]
262        mut config: xitca_tls::rustls::ServerConfig,
263    ) -> std::io::Result<Self>
264    where
265        S: Service + 'static,
266        S::Response: ReadyService + Service<Request<RequestExt<RequestBody>>, Response = Response<ResB>> + 'static,
267        S::Error: fmt::Debug,
268        <S::Response as Service<Request<RequestExt<RequestBody>>>>::Error: fmt::Debug,
269        ResB: Stream<Item = Result<Bytes, BE>> + 'static,
270        BE: fmt::Debug + 'static,
271    {
272        let service_config = self.config;
273
274        #[cfg(feature = "http2")]
275        config.alpn_protocols.push("h2".into());
276
277        #[cfg(feature = "http1")]
278        config.alpn_protocols.push("http/1.1".into());
279
280        let config = std::sync::Arc::new(config);
281
282        let service = self
283            .service
284            .clone()
285            .enclosed(HttpServiceBuilder::with_config(service_config).rustls(config));
286
287        self.builder = self.builder.bind("xitca-web-rustls", addr, service)?;
288
289        Ok(self)
290    }
291
292    #[cfg(unix)]
293    pub fn bind_unix<P: AsRef<std::path::Path>, ResB, BE>(mut self, path: P) -> std::io::Result<Self>
294    where
295        S: Service + 'static,
296        S::Response: ReadyService + Service<Request<RequestExt<RequestBody>>, Response = Response<ResB>> + 'static,
297        S::Error: fmt::Debug,
298        <S::Response as Service<Request<RequestExt<RequestBody>>>>::Error: fmt::Debug,
299        ResB: Stream<Item = Result<Bytes, BE>> + 'static,
300        BE: fmt::Debug + 'static,
301    {
302        let config = self.config;
303        let service = self.service.clone().enclosed(HttpServiceBuilder::with_config(config));
304        self.builder = self.builder.bind_unix("xitca-web", path, service)?;
305        Ok(self)
306    }
307
308    #[cfg(feature = "http3")]
309    pub fn bind_h3<A: std::net::ToSocketAddrs, ResB, BE>(
310        mut self,
311        addr: A,
312        config: xitca_io::net::QuicConfig,
313    ) -> std::io::Result<Self>
314    where
315        S: Service + 'static,
316        S::Response: ReadyService + Service<Request<RequestExt<RequestBody>>, Response = Response<ResB>> + 'static,
317        S::Error: fmt::Debug,
318        <S::Response as Service<Request<RequestExt<RequestBody>>>>::Error: fmt::Debug,
319        ResB: Stream<Item = Result<Bytes, BE>> + 'static,
320        BE: fmt::Debug + 'static,
321    {
322        let service = self
323            .service
324            .clone()
325            .enclosed(HttpServiceBuilder::with_config(self.config));
326
327        self.builder = self.builder.bind_h3("xitca-web", addr, config, service)?;
328        Ok(self)
329    }
330
331    pub fn run(self) -> ServerFuture {
332        self.builder.build()
333    }
334
335    fn mutate_const_generic<const HEADER_LIMIT2: usize, const READ_BUF_LIMIT2: usize, const WRITE_BUF_LIMIT2: usize>(
336        self,
337    ) -> HttpServer<S, HEADER_LIMIT2, READ_BUF_LIMIT2, WRITE_BUF_LIMIT2> {
338        HttpServer {
339            service: self.service,
340            builder: self.builder,
341            config: self
342                .config
343                .mutate_const_generic::<HEADER_LIMIT2, READ_BUF_LIMIT2, WRITE_BUF_LIMIT2>(),
344        }
345    }
346}