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