nidus_http/middleware/
catch_panic.rs1use std::{
10 any::Any,
11 panic::{AssertUnwindSafe, catch_unwind},
12 task::{Context, Poll},
13};
14
15use axum::{body::Body, extract::Request};
16use futures_util::{
17 FutureExt,
18 future::{CatchUnwind, Either, Map, Ready, ready},
19};
20use http::{Response, StatusCode};
21use tower::{Layer, Service};
22
23pub fn catch_panic_layer() -> CatchPanicLayer {
28 CatchPanicLayer
29}
30
31#[derive(Clone, Copy, Debug, Default)]
38pub struct CatchPanicLayer;
39
40impl<S> Layer<S> for CatchPanicLayer {
41 type Service = CatchPanicService<S>;
42
43 fn layer(&self, inner: S) -> Self::Service {
44 CatchPanicService { inner }
45 }
46}
47
48#[derive(Clone, Debug)]
50pub struct CatchPanicService<S> {
51 inner: S,
52}
53
54type PanicPayload = Box<dyn Any + Send + 'static>;
55type CatchPanicResult<E> = Result<Result<Response<Body>, E>, PanicPayload>;
56type CatchPanicResultMapper<E> = fn(CatchPanicResult<E>) -> Result<Response<Body>, E>;
57type ImmediatePanicMapper<E> = fn(PanicPayload) -> Result<Response<Body>, E>;
58type CatchPanicFuture<F, E> = Either<
59 Map<CatchUnwind<AssertUnwindSafe<F>>, CatchPanicResultMapper<E>>,
60 Map<Ready<PanicPayload>, ImmediatePanicMapper<E>>,
61>;
62
63impl<S> Service<Request> for CatchPanicService<S>
64where
65 S: Service<Request, Response = Response<Body>> + Send + 'static,
66 S::Future: Send + 'static,
67 S::Error: Send + 'static,
68{
69 type Response = Response<Body>;
70 type Error = S::Error;
71 type Future = CatchPanicFuture<S::Future, S::Error>;
72
73 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
74 self.inner.poll_ready(cx)
75 }
76
77 fn call(&mut self, request: Request) -> Self::Future {
78 match catch_unwind(AssertUnwindSafe(|| self.inner.call(request))) {
82 Ok(future) => Either::Left(
83 AssertUnwindSafe(future)
84 .catch_unwind()
85 .map(map_catch_panic_result::<S::Error> as CatchPanicResultMapper<S::Error>),
86 ),
87 Err(payload) => Either::Right(
88 ready(payload)
89 .map(map_immediate_panic::<S::Error> as ImmediatePanicMapper<S::Error>),
90 ),
91 }
92 }
93}
94
95fn map_catch_panic_result<E>(result: CatchPanicResult<E>) -> Result<Response<Body>, E> {
96 match result {
97 Ok(result) => result,
98 Err(payload) => map_immediate_panic(payload),
99 }
100}
101
102fn map_immediate_panic<E>(payload: PanicPayload) -> Result<Response<Body>, E> {
103 log_panic(&payload);
104 Ok(internal_server_error())
105}
106
107fn log_panic(payload: &PanicPayload) {
108 if let Some(message) = payload.downcast_ref::<String>() {
109 tracing::error!(http.status = 500, panic.message = %message, "request handler panicked");
110 } else if let Some(message) = payload.downcast_ref::<&'static str>() {
111 tracing::error!(http.status = 500, panic.message = %message, "request handler panicked");
112 } else {
113 tracing::error!(
114 http.status = 500,
115 "request handler panicked with non-string payload"
116 );
117 }
118}
119
120fn internal_server_error() -> Response<Body> {
121 let mut response = Response::new(Body::empty());
122 *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
123 response
124}