use std::any::Any;
use std::error::Error;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::{io, mem};
use failure;
use futures::future::{self, Future, FutureResult, IntoFuture};
use futures::Async;
use hyper::{Body, Response, StatusCode};
use log::error;
use crate::handler::{Handler, HandlerError, IntoResponse, NewHandler};
use crate::state::{request_id, State};
type CompatError = failure::Compat<failure::Error>;
pub(super) fn call_handler<'a, T>(
t: &T,
state: AssertUnwindSafe<State>,
) -> Box<dyn Future<Item = Response<Body>, Error = CompatError> + Send + 'a>
where
T: NewHandler + 'a,
{
let res = catch_unwind(move || {
t.new_handler()
.into_future()
.map_err(failure::Error::compat)
.and_then(move |handler| {
let AssertUnwindSafe(state) = state;
handler.handle(state).then(move |result| match result {
Ok((_state, res)) => future::ok(res),
Err((state, err)) => finalize_error_response(state, err),
})
})
});
if let Ok(f) = res {
return Box::new(
UnwindSafeFuture::new(f)
.catch_unwind()
.then(finalize_catch_unwind_response), );
}
Box::new(finalize_panic_response())
}
fn finalize_error_response(
state: State,
err: HandlerError,
) -> FutureResult<Response<Body>, CompatError> {
{
let err_description = err
.source()
.map(Error::description)
.unwrap_or_else(|| err.description());
error!(
"[ERROR][{}][Error: {}]",
request_id(&state),
err_description
);
}
future::ok(err.into_response(&state))
}
fn finalize_panic_response() -> FutureResult<Response<Body>, CompatError> {
error!("[PANIC][A panic occurred while invoking the handler]");
future::ok(
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::default())
.unwrap(),
)
}
fn finalize_catch_unwind_response(
result: Result<Result<Response<Body>, CompatError>, Box<dyn Any + Send>>,
) -> FutureResult<Response<Body>, CompatError> {
let response = result
.unwrap_or_else(|_| {
let e = io::Error::new(
io::ErrorKind::Other,
"Attempting to poll the future caused a panic",
);
Err(failure::Error::from(e).compat())
})
.unwrap_or_else(|_| {
error!("[PANIC][A panic occurred while polling the future]");
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::default())
.unwrap()
});
future::ok(response)
}
enum UnwindSafeFuture<F>
where
F: Future<Error = CompatError> + Send,
{
Available(AssertUnwindSafe<F>),
Poisoned,
}
impl<F> Future for UnwindSafeFuture<F>
where
F: Future<Error = CompatError> + Send,
{
type Item = F::Item;
type Error = CompatError;
fn poll(&mut self) -> Result<Async<Self::Item>, CompatError> {
match mem::replace(self, UnwindSafeFuture::Poisoned) {
UnwindSafeFuture::Available(mut f) => {
let r = f.poll();
*self = UnwindSafeFuture::Available(f);
r
}
UnwindSafeFuture::Poisoned => {
let e = io::Error::new(
io::ErrorKind::Other,
"Poisoned future due to previous panic",
);
Err(failure::Error::from(e).compat())
}
}
}
}
impl<F> UnwindSafeFuture<F>
where
F: Future<Error = CompatError> + Send,
{
fn new(f: F) -> UnwindSafeFuture<F> {
UnwindSafeFuture::Available(AssertUnwindSafe(f))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io;
use hyper::{HeaderMap, Method, StatusCode};
use crate::handler::{HandlerFuture, IntoHandlerError};
use crate::helpers::http::response::create_empty_response;
use crate::state::set_request_id;
#[test]
fn success() {
let new_handler = || {
Ok(|state| {
let res = create_empty_response(&state, StatusCode::ACCEPTED);
(state, res)
})
};
let mut state = State::new();
state.put(HeaderMap::new());
state.put(Method::GET);
set_request_id(&mut state);
let r = call_handler(&new_handler, AssertUnwindSafe(state));
let response = r.wait().unwrap();
assert_eq!(response.status(), StatusCode::ACCEPTED);
}
#[test]
fn async_success_repeat_poll() {
let new_handler = || {
Ok(|state| {
let f = future::lazy(move || {
let res = create_empty_response(&state, StatusCode::ACCEPTED);
future::ok((state, res))
});
let f = future::lazy(move || f);
let f = future::lazy(move || f);
let f = future::lazy(move || f);
Box::new(f) as Box<HandlerFuture>
})
};
let mut state = State::new();
state.put(HeaderMap::new());
state.put(Method::GET);
set_request_id(&mut state);
let r = call_handler(&new_handler, AssertUnwindSafe(state));
let response = r.wait().unwrap();
assert_eq!(response.status(), StatusCode::ACCEPTED);
}
#[test]
fn error() {
let new_handler = || {
Ok(|state| {
Box::new(future::err((
state,
io::Error::last_os_error().into_handler_error(),
))) as Box<HandlerFuture>
})
};
let mut state = State::new();
state.put(HeaderMap::new());
state.put(Method::GET);
set_request_id(&mut state);
let r = call_handler(&new_handler, AssertUnwindSafe(state));
let response = r.wait().unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn panic() {
let new_handler = || {
Ok(|_| {
let val: Option<Box<HandlerFuture>> = None;
val.expect("test panic")
})
};
let mut state = State::new();
state.put(HeaderMap::new());
state.put(Method::GET);
set_request_id(&mut state);
let r = call_handler(&new_handler, AssertUnwindSafe(state));
let response = r.wait().unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn async_panic() {
let new_handler = || {
Ok(|_| {
let val: Option<Box<HandlerFuture>> = None;
Box::new(future::lazy(move || val.expect("test panic"))) as Box<HandlerFuture>
})
};
let mut state = State::new();
state.put(HeaderMap::new());
state.put(Method::GET);
set_request_id(&mut state);
let r = call_handler(&new_handler, AssertUnwindSafe(state));
let response = r.wait().unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn async_panic_repeat_poll() {
let new_handler = || {
Ok(|_| {
let val: Option<Box<HandlerFuture>> = None;
let f = future::lazy(move || val.expect("test panic"));
let f = future::lazy(move || f);
let f = future::lazy(move || f);
let f = future::lazy(move || f);
Box::new(f) as Box<HandlerFuture>
})
};
let mut state = State::new();
state.put(HeaderMap::new());
state.put(Method::GET);
set_request_id(&mut state);
let r = call_handler(&new_handler, AssertUnwindSafe(state));
let response = r.wait().unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}