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