forked from cloudwego/volo
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpanic_handler.rs
More file actions
155 lines (138 loc) · 4.7 KB
/
Copy pathpanic_handler.rs
File metadata and controls
155 lines (138 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! Collections for some panic handlers
//!
//! [`volo::catch_panic::Layer`] can handle panics in services and when panic occurs, it can
//! respond a [`Response`]. This module has some useful handlers for handling panics and
//! returning a response.
use std::any::Any;
use http::StatusCode;
use motore::service::Service;
use volo::catch_panic;
use super::IntoResponse;
use crate::response::Response;
/// Panic handler which can return a fixed payload.
///
/// This type can be constructed by [`fixed_payload`].
#[derive(Clone, Debug)]
pub struct FixedPayload<R> {
payload: R,
}
impl<S, Cx, Req, Resp> catch_panic::Handler<S, Cx, Req> for FixedPayload<Resp>
where
S: Service<Cx, Req, Response = Response> + Send + Sync + 'static,
Cx: Send + 'static,
Req: Send + 'static,
Resp: IntoResponse + Clone,
{
fn handle(
&self,
_: &mut Cx,
_: Box<dyn Any + Send>,
panic_info: catch_panic::PanicInfo,
) -> Result<S::Response, S::Error> {
tracing::error!("[Volo-HTTP] panic_handler: {panic_info}");
Ok(self.payload.clone().into_response())
}
}
/// This function is a panic handler and can work with [`volo::catch_panic::Layer`], it will always
/// return `500 Internal Server Error`.
pub fn always_internal_error<Cx, E>(
_: &mut Cx,
_: Box<dyn Any + Send>,
panic_info: catch_panic::PanicInfo,
) -> Result<Response, E> {
tracing::error!("[Volo-HTTP] panic_handler: {panic_info}");
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
}
/// Create a panic handler which can work with [`volo::catch_panic::Layer`]. The handler will
/// always return the specified fixed payload as response.
pub fn fixed_payload<R>(payload: R) -> FixedPayload<R> {
FixedPayload { payload }
}
#[cfg(test)]
mod panic_handler_tests {
use std::{convert::Infallible, marker::PhantomData};
use http::status::StatusCode;
use motore::{layer::Layer, service::Service};
use super::{always_internal_error, fixed_payload};
use crate::{
body::{Body, BodyConversion},
request::Request,
response::Response,
server::{IntoResponse, test_helpers},
};
struct PanicService<R = Response, E = Infallible> {
_marker: PhantomData<fn(R, E)>,
}
impl<R, E> PanicService<R, E> {
const fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<Cx, Req, Resp, E> Service<Cx, Req> for PanicService<Resp, E>
where
Cx: Send,
Req: Send,
Resp: Send,
{
type Response = Resp;
type Error = E;
async fn call(&self, _: &mut Cx, _: Req) -> Result<Self::Response, Self::Error> {
panic!("oops")
}
}
#[tokio::test]
async fn fixed_payload_test() {
const ERR_MSG: &str = "oops";
// it's ok
{
let srv =
volo::catch_panic::Layer::new(fixed_payload((StatusCode::NOT_FOUND, ERR_MSG)))
.layer(test_helpers::DefaultService::<Response, Infallible>::new());
let resp = srv
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
.await
.into_response();
assert_eq!(resp.status(), StatusCode::OK);
}
// it panics
{
let srv =
volo::catch_panic::Layer::new(fixed_payload((StatusCode::NOT_FOUND, ERR_MSG)))
.layer(PanicService::<Response, Infallible>::new());
let resp = srv
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
.await
.into_response();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
assert_eq!(resp.into_string().await.unwrap(), ERR_MSG);
}
}
#[tokio::test]
async fn internal_error_test() {
// it's ok
{
let srv = volo::catch_panic::Layer::new(always_internal_error)
.layer(test_helpers::DefaultService::<Response, Infallible>::new());
let resp = srv
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
.await
.into_response();
assert_eq!(resp.status(), StatusCode::OK);
}
// it panics
{
let srv = volo::catch_panic::Layer::new(always_internal_error).layer(PanicService::<
Response,
Infallible,
>::new(
));
let resp = srv
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
.await
.into_response();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}
}