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