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