1use std::future::Future;
2use std::net::SocketAddr;
3use std::sync::Arc;
4use std::time::Duration;
5
6use hyper::body::Bytes;
7use hyper::body::Incoming;
8use hyper::server::conn::http1;
9use hyper::{Method, Request, Response, StatusCode};
10use hyper::service::service_fn;
11use hyper_util::rt::TokioIo;
12use hyper_util::rt::TokioTimer;
13use http_body_util::combinators::BoxBody;
14use http_body_util::{BodyExt, Empty, Full};
15use tokio::net::{TcpListener, TcpStream};
16#[cfg(feature = "tls")]
17use tokio_rustls::TlsAcceptor;
18
19use crate::body::{MaxBodySize, DEFAULT_MAX_BODY_SIZE};
20use crate::cors::CorsConfig;
21use crate::error::ServeError;
22use crate::handler::{Handler, Middleware, ResponseBody};
23use crate::router::{QueryParams, Router};
24use crate::state::State;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct PeerAddr(pub SocketAddr);
31
32const MAX_PATH_LEN: usize = 8_192;
33const MAX_QUERY_LEN: usize = 4_096;
34const DEFAULT_HEADER_READ_TIMEOUT: Duration = Duration::from_secs(30);
35const DEFAULT_MAX_CONNECTIONS: usize = 1024;
36#[cfg(feature = "tls")]
37const DEFAULT_TLS_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
38const ACCEPT_BACKOFF_INITIAL: Duration = Duration::from_millis(10);
39const ACCEPT_BACKOFF_MAX: Duration = Duration::from_secs(1);
40
41#[cfg(test)]
42thread_local! {
43 static ERROR_LOG: std::cell::RefCell<Vec<(u16, String)>> = std::cell::RefCell::new(Vec::new());
44}
45
46#[cfg(test)]
47fn capture_error(code: u16, message: String) {
48 ERROR_LOG.with(|log| log.borrow_mut().push((code, message)));
49}
50
51#[cfg(test)]
52fn take_error_log() -> Vec<(u16, String)> {
53 ERROR_LOG.with(|log| log.borrow_mut().drain(..).collect())
54}
55
56trait TcpAccept {
61 async fn accept(&self) -> std::io::Result<(TcpStream, SocketAddr)>;
62}
63
64impl TcpAccept for TcpListener {
65 async fn accept(&self) -> std::io::Result<(TcpStream, SocketAddr)> {
66 TcpListener::accept(self).await
67 }
68}
69
70struct Backoff {
75 delay: Duration,
76}
77
78impl Backoff {
79 fn new() -> Self {
80 Backoff { delay: ACCEPT_BACKOFF_INITIAL }
81 }
82
83 fn next_delay(&mut self) -> Duration {
84 let delay = self.delay;
85 self.delay = (self.delay * 2).min(ACCEPT_BACKOFF_MAX);
86 delay
87 }
88
89 fn reset(&mut self) {
90 self.delay = ACCEPT_BACKOFF_INITIAL;
91 }
92}
93
94async fn accept_with_backoff<L: TcpAccept>(
95 listener: &L,
96 backoff: &mut Backoff,
97) -> (TcpStream, SocketAddr) {
98 loop {
99 match listener.accept().await {
100 Ok(conn) => {
101 backoff.reset();
102 return conn;
103 }
104 Err(_) => {
105 tokio::time::sleep(backoff.next_delay()).await;
106 }
107 }
108 }
109}
110
111pub type ErrorHandler =
112 Arc<dyn Fn(StatusCode, &str) -> Response<ResponseBody> + Send + Sync>;
113
114pub struct App<S> {
142 state: Arc<S>,
143 router: Arc<Router<S>>,
144 max_body_size: usize,
145 pub(crate) header_read_timeout: Duration,
146 pub(crate) max_connections: usize,
147 #[cfg(feature = "tls")]
148 pub(crate) tls_handshake_timeout: Duration,
149 error_handler: ErrorHandler,
150 cors_config: Option<CorsConfig>,
151}
152
153fn parse_query(query: Option<&str>) -> QueryParams {
154 let mut map = std::collections::HashMap::new();
155 if let Some(query) = query {
156 for pair in query.split('&').filter(|s| !s.is_empty()) {
157 if let Some((key, value)) = pair.split_once('=') {
158 let key = decode_query_component(key);
159 let value = decode_query_component(value);
160 map.insert(key, value);
161 } else {
162 let pair = decode_query_component(pair);
163 map.insert(pair, String::new());
164 }
165 }
166 }
167 QueryParams(map)
168}
169
170fn decode_query_component(s: &str) -> String {
171 let with_spaces = s.replace('+', " ");
172 percent_encoding::percent_decode_str(&with_spaces)
173 .decode_utf8_lossy()
174 .into_owned()
175}
176
177fn error_response(status: StatusCode, message: &str) -> Response<ResponseBody> {
178 #[cfg(test)]
179 capture_error(status.as_u16(), message.to_string());
180
181 let client_message = if status.is_server_error() {
182 "internal server error"
183 } else {
184 message
185 };
186
187 let body = serde_json::json!({ "message": client_message });
188 let json = serde_json::to_string(&body)
189 .unwrap_or_else(|_| r#"{"message":"internal server error"}"#.to_string());
190 Response::builder()
191 .status(status)
192 .header("content-type", "application/json")
193 .body(BoxBody::new(Full::new(Bytes::from(json)).map_err(|never: std::convert::Infallible| match never {})))
194 .expect("status is valid and headers are static ASCII")
195}
196
197fn default_error_handler() -> ErrorHandler {
198 Arc::new(error_response)
199}
200
201fn ephemeral_bind_addr() -> SocketAddr {
205 (std::net::Ipv4Addr::LOCALHOST, 0).into()
206}
207
208impl<S: Send + Sync + 'static> App<S> {
209 pub fn new(state: S) -> Self {
214 App {
215 state: Arc::new(state),
216 router: Arc::new(Router::new()),
217 max_body_size: DEFAULT_MAX_BODY_SIZE,
218 header_read_timeout: DEFAULT_HEADER_READ_TIMEOUT,
219 max_connections: DEFAULT_MAX_CONNECTIONS,
220 #[cfg(feature = "tls")]
221 tls_handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
222 error_handler: default_error_handler(),
223 cors_config: None,
224 }
225 }
226
227 pub fn state_arc(&self) -> Arc<S> {
232 Arc::clone(&self.state)
233 }
234
235 pub async fn route(&self, req: Request<Incoming>) -> Response<ResponseBody> {
236 self.route_with(req, None).await
237 }
238
239 pub async fn route_with_peer(&self, req: Request<Incoming>, peer: SocketAddr) -> Response<ResponseBody> {
246 self.route_with(req, Some(peer)).await
247 }
248
249 async fn route_with(&self, req: Request<Incoming>, peer: Option<SocketAddr>) -> Response<ResponseBody> {
250 let method = req.method().clone();
251 let resp = self.route_inner(req, peer).await;
252
253 if method == Method::HEAD {
260 let (parts, _) = resp.into_parts();
261 Response::from_parts(parts, BoxBody::new(Empty::new().map_err(|never: std::convert::Infallible| match never {})))
262 } else {
263 resp
264 }
265 }
266
267 async fn route_inner(&self, req: Request<Incoming>, peer: Option<SocketAddr>) -> Response<ResponseBody> {
268 if req.uri().path().len() > MAX_PATH_LEN {
269 return (self.error_handler)(StatusCode::BAD_REQUEST, "path too long");
270 }
271 if req.uri().query().map(|q| q.len()).unwrap_or(0) > MAX_QUERY_LEN {
272 return (self.error_handler)(StatusCode::BAD_REQUEST, "query string too long");
273 }
274
275 let method = req.method().clone();
276 let path = req.uri().path().to_string();
277 let state = State::from_arc(Arc::clone(&self.state));
278 let query_params = parse_query(req.uri().query());
279
280 let req_origin = req
282 .headers()
283 .get("origin")
284 .and_then(|v| v.to_str().ok())
285 .map(|s| s.to_string());
286
287 if method == Method::OPTIONS && req_origin.is_some() {
289 if let Some(cfg) = &self.cors_config {
290 if self.router.path_exists(&path) {
291 let requested_headers = req
292 .headers()
293 .get("access-control-request-headers")
294 .and_then(|v| v.to_str().ok());
295 let allowed = self.allowed_methods_with_head(&path);
296 return cfg.preflight_response(req_origin.as_deref(), requested_headers, &allowed);
297 }
298 }
299 }
300
301 let method_to_match = if method == Method::HEAD {
302 Method::GET
303 } else {
304 method.clone()
305 };
306
307 match self.router.match_route(&method_to_match, &path) {
308 Some((handler, params)) => {
309 let mut req = req;
310 req.extensions_mut().insert(query_params);
311 req.extensions_mut().insert(params);
312 req.extensions_mut().insert(MaxBodySize(self.max_body_size));
313 if let Some(peer) = peer {
314 req.extensions_mut().insert(PeerAddr(peer));
315 }
316 match handler(req, state).await {
317 Ok(mut resp) => {
318 if let Some(cfg) = &self.cors_config {
319 cfg.apply_to_response(&mut resp, req_origin.as_deref());
320 }
321 resp
322 }
323 Err(e) => (self.error_handler)(
324 StatusCode::from_u16(e.code)
325 .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
326 &e.message,
327 ),
328 }
329 }
330 None => {
331 let allowed = self.allowed_methods_with_head(&path);
332 if !allowed.is_empty() {
333 let mut method_strs: Vec<&str> = allowed.iter().map(|m| m.as_str()).collect();
334 method_strs.sort();
335 method_strs.dedup();
336 let allow_header = method_strs.join(", ");
337 let mut resp = (self.error_handler)(StatusCode::METHOD_NOT_ALLOWED, "method not allowed");
338 if let Ok(val) = allow_header.parse() {
339 resp.headers_mut().insert("allow", val);
340 }
341 resp
342 } else {
343 (self.error_handler)(StatusCode::NOT_FOUND, "not found")
344 }
345 }
346 }
347 }
348
349 fn allowed_methods_with_head(&self, path: &str) -> Vec<Method> {
356 let mut allowed = self.router.allowed_methods(path);
357 if allowed.contains(&Method::GET) {
358 allowed.push(Method::HEAD);
359 }
360 allowed
361 }
362
363 pub async fn bind_ephemeral(self) -> Result<u16, ServeError> {
369 let listener = TcpListener::bind(ephemeral_bind_addr())
370 .await
371 .map_err(|e| ServeError::new(500, format!("failed to bind to ephemeral port: {e}")))?;
372 let port = listener
373 .local_addr()
374 .map_err(|e| ServeError::new(500, format!("failed to get assigned port: {e}")))?
375 .port();
376 let app = Arc::new(self);
377 tokio::spawn(async move {
378 serve_inner(listener, app).await;
379 });
380 Ok(port)
381 }
382
383 #[cfg(feature = "tls")]
389 pub async fn bind_tls_ephemeral(
390 self,
391 config: Arc<rustls::ServerConfig>,
392 ) -> Result<u16, ServeError> {
393 let listener = TcpListener::bind(ephemeral_bind_addr())
394 .await
395 .map_err(|e| ServeError::new(500, format!("failed to bind to ephemeral port: {e}")))?;
396 let port = listener
397 .local_addr()
398 .map_err(|e| ServeError::new(500, format!("failed to get assigned port: {e}")))?
399 .port();
400 let acceptor = TlsAcceptor::from(config);
401 let app = Arc::new(self);
402 tokio::spawn(async move {
403 serve_tls_inner(listener, app, acceptor).await;
404 });
405 Ok(port)
406 }
407
408 pub async fn run<F>(self, listener: TcpListener, shutdown: F) -> Result<(), ServeError>
413 where
414 F: Future<Output = ()> + Send + 'static,
415 {
416 let app = Arc::new(self);
417 serve_with_shutdown(listener, app, shutdown).await;
418 Ok(())
419 }
420
421 pub async fn bind(self, addr: SocketAddr) -> Result<(), ServeError> {
426 let listener = TcpListener::bind(addr)
427 .await
428 .map_err(|e| ServeError::new(500, format!("failed to bind to {addr}: {e}")))?;
429 self.run(listener, signal_shutdown()).await
430 }
431
432 #[cfg(feature = "tls")]
434 pub async fn run_tls<F>(
435 self,
436 listener: TcpListener,
437 config: Arc<rustls::ServerConfig>,
438 shutdown: F,
439 ) -> Result<(), ServeError>
440 where
441 F: Future<Output = ()> + Send + 'static,
442 {
443 let acceptor = TlsAcceptor::from(config);
444 let app = Arc::new(self);
445 serve_tls_with_shutdown(listener, app, acceptor, shutdown).await;
446 Ok(())
447 }
448
449 #[cfg(feature = "tls")]
451 pub async fn bind_tls(
452 self,
453 addr: SocketAddr,
454 config: Arc<rustls::ServerConfig>,
455 ) -> Result<(), ServeError> {
456 let listener = TcpListener::bind(addr)
457 .await
458 .map_err(|e| ServeError::new(500, format!("failed to bind to {addr}: {e}")))?;
459 self.run_tls(listener, config, signal_shutdown()).await
460 }
461}
462
463impl App<()> {
464 pub fn stateless() -> Self {
465 App::new(())
466 }
467}
468
469async fn serve_connection<S, IO>(io: IO, app: Arc<App<S>>, header_read_timeout: Duration, peer: SocketAddr)
473where
474 S: Send + Sync + 'static,
475 IO: hyper::rt::Read + hyper::rt::Write + Unpin + 'static,
476{
477 let svc = service_fn(move |req: Request<Incoming>| {
478 let app = app.clone();
479 async move {
480 Ok::<_, hyper::Error>(app.route_with_peer(req, peer).await)
481 }
482 });
483 let mut builder = http1::Builder::new();
484 builder.timer(TokioTimer::new());
485 builder.header_read_timeout(header_read_timeout);
486 let conn = builder.serve_connection(io, svc);
487 let _ = conn.await;
488}
489
490async fn serve_inner<S: Send + Sync + 'static>(
491 listener: TcpListener,
492 app: Arc<App<S>>,
493) {
494 let header_read_timeout = app.header_read_timeout;
495 let semaphore = Arc::new(tokio::sync::Semaphore::new(app.max_connections));
496 let mut backoff = Backoff::new();
497 loop {
498 let (stream, peer) = accept_with_backoff(&listener, &mut backoff).await;
499 let sem = semaphore.clone();
500 let permit = match sem.acquire_owned().await {
501 Ok(p) => p,
502 Err(_) => continue,
503 };
504 let app = app.clone();
505 tokio::spawn(async move {
506 let _permit = permit;
507 serve_connection(TokioIo::new(stream), app, header_read_timeout, peer).await;
508 });
509 }
510}
511
512#[cfg(feature = "tls")]
517async fn serve_tls_inner<S: Send + Sync + 'static>(
518 listener: TcpListener,
519 app: Arc<App<S>>,
520 acceptor: TlsAcceptor,
521) {
522 let header_read_timeout = app.header_read_timeout;
523 let handshake_timeout = app.tls_handshake_timeout;
524 let semaphore = Arc::new(tokio::sync::Semaphore::new(app.max_connections));
525 let mut backoff = Backoff::new();
526 loop {
527 let (stream, peer) = accept_with_backoff(&listener, &mut backoff).await;
528 let sem = semaphore.clone();
529 let permit = match sem.acquire_owned().await {
530 Ok(p) => p,
531 Err(_) => continue,
532 };
533 let app = app.clone();
534 let acceptor = acceptor.clone();
535 tokio::spawn(async move {
536 let _permit = permit;
537 let tls_stream = match tokio::time::timeout(handshake_timeout, acceptor.accept(stream)).await {
538 Ok(Ok(s)) => s,
539 Ok(Err(_)) | Err(_) => return,
540 };
541 serve_connection(TokioIo::new(tls_stream), app, header_read_timeout, peer).await;
542 });
543 }
544}
545
546async fn accept_and_permit<L: TcpAccept>(
560 listener: &L,
561 backoff: &mut Backoff,
562 semaphore: &Arc<tokio::sync::Semaphore>,
563) -> Option<(TcpStream, SocketAddr, tokio::sync::OwnedSemaphorePermit)> {
564 loop {
565 let (stream, peer) = match listener.accept().await {
566 Ok(conn) => {
567 backoff.reset();
568 conn
569 }
570 Err(_) => {
571 tokio::time::sleep(backoff.next_delay()).await;
572 continue;
573 }
574 };
575 return match semaphore.clone().acquire_owned().await {
576 Ok(permit) => Some((stream, peer, permit)),
577 Err(_) => None,
578 };
579 }
580}
581
582async fn signal_shutdown() {
584 let mut sigint = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
585 .expect("failed to install SIGINT handler");
586 let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
587 .expect("failed to install SIGTERM handler");
588
589 tokio::select! {
590 _ = sigint.recv() => {}
591 _ = sigterm.recv() => {}
592 }
593}
594
595async fn serve_with_shutdown<S, F>(listener: TcpListener, app: Arc<App<S>>, shutdown: F)
604where
605 S: Send + Sync + 'static,
606 F: Future<Output = ()> + Send + 'static,
607{
608 let header_read_timeout = app.header_read_timeout;
609 let semaphore = Arc::new(tokio::sync::Semaphore::new(app.max_connections));
610 let mut backoff = Backoff::new();
611 let mut join_set: tokio::task::JoinSet<()> = tokio::task::JoinSet::new();
612 let mut shutdown_pin = std::pin::pin!(shutdown);
613 let mut shutting_down = false;
614
615 loop {
616 if !shutting_down {
617 tokio::select! {
618 accepted = accept_and_permit(&listener, &mut backoff, &semaphore) => {
619 match accepted {
620 Some((stream, peer, permit)) => {
621 let app = app.clone();
622 join_set.spawn(async move {
623 let _permit = permit;
624 serve_connection(TokioIo::new(stream), app, header_read_timeout, peer).await;
625 });
626 }
627 None => shutting_down = true,
628 }
629 }
630 _ = shutdown_pin.as_mut() => {
631 shutting_down = true;
632 }
633 }
634 continue;
635 }
636
637 match join_set.join_next().await {
638 Some(_) => continue,
639 None => break,
640 }
641 }
642}
643
644#[cfg(feature = "tls")]
649async fn serve_tls_with_shutdown<S, F>(
650 listener: TcpListener,
651 app: Arc<App<S>>,
652 acceptor: TlsAcceptor,
653 shutdown: F,
654) where
655 S: Send + Sync + 'static,
656 F: Future<Output = ()> + Send + 'static,
657{
658 let header_read_timeout = app.header_read_timeout;
659 let handshake_timeout = app.tls_handshake_timeout;
660 let semaphore = Arc::new(tokio::sync::Semaphore::new(app.max_connections));
661 let mut backoff = Backoff::new();
662 let mut join_set: tokio::task::JoinSet<()> = tokio::task::JoinSet::new();
663 let mut shutdown_pin = std::pin::pin!(shutdown);
664 let mut shutting_down = false;
665
666 loop {
667 if !shutting_down {
668 tokio::select! {
669 accepted = accept_and_permit(&listener, &mut backoff, &semaphore) => {
670 match accepted {
671 Some((stream, peer, permit)) => {
672 let app = app.clone();
673 let acceptor = acceptor.clone();
674 join_set.spawn(async move {
675 let _permit = permit;
676 let tls_stream = match tokio::time::timeout(handshake_timeout, acceptor.accept(stream)).await {
677 Ok(Ok(s)) => s,
678 Ok(Err(_)) | Err(_) => return,
679 };
680 serve_connection(TokioIo::new(tls_stream), app, header_read_timeout, peer).await;
681 });
682 }
683 None => shutting_down = true,
684 }
685 }
686 _ = shutdown_pin.as_mut() => {
687 shutting_down = true;
688 }
689 }
690 continue;
691 }
692
693 match join_set.join_next().await {
694 Some(_) => continue,
695 None => break,
696 }
697 }
698}
699
700#[must_use = "RouteBuilder does nothing until .seal() is called"]
725pub struct RouteBuilder<S> {
726 state: Arc<S>,
727 router: Router<S>,
728 max_body_size: usize,
729 header_read_timeout: Duration,
730 max_connections: usize,
731 #[cfg(feature = "tls")]
732 tls_handshake_timeout: Duration,
733 error_handler: ErrorHandler,
734 cors_config: Option<CorsConfig>,
735 middlewares: Vec<Middleware<S>>,
736}
737
738impl<S: Send + Sync + 'static> RouteBuilder<S> {
739 pub fn new(state: S) -> Self {
741 RouteBuilder {
742 state: Arc::new(state),
743 router: Router::new(),
744 max_body_size: DEFAULT_MAX_BODY_SIZE,
745 header_read_timeout: DEFAULT_HEADER_READ_TIMEOUT,
746 max_connections: DEFAULT_MAX_CONNECTIONS,
747 #[cfg(feature = "tls")]
748 tls_handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
749 error_handler: default_error_handler(),
750 cors_config: None,
751 middlewares: Vec::new(),
752 }
753 }
754
755 pub fn wrap(mut self, middleware: Middleware<S>) -> Self {
759 self.middlewares.push(middleware);
760 self
761 }
762
763 fn apply_middlewares(&self, handler: Handler<S>) -> Handler<S> {
764 self.middlewares.iter().rev().fold(handler, |acc, mw| mw(acc))
765 }
766
767 pub fn with_max_body_size(mut self, max: usize) -> Self {
769 self.max_body_size = max;
770 self
771 }
772
773 pub fn with_header_read_timeout(mut self, d: Duration) -> Self {
775 self.header_read_timeout = d;
776 self
777 }
778
779 #[cfg(feature = "tls")]
782 pub fn with_tls_handshake_timeout(mut self, d: Duration) -> Self {
783 self.tls_handshake_timeout = d;
784 self
785 }
786
787 pub fn with_max_connections(mut self, max: usize) -> Self {
789 self.max_connections = max;
790 self
791 }
792
793 pub fn with_error_handler(
794 mut self,
795 f: impl Fn(StatusCode, &str) -> Response<ResponseBody> + Send + Sync + 'static,
796 ) -> Self {
797 self.error_handler = Arc::new(f);
798 self
799 }
800
801 pub fn with_cors(mut self, config: CorsConfig) -> Self {
802 self.cors_config = Some(config);
803 self
804 }
805
806 pub fn get(mut self, path: &str, handler: Handler<S>) -> Self {
807 let handler = self.apply_middlewares(handler);
808 self.router.insert(Method::GET, path, handler);
809 self
810 }
811
812 pub fn post(mut self, path: &str, handler: Handler<S>) -> Self {
813 let handler = self.apply_middlewares(handler);
814 self.router.insert(Method::POST, path, handler);
815 self
816 }
817
818 pub fn put(mut self, path: &str, handler: Handler<S>) -> Self {
819 let handler = self.apply_middlewares(handler);
820 self.router.insert(Method::PUT, path, handler);
821 self
822 }
823
824 pub fn delete(mut self, path: &str, handler: Handler<S>) -> Self {
825 let handler = self.apply_middlewares(handler);
826 self.router.insert(Method::DELETE, path, handler);
827 self
828 }
829
830 pub fn seal(self) -> App<S> {
831 App {
832 state: self.state,
833 router: Arc::new(self.router),
834 max_body_size: self.max_body_size,
835 header_read_timeout: self.header_read_timeout,
836 max_connections: self.max_connections,
837 #[cfg(feature = "tls")]
838 tls_handshake_timeout: self.tls_handshake_timeout,
839 error_handler: self.error_handler,
840 cors_config: self.cors_config,
841 }
842 }
843}
844
845impl RouteBuilder<()> {
846 pub fn stateless() -> Self {
847 RouteBuilder::new(())
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854 use std::sync::Mutex;
855 use std::sync::atomic::{AtomicUsize, Ordering};
856 use http_body_util::BodyExt;
857
858 #[test]
859 fn ephemeral_bind_addr_is_loopback_only() {
860 assert_eq!(
861 ephemeral_bind_addr().ip(),
862 std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)
863 );
864 }
865
866 #[test]
867 fn backoff_delays_double_up_to_a_cap() {
868 let mut backoff = Backoff::new();
869
870 assert_eq!(backoff.next_delay(), ACCEPT_BACKOFF_INITIAL);
871 assert_eq!(backoff.next_delay(), ACCEPT_BACKOFF_INITIAL * 2);
872 assert_eq!(backoff.next_delay(), ACCEPT_BACKOFF_INITIAL * 4);
873
874 let mut last = Duration::ZERO;
876 for _ in 0..20 {
877 last = backoff.next_delay();
878 }
879 assert_eq!(last, ACCEPT_BACKOFF_MAX);
880 }
881
882 #[test]
883 fn backoff_reset_returns_to_initial_delay() {
884 let mut backoff = Backoff::new();
885 backoff.next_delay();
886 backoff.next_delay();
887 backoff.reset();
888 assert_eq!(backoff.next_delay(), ACCEPT_BACKOFF_INITIAL);
889 }
890
891 struct FlakyListener {
895 inner: TcpListener,
896 remaining_failures: AtomicUsize,
897 attempts: Mutex<Vec<tokio::time::Instant>>,
898 }
899
900 impl TcpAccept for FlakyListener {
901 async fn accept(&self) -> std::io::Result<(TcpStream, SocketAddr)> {
902 self.attempts.lock().unwrap().push(tokio::time::Instant::now());
903 if self.remaining_failures.fetch_sub(1, Ordering::SeqCst) > 0 {
904 Err(std::io::Error::other("simulated accept error"))
905 } else {
906 TcpAccept::accept(&self.inner).await
907 }
908 }
909 }
910
911 #[tokio::test(start_paused = true)]
912 async fn accept_loop_backs_off_between_repeated_errors_instead_of_busy_spinning() {
913 let inner = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
914 let addr = inner.local_addr().unwrap();
915
916 let flaky = FlakyListener {
917 inner,
918 remaining_failures: AtomicUsize::new(5),
919 attempts: Mutex::new(Vec::new()),
920 };
921
922 tokio::spawn(async move {
923 let _ = TcpStream::connect(addr).await;
924 });
925
926 let mut backoff = Backoff::new();
927 accept_with_backoff(&flaky, &mut backoff).await;
928
929 let recorded = flaky.attempts.lock().unwrap();
930 assert_eq!(recorded.len(), 6, "5 failures then 1 success");
931
932 let expected_gaps = [
933 ACCEPT_BACKOFF_INITIAL,
934 ACCEPT_BACKOFF_INITIAL * 2,
935 ACCEPT_BACKOFF_INITIAL * 4,
936 ACCEPT_BACKOFF_INITIAL * 8,
937 ACCEPT_BACKOFF_INITIAL * 16,
938 ];
939 for (i, expected) in expected_gaps.iter().enumerate() {
940 let gap = recorded[i + 1] - recorded[i];
941 assert_eq!(
942 gap, *expected,
943 "gap between attempt {i} and {} should reflect the backoff delay, not a busy spin",
944 i + 1
945 );
946 }
947 }
948
949 #[tokio::test]
950 async fn error_handler_sanitizes_5xx_in_response_body() {
951 take_error_log(); let resp = error_response(StatusCode::INTERNAL_SERVER_ERROR, "raw db connection string leaked");
953
954 let (parts, body) = resp.into_parts();
955 assert_eq!(parts.status, StatusCode::INTERNAL_SERVER_ERROR);
956
957 let collected = body.collect().await.unwrap();
958 let bytes = collected.to_bytes();
959 let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
960 let msg = json.get("message").and_then(|v| v.as_str()).unwrap();
961 assert_eq!(msg, "internal server error", "5xx message should be sanitized");
962 }
963
964 #[test]
965 fn error_handler_captures_5xx_message_in_log() {
966 take_error_log(); let sensitive_msg = "raw db connection string leaked";
968 error_response(StatusCode::INTERNAL_SERVER_ERROR, sensitive_msg);
969
970 let log = take_error_log();
971 assert_eq!(log.len(), 1);
972 assert_eq!(log[0].0, 500);
973 assert_eq!(log[0].1, sensitive_msg);
974 }
975
976 #[tokio::test]
977 async fn error_handler_passes_through_4xx_in_response_body() {
978 take_error_log(); let msg = "bad request";
980 let resp = error_response(StatusCode::BAD_REQUEST, msg);
981
982 let (parts, body) = resp.into_parts();
983 assert_eq!(parts.status, StatusCode::BAD_REQUEST);
984
985 let collected = body.collect().await.unwrap();
986 let bytes = collected.to_bytes();
987 let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
988 let response_msg = json.get("message").and_then(|v| v.as_str()).unwrap();
989 assert_eq!(response_msg, msg, "4xx message should pass through");
990 }
991
992 #[test]
993 fn error_handler_logs_4xx_messages() {
994 take_error_log(); let msg = "bad request";
996 error_response(StatusCode::BAD_REQUEST, msg);
997
998 let log = take_error_log();
999 assert_eq!(log.len(), 1);
1000 assert_eq!(log[0].0, 400);
1001 assert_eq!(log[0].1, msg);
1002 }
1003}