1#![cfg_attr(feature = "docs", doc = "\n\nSee the [changelog][changelog] for a full release history.")]
7#![cfg_attr(feature = "docs", doc = "## Feature flags")]
8#![cfg_attr(feature = "docs", doc = document_features::document_features!())]
9#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
56#![cfg_attr(docsrs, feature(doc_auto_cfg))]
57#![deny(missing_docs)]
58#![deny(unsafe_code)]
59#![deny(unreachable_pub)]
60
61#[cfg(all(feature = "http3", not(feature = "tls-rustls")))]
62compile_error!("feature \"tls-rustls\" must be enabled when \"http3\" is enabled.");
63
64#[cfg(any(feature = "http1", feature = "http2", feature = "http3"))]
65pub mod backend;
66pub mod body;
67pub mod error;
68mod server;
69pub mod service;
70
71pub use http;
72pub use http::Response;
73pub use server::{HttpServer, HttpServerBuilder};
74
75pub type IncomingRequest = http::Request<body::IncomingBody>;
77
78#[cfg(feature = "docs")]
80#[scuffle_changelog::changelog]
81pub mod changelog {}
82
83#[cfg(test)]
84#[cfg_attr(all(test, coverage_nightly), coverage(off))]
85mod tests {
86 use std::convert::Infallible;
87 use std::time::Duration;
88
89 use scuffle_future_ext::FutureExt;
90
91 use crate::HttpServer;
92 use crate::service::{fn_http_service, service_clone_factory};
93
94 fn get_available_addr() -> std::io::Result<std::net::SocketAddr> {
95 let listener = std::net::TcpListener::bind("127.0.0.1:0")?;
96 listener.local_addr()
97 }
98
99 const RESPONSE_TEXT: &str = "Hello, world!";
100
101 #[allow(dead_code)]
102 async fn test_server<F, S>(builder: crate::HttpServerBuilder<F, S>, versions: &[reqwest::Version])
103 where
104 F: crate::service::HttpServiceFactory + std::fmt::Debug + Clone + Send + 'static,
105 F::Error: std::error::Error + Send,
106 F::Service: Clone + std::fmt::Debug + Send + 'static,
107 <F::Service as crate::service::HttpService>::Error: std::error::Error + Send + Sync,
108 <F::Service as crate::service::HttpService>::ResBody: Send,
109 <<F::Service as crate::service::HttpService>::ResBody as http_body::Body>::Data: Send,
110 <<F::Service as crate::service::HttpService>::ResBody as http_body::Body>::Error: std::error::Error + Send + Sync,
111 S: crate::server::http_server_builder::State,
112 S::ServiceFactory: crate::server::http_server_builder::IsSet,
113 S::Bind: crate::server::http_server_builder::IsUnset,
114 S::Ctx: crate::server::http_server_builder::IsUnset,
115 {
116 let addr = get_available_addr().expect("failed to get available address");
117 let (ctx, handler) = scuffle_context::Context::new();
118
119 let server = builder.bind(addr).ctx(ctx).build();
120
121 let handle = tokio::spawn(async move {
122 server.run().await.expect("server run failed");
123 });
124
125 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
127
128 let url = format!("http://{addr}/");
129
130 for version in versions {
131 let mut builder = reqwest::Client::builder().danger_accept_invalid_certs(true);
132
133 if *version == reqwest::Version::HTTP_3 {
134 builder = builder.http3_prior_knowledge();
135 } else if *version == reqwest::Version::HTTP_2 {
136 builder = builder.http2_prior_knowledge();
137 } else {
138 builder = builder.http1_only();
139 }
140
141 let client = builder.build().expect("failed to build client");
142
143 let request = client
144 .request(reqwest::Method::GET, &url)
145 .version(*version)
146 .body(RESPONSE_TEXT.to_string())
147 .build()
148 .expect("failed to build request");
149
150 let resp = client
151 .execute(request)
152 .await
153 .expect("failed to get response")
154 .text()
155 .await
156 .expect("failed to get text");
157
158 assert_eq!(resp, RESPONSE_TEXT);
159 }
160
161 handler.shutdown().await;
162 handle.await.expect("task failed");
163 }
164
165 #[cfg(feature = "tls-rustls")]
166 #[allow(dead_code)]
167 async fn test_tls_server<F, S>(builder: crate::HttpServerBuilder<F, S>, versions: &[reqwest::Version])
168 where
169 F: crate::service::HttpServiceFactory + std::fmt::Debug + Clone + Send + 'static,
170 F::Error: std::error::Error + Send,
171 F::Service: Clone + std::fmt::Debug + Send + 'static,
172 <F::Service as crate::service::HttpService>::Error: std::error::Error + Send + Sync,
173 <F::Service as crate::service::HttpService>::ResBody: Send,
174 <<F::Service as crate::service::HttpService>::ResBody as http_body::Body>::Data: Send,
175 <<F::Service as crate::service::HttpService>::ResBody as http_body::Body>::Error: std::error::Error + Send + Sync,
176 S: crate::server::http_server_builder::State,
177 S::ServiceFactory: crate::server::http_server_builder::IsSet,
178 S::Bind: crate::server::http_server_builder::IsUnset,
179 S::Ctx: crate::server::http_server_builder::IsUnset,
180 {
181 let addr = get_available_addr().expect("failed to get available address");
182 let (ctx, handler) = scuffle_context::Context::new();
183
184 let server = builder.bind(addr).ctx(ctx).build();
185
186 let handle = tokio::spawn(async move {
187 server.run().await.expect("server run failed");
188 });
189
190 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
192
193 let url = format!("https://{addr}/");
194
195 for version in versions {
196 let mut builder = reqwest::Client::builder().danger_accept_invalid_certs(true).https_only(true);
197
198 if *version == reqwest::Version::HTTP_3 {
199 builder = builder.http3_prior_knowledge();
200 } else if *version == reqwest::Version::HTTP_2 {
201 builder = builder.http2_prior_knowledge();
202 } else {
203 builder = builder.http1_only();
204 }
205
206 let client = builder.build().expect("failed to build client");
207
208 let request = client
209 .request(reqwest::Method::GET, &url)
210 .version(*version)
211 .body(RESPONSE_TEXT.to_string())
212 .build()
213 .expect("failed to build request");
214
215 let resp = client
216 .execute(request)
217 .await
218 .unwrap_or_else(|_| panic!("failed to get response version {version:?}"))
219 .text()
220 .await
221 .expect("failed to get text");
222
223 assert_eq!(resp, RESPONSE_TEXT);
224 }
225
226 handler.shutdown().await;
227 handle.await.expect("task failed");
228 }
229
230 #[tokio::test]
231 #[cfg(feature = "http2")]
232 async fn http2_server() {
233 let builder = HttpServer::builder().service_factory(service_clone_factory(fn_http_service(|_| async {
234 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
235 })));
236
237 #[cfg(feature = "http1")]
238 let builder = builder.enable_http1(false);
239
240 test_server(builder, &[reqwest::Version::HTTP_2]).await;
241 }
242
243 #[tokio::test]
244 #[cfg(all(feature = "http1", feature = "http2"))]
245 async fn http12_server() {
246 let server = HttpServer::builder()
247 .service_factory(service_clone_factory(fn_http_service(|_| async {
248 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
249 })))
250 .enable_http1(true)
251 .enable_http2(true);
252
253 test_server(server, &[reqwest::Version::HTTP_11, reqwest::Version::HTTP_2]).await;
254 }
255
256 #[cfg(feature = "tls-rustls")]
257 fn rustls_config() -> rustls::ServerConfig {
258 rustls::crypto::aws_lc_rs::default_provider()
259 .install_default()
260 .expect("failed to install aws lc provider");
261
262 let certfile = std::fs::File::open("../../assets/cert.pem").expect("cert not found");
263 let certs = rustls_pemfile::certs(&mut std::io::BufReader::new(certfile))
264 .collect::<Result<Vec<_>, _>>()
265 .expect("failed to load certs");
266 let keyfile = std::fs::File::open("../../assets/key.pem").expect("key not found");
267 let key = rustls_pemfile::private_key(&mut std::io::BufReader::new(keyfile))
268 .expect("failed to load key")
269 .expect("no key found");
270
271 rustls::ServerConfig::builder()
272 .with_no_client_auth()
273 .with_single_cert(certs, key)
274 .expect("failed to build config")
275 }
276
277 #[tokio::test]
278 #[cfg(all(feature = "tls-rustls", feature = "http1"))]
279 async fn rustls_http1_server() {
280 let builder = HttpServer::builder()
281 .service_factory(service_clone_factory(fn_http_service(|_| async {
282 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
283 })))
284 .rustls_config(rustls_config());
285
286 #[cfg(feature = "http2")]
287 let builder = builder.enable_http2(false);
288
289 test_tls_server(builder, &[reqwest::Version::HTTP_11]).await;
290 }
291
292 #[tokio::test]
293 #[cfg(all(feature = "tls-rustls", feature = "http3"))]
294 async fn rustls_http3_server() {
295 let builder = HttpServer::builder()
296 .service_factory(service_clone_factory(fn_http_service(|_| async {
297 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
298 })))
299 .rustls_config(rustls_config())
300 .enable_http3(true);
301
302 #[cfg(feature = "http2")]
303 let builder = builder.enable_http2(false);
304
305 #[cfg(feature = "http1")]
306 let builder = builder.enable_http1(false);
307
308 test_tls_server(builder, &[reqwest::Version::HTTP_3]).await;
309 }
310
311 #[tokio::test]
312 #[cfg(all(feature = "tls-rustls", feature = "http1", feature = "http2"))]
313 async fn rustls_http12_server() {
314 let builder = HttpServer::builder()
315 .service_factory(service_clone_factory(fn_http_service(|_| async {
316 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
317 })))
318 .rustls_config(rustls_config())
319 .enable_http1(true)
320 .enable_http2(true);
321
322 test_tls_server(builder, &[reqwest::Version::HTTP_11, reqwest::Version::HTTP_2]).await;
323 }
324
325 #[tokio::test]
326 #[cfg(all(feature = "tls-rustls", feature = "http1", feature = "http2", feature = "http3"))]
327 async fn rustls_http123_server() {
328 let builder = HttpServer::builder()
329 .service_factory(service_clone_factory(fn_http_service(|_| async {
330 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
331 })))
332 .rustls_config(rustls_config())
333 .enable_http1(true)
334 .enable_http2(true)
335 .enable_http3(true);
336
337 test_tls_server(
338 builder,
339 &[reqwest::Version::HTTP_11, reqwest::Version::HTTP_2, reqwest::Version::HTTP_3],
340 )
341 .await;
342 }
343
344 #[tokio::test]
345 async fn no_backend() {
346 let addr = get_available_addr().expect("failed to get available address");
347
348 let builder = HttpServer::builder()
349 .service_factory(service_clone_factory(fn_http_service(|_| async {
350 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
351 })))
352 .bind(addr);
353
354 #[cfg(feature = "http1")]
355 let builder = builder.enable_http1(false);
356
357 #[cfg(feature = "http2")]
358 let builder = builder.enable_http2(false);
359
360 builder
361 .build()
362 .run()
363 .with_timeout(Duration::from_millis(100))
364 .await
365 .expect("server timed out")
366 .expect("server failed");
367 }
368
369 #[tokio::test]
370 #[cfg(feature = "tls-rustls")]
371 async fn rustls_no_backend() {
372 let addr = get_available_addr().expect("failed to get available address");
373
374 let builder = HttpServer::builder()
375 .service_factory(service_clone_factory(fn_http_service(|_| async {
376 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
377 })))
378 .rustls_config(rustls_config())
379 .bind(addr);
380
381 #[cfg(feature = "http1")]
382 let builder = builder.enable_http1(false);
383
384 #[cfg(feature = "http2")]
385 let builder = builder.enable_http2(false);
386
387 builder
388 .build()
389 .run()
390 .with_timeout(Duration::from_millis(100))
391 .await
392 .expect("server timed out")
393 .expect("server failed");
394 }
395
396 #[tokio::test]
397 #[cfg(all(feature = "tower", feature = "http1", feature = "http2"))]
398 async fn tower_make_service() {
399 let builder = HttpServer::builder()
400 .tower_make_service_factory(tower::service_fn(|_| async {
401 Ok::<_, Infallible>(tower::service_fn(|_| async move {
402 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
403 }))
404 }))
405 .enable_http1(true)
406 .enable_http2(true);
407
408 test_server(builder, &[reqwest::Version::HTTP_11, reqwest::Version::HTTP_2]).await;
409 }
410
411 #[tokio::test]
412 #[cfg(all(feature = "tower", feature = "http1", feature = "http2"))]
413 async fn tower_custom_make_service() {
414 let builder = HttpServer::builder()
415 .custom_tower_make_service_factory(
416 tower::service_fn(|target| async move {
417 assert_eq!(target, 42);
418 Ok::<_, Infallible>(tower::service_fn(|_| async move {
419 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
420 }))
421 }),
422 42,
423 )
424 .enable_http1(true)
425 .enable_http2(true);
426
427 test_server(builder, &[reqwest::Version::HTTP_11, reqwest::Version::HTTP_2]).await;
428 }
429
430 #[tokio::test]
431 #[cfg(all(feature = "tower", feature = "http1", feature = "http2"))]
432 async fn tower_make_service_with_addr() {
433 use std::net::SocketAddr;
434
435 let builder = HttpServer::builder()
436 .tower_make_service_with_addr(tower::service_fn(|addr: SocketAddr| async move {
437 assert!(addr.ip().is_loopback());
438 Ok::<_, Infallible>(tower::service_fn(|_| async move {
439 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
440 }))
441 }))
442 .enable_http1(true)
443 .enable_http2(true);
444
445 test_server(builder, &[reqwest::Version::HTTP_11, reqwest::Version::HTTP_2]).await;
446 }
447
448 #[tokio::test]
449 #[cfg(all(feature = "http1", feature = "http2"))]
450 async fn fn_service_factory() {
451 use crate::service::fn_http_service_factory;
452
453 let builder = HttpServer::builder()
454 .service_factory(fn_http_service_factory(|_| async {
455 Ok::<_, Infallible>(fn_http_service(|_| async {
456 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
457 }))
458 }))
459 .enable_http1(true)
460 .enable_http2(true);
461
462 test_server(builder, &[reqwest::Version::HTTP_11, reqwest::Version::HTTP_2]).await;
463 }
464
465 #[tokio::test]
466 #[cfg(all(
467 feature = "http1",
468 feature = "http2",
469 feature = "http3",
470 feature = "tls-rustls",
471 feature = "tower"
472 ))]
473 async fn axum_service() {
474 let router = axum::Router::new().route(
475 "/",
476 axum::routing::get(|req: String| async move {
477 assert_eq!(req, RESPONSE_TEXT);
478 http::Response::new(RESPONSE_TEXT.to_string())
479 }),
480 );
481
482 let builder = HttpServer::builder()
483 .tower_make_service_factory(router.into_make_service())
484 .rustls_config(rustls_config())
485 .enable_http3(true)
486 .enable_http1(true)
487 .enable_http2(true);
488
489 test_tls_server(
490 builder,
491 &[reqwest::Version::HTTP_11, reqwest::Version::HTTP_2, reqwest::Version::HTTP_3],
492 )
493 .await;
494 }
495
496 #[tokio::test]
497 #[cfg(all(feature = "http1", feature = "http2"))]
498 async fn tracked_body() {
499 use crate::body::TrackedBody;
500
501 #[derive(Clone)]
502 struct TestTracker;
503
504 impl crate::body::Tracker for TestTracker {
505 type Error = Infallible;
506
507 fn on_data(&self, size: usize) -> Result<(), Self::Error> {
508 assert_eq!(size, RESPONSE_TEXT.len());
509 Ok(())
510 }
511 }
512
513 let builder = HttpServer::builder()
514 .service_factory(service_clone_factory(fn_http_service(|req| async {
515 let req = req.map(|b| TrackedBody::new(b, TestTracker));
516 let body = req.into_body();
517 Ok::<_, Infallible>(http::Response::new(body))
518 })))
519 .enable_http1(true)
520 .enable_http2(true);
521
522 test_server(builder, &[reqwest::Version::HTTP_11, reqwest::Version::HTTP_2]).await;
523 }
524
525 #[tokio::test]
526 #[cfg(all(feature = "http1", feature = "http2"))]
527 async fn tracked_body_error() {
528 use crate::body::TrackedBody;
529
530 #[derive(Clone)]
531 struct TestTracker;
532
533 impl crate::body::Tracker for TestTracker {
534 type Error = &'static str;
535
536 fn on_data(&self, size: usize) -> Result<(), Self::Error> {
537 assert_eq!(size, RESPONSE_TEXT.len());
538 Err("test")
539 }
540 }
541
542 let builder = HttpServer::builder()
543 .service_factory(service_clone_factory(fn_http_service(|req| async {
544 let req = req.map(|b| TrackedBody::new(b, TestTracker));
545 let body = req.into_body();
546 let bytes = axum::body::to_bytes(axum::body::Body::new(body), usize::MAX).await;
548 assert_eq!(bytes.expect_err("expected error").to_string(), "tracker error: test");
549
550 Ok::<_, Infallible>(http::Response::new(RESPONSE_TEXT.to_string()))
551 })))
552 .enable_http1(true)
553 .enable_http2(true);
554
555 test_server(builder, &[reqwest::Version::HTTP_11, reqwest::Version::HTTP_2]).await;
556 }
557
558 #[tokio::test]
559 #[cfg(all(feature = "http2", feature = "http3", feature = "tls-rustls"))]
560 async fn response_trailers() {
561 #[derive(Default)]
562 struct TestBody {
563 data_sent: bool,
564 }
565
566 impl http_body::Body for TestBody {
567 type Data = bytes::Bytes;
568 type Error = Infallible;
569
570 fn poll_frame(
571 mut self: std::pin::Pin<&mut Self>,
572 _cx: &mut std::task::Context<'_>,
573 ) -> std::task::Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
574 if !self.data_sent {
575 self.as_mut().data_sent = true;
576 let data = http_body::Frame::data(bytes::Bytes::from_static(RESPONSE_TEXT.as_bytes()));
577 std::task::Poll::Ready(Some(Ok(data)))
578 } else {
579 let mut trailers = http::HeaderMap::new();
580 trailers.insert("test", "test".parse().unwrap());
581 std::task::Poll::Ready(Some(Ok(http_body::Frame::trailers(trailers))))
582 }
583 }
584 }
585
586 let builder = HttpServer::builder()
587 .service_factory(service_clone_factory(fn_http_service(|_req| async {
588 let mut resp = http::Response::new(TestBody::default());
589 resp.headers_mut().insert("trailers", "test".parse().unwrap());
590 Ok::<_, Infallible>(resp)
591 })))
592 .rustls_config(rustls_config())
593 .enable_http3(true)
594 .enable_http2(true);
595
596 #[cfg(feature = "http1")]
597 let builder = builder.enable_http1(false);
598
599 test_tls_server(builder, &[reqwest::Version::HTTP_2, reqwest::Version::HTTP_3]).await;
600 }
601}