scuffle_http/
server.rs

1use std::fmt::Debug;
2use std::net::SocketAddr;
3
4use crate::error::HttpError;
5use crate::service::{HttpService, HttpServiceFactory};
6
7/// The HTTP server.
8///
9/// This struct is the main entry point for creating and running an HTTP server.
10///
11/// Start creating a new server by calling [`HttpServer::builder`].
12#[derive(Debug, Clone, bon::Builder)]
13#[builder(state_mod(vis = "pub(crate)"))]
14#[allow(dead_code)]
15pub struct HttpServer<F> {
16    /// The [`scuffle_context::Context`] this server will live by.
17    #[builder(default = scuffle_context::Context::global())]
18    ctx: scuffle_context::Context,
19    /// The number of worker tasks to spawn for each server backend.
20    #[builder(default = 1)]
21    worker_tasks: usize,
22    /// The service factory that will be used to create new services.
23    service_factory: F,
24    /// The address to bind to.
25    ///
26    /// Use `[::]` for a dual-stack listener.
27    /// For example, use `[::]:80` to bind to port 80 on both IPv4 and IPv6.
28    bind: SocketAddr,
29    /// Enable HTTP/1.1.
30    #[builder(default = true)]
31    #[cfg(feature = "http1")]
32    enable_http1: bool,
33    /// Enable HTTP/2.
34    #[builder(default = true)]
35    #[cfg(feature = "http2")]
36    enable_http2: bool,
37    #[builder(default = false, setters(vis = "", name = enable_http3_internal))]
38    #[cfg(feature = "http3")]
39    enable_http3: bool,
40    #[builder(default = false, setters(vis = "", name = enable_webtransport_internal))]
41    #[cfg(feature = "webtransport")]
42    enable_webtransport: bool,
43    #[builder(default = 1, setters(vis = "", name = max_webtransport_sessions_internal))]
44    #[cfg(feature = "webtransport")]
45    max_webtransport_sessions: u64,
46    /// rustls config.
47    ///
48    /// Use this field to set the server into TLS mode.
49    /// It will only accept TLS connections when this is set.
50    #[cfg(feature = "tls-rustls")]
51    rustls_config: Option<tokio_rustls::rustls::ServerConfig>,
52}
53
54#[cfg(feature = "http3")]
55impl<F, S> HttpServerBuilder<F, S>
56where
57    S: http_server_builder::State,
58    S::EnableHttp3: http_server_builder::IsUnset,
59    S::RustlsConfig: http_server_builder::IsSet,
60{
61    /// Enable HTTP/3 support.
62    ///
63    /// First enable TLS by calling [`rustls_config`](HttpServerBuilder::rustls_config) to enable HTTP/3.
64    pub fn enable_http3(self, enable_http3: bool) -> HttpServerBuilder<F, http_server_builder::SetEnableHttp3<S>> {
65        self.enable_http3_internal(enable_http3)
66    }
67}
68
69#[cfg(feature = "webtransport")]
70impl<F, S> HttpServerBuilder<F, S>
71where
72    S: http_server_builder::State,
73    S::EnableWebtransport: http_server_builder::IsUnset,
74    S::EnableHttp3: http_server_builder::IsSet,
75{
76    /// Enable WebTransport support.
77    ///
78    /// First enable HTTP/3 by calling [`enable_http3`](HttpServerBuilder::enable_http3) to enable WebTransport.
79    pub fn enable_webtransport(
80        self,
81        enable_webtransport: bool,
82    ) -> HttpServerBuilder<F, http_server_builder::SetEnableWebtransport<S>> {
83        self.enable_webtransport_internal(enable_webtransport)
84    }
85}
86
87#[cfg(feature = "webtransport")]
88impl<F, S> HttpServerBuilder<F, S>
89where
90    S: http_server_builder::State,
91    S::MaxWebtransportSessions: http_server_builder::IsUnset,
92    S::EnableWebtransport: http_server_builder::IsSet,
93{
94    /// Set the maximum number of concurrent WebTransport sessions.
95    ///
96    /// Corresponds to [h3::server::Builder::max_webtransport_sessions].
97    ///
98    /// Default is 1 when WebTransport is enabled.
99    pub fn max_webtransport_sessions(
100        self,
101        max_webtransport_sessions: u64,
102    ) -> HttpServerBuilder<F, http_server_builder::SetMaxWebtransportSessions<S>> {
103        self.max_webtransport_sessions_internal(max_webtransport_sessions)
104    }
105}
106
107#[cfg(feature = "tower")]
108impl<M, S> HttpServerBuilder<crate::service::TowerMakeServiceFactory<M, ()>, S>
109where
110    M: tower::MakeService<(), crate::IncomingRequest> + Send,
111    M::Future: Send,
112    M::Service: crate::service::HttpService,
113    S: http_server_builder::State,
114    S::ServiceFactory: http_server_builder::IsUnset,
115{
116    /// Same as calling `service_factory(tower_make_service_factory(tower_make_service))`.
117    ///
118    /// # See Also
119    ///
120    /// - [`service_factory`](HttpServerBuilder::service_factory)
121    /// - [`tower_make_service_factory`](crate::service::tower_make_service_factory)
122    pub fn tower_make_service_factory(
123        self,
124        tower_make_service: M,
125    ) -> HttpServerBuilder<crate::service::TowerMakeServiceFactory<M, ()>, http_server_builder::SetServiceFactory<S>> {
126        self.service_factory(crate::service::tower_make_service_factory(tower_make_service))
127    }
128}
129
130#[cfg(feature = "tower")]
131impl<M, S> HttpServerBuilder<crate::service::TowerMakeServiceWithAddrFactory<M>, S>
132where
133    M: tower::MakeService<SocketAddr, crate::IncomingRequest> + Send,
134    M::Future: Send,
135    M::Service: crate::service::HttpService,
136    S: http_server_builder::State,
137    S::ServiceFactory: http_server_builder::IsUnset,
138{
139    /// Same as calling `service_factory(tower_make_service_with_addr_factory(tower_make_service))`.
140    ///
141    /// # See Also
142    ///
143    /// - [`service_factory`](HttpServerBuilder::service_factory)
144    /// - [`tower_make_service_with_addr_factory`](crate::service::tower_make_service_with_addr_factory)
145    pub fn tower_make_service_with_addr(
146        self,
147        tower_make_service: M,
148    ) -> HttpServerBuilder<crate::service::TowerMakeServiceWithAddrFactory<M>, http_server_builder::SetServiceFactory<S>>
149    {
150        self.service_factory(crate::service::tower_make_service_with_addr_factory(tower_make_service))
151    }
152}
153
154#[cfg(feature = "tower")]
155impl<M, T, S> HttpServerBuilder<crate::service::TowerMakeServiceFactory<M, T>, S>
156where
157    M: tower::MakeService<T, crate::IncomingRequest> + Send,
158    M::Future: Send,
159    M::Service: crate::service::HttpService,
160    T: Clone + Send,
161    S: http_server_builder::State,
162    S::ServiceFactory: http_server_builder::IsUnset,
163{
164    /// Same as calling `service_factory(custom_tower_make_service_factory(tower_make_service, target))`.
165    ///
166    /// # See Also
167    ///
168    /// - [`service_factory`](HttpServerBuilder::service_factory)
169    /// - [`custom_tower_make_service_factory`](crate::service::custom_tower_make_service_factory)
170    pub fn custom_tower_make_service_factory(
171        self,
172        tower_make_service: M,
173        target: T,
174    ) -> HttpServerBuilder<crate::service::TowerMakeServiceFactory<M, T>, http_server_builder::SetServiceFactory<S>> {
175        self.service_factory(crate::service::custom_tower_make_service_factory(tower_make_service, target))
176    }
177}
178
179impl<F> HttpServer<F>
180where
181    F: HttpServiceFactory + Clone + Send + 'static,
182    F::Error: std::error::Error + Send,
183    F::Service: Clone + Send + 'static,
184    <F::Service as HttpService>::Error: std::error::Error + Send + Sync,
185    <F::Service as HttpService>::ResBody: Send,
186    <<F::Service as HttpService>::ResBody as http_body::Body>::Data: Send,
187    <<F::Service as HttpService>::ResBody as http_body::Body>::Error: std::error::Error + Send + Sync,
188{
189    #[cfg(feature = "tls-rustls")]
190    fn set_alpn_protocols(&mut self) {
191        let Some(rustls_config) = &mut self.rustls_config else {
192            return;
193        };
194
195        // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
196        if rustls_config.alpn_protocols.is_empty() {
197            #[cfg(feature = "http1")]
198            if self.enable_http1 {
199                rustls_config.alpn_protocols.push(b"http/1.0".to_vec());
200                rustls_config.alpn_protocols.push(b"http/1.1".to_vec());
201            }
202
203            #[cfg(feature = "http2")]
204            if self.enable_http2 {
205                rustls_config.alpn_protocols.push(b"h2".to_vec());
206                rustls_config.alpn_protocols.push(b"h2c".to_vec());
207            }
208
209            #[cfg(feature = "http3")]
210            if self.enable_http3 {
211                rustls_config.alpn_protocols.push(b"h3".to_vec());
212                rustls_config.alpn_protocols.push(b"h3-32".to_vec());
213                rustls_config.alpn_protocols.push(b"h3-31".to_vec());
214                rustls_config.alpn_protocols.push(b"h3-30".to_vec());
215                rustls_config.alpn_protocols.push(b"h3-29".to_vec());
216            }
217        }
218    }
219
220    /// Run the server.
221    ///
222    /// This will:
223    ///
224    /// - Start listening on all configured interfaces for incoming connections.
225    /// - Accept all incoming connections.
226    /// - Handle incoming requests by passing them to the configured service factory.
227    pub async fn run(#[allow(unused_mut)] mut self) -> Result<(), HttpError<F>> {
228        #[cfg(feature = "tls-rustls")]
229        self.set_alpn_protocols();
230
231        #[cfg(all(not(any(feature = "http1", feature = "http2")), feature = "tls-rustls"))]
232        let start_tcp_backend = false;
233        #[cfg(all(feature = "http1", not(feature = "http2")))]
234        let start_tcp_backend = self.enable_http1;
235        #[cfg(all(not(feature = "http1"), feature = "http2"))]
236        let start_tcp_backend = self.enable_http2;
237        #[cfg(all(feature = "http1", feature = "http2"))]
238        let start_tcp_backend = self.enable_http1 || self.enable_http2;
239
240        #[cfg(feature = "tls-rustls")]
241        if let Some(_rustls_config) = self.rustls_config {
242            #[cfg(not(feature = "http3"))]
243            let enable_http3 = false;
244            #[cfg(feature = "http3")]
245            let enable_http3 = self.enable_http3;
246
247            match (start_tcp_backend, enable_http3) {
248                #[cfg(feature = "http3")]
249                (false, true) => {
250                    let builder = crate::backend::h3::Http3Backend::builder()
251                        .ctx(self.ctx)
252                        .worker_tasks(self.worker_tasks)
253                        .service_factory(self.service_factory)
254                        .bind(self.bind)
255                        .rustls_config(_rustls_config);
256
257                    #[cfg(feature = "webtransport")]
258                    let builder = builder
259                        .enable_webtransport(self.enable_webtransport)
260                        .max_webtransport_sessions(self.max_webtransport_sessions);
261
262                    return builder.build().run().await;
263                }
264                #[cfg(any(feature = "http1", feature = "http2"))]
265                (true, false) => {
266                    let builder = crate::backend::hyper::HyperBackend::builder()
267                        .ctx(self.ctx)
268                        .worker_tasks(self.worker_tasks)
269                        .service_factory(self.service_factory)
270                        .bind(self.bind)
271                        .rustls_config(_rustls_config);
272
273                    #[cfg(feature = "http1")]
274                    let builder = builder.http1_enabled(self.enable_http1);
275
276                    #[cfg(feature = "http2")]
277                    let builder = builder.http2_enabled(self.enable_http2);
278
279                    return builder.build().run().await;
280                }
281                #[cfg(all(any(feature = "http1", feature = "http2"), feature = "http3"))]
282                (true, true) => {
283                    let builder = crate::backend::hyper::HyperBackend::builder()
284                        .ctx(self.ctx.clone())
285                        .worker_tasks(self.worker_tasks)
286                        .service_factory(self.service_factory.clone())
287                        .bind(self.bind)
288                        .rustls_config(_rustls_config.clone());
289
290                    #[cfg(feature = "http1")]
291                    let builder = builder.http1_enabled(self.enable_http1);
292
293                    #[cfg(feature = "http2")]
294                    let builder = builder.http2_enabled(self.enable_http2);
295
296                    let hyper = std::pin::pin!(builder.build().run());
297
298                    let http3_builder = crate::backend::h3::Http3Backend::builder()
299                        .ctx(self.ctx)
300                        .worker_tasks(self.worker_tasks)
301                        .service_factory(self.service_factory)
302                        .bind(self.bind)
303                        .rustls_config(_rustls_config);
304
305                    #[cfg(feature = "webtransport")]
306                    let http3_builder = http3_builder
307                        .enable_webtransport(self.enable_webtransport)
308                        .max_webtransport_sessions(self.max_webtransport_sessions);
309
310                    let http3 = http3_builder.build().run();
311                    let http3 = std::pin::pin!(http3);
312
313                    let res = futures::future::select(hyper, http3).await;
314                    match res {
315                        futures::future::Either::Left((res, _)) => return res,
316                        futures::future::Either::Right((res, _)) => return res,
317                    }
318                }
319                _ => return Ok(()),
320            }
321
322            // This line must be unreachable
323        }
324
325        // At this point we know that we are not using TLS either
326        // - because the feature is disabled
327        // - or because it's enabled but the config is None.
328
329        #[cfg(any(feature = "http1", feature = "http2"))]
330        if start_tcp_backend {
331            let builder = crate::backend::hyper::HyperBackend::builder()
332                .ctx(self.ctx)
333                .worker_tasks(self.worker_tasks)
334                .service_factory(self.service_factory)
335                .bind(self.bind);
336
337            #[cfg(feature = "http1")]
338            let builder = builder.http1_enabled(self.enable_http1);
339
340            #[cfg(feature = "http2")]
341            let builder = builder.http2_enabled(self.enable_http2);
342
343            return builder.build().run().await;
344        }
345
346        Ok(())
347    }
348}