improve error handling, use a Service

This commit is contained in:
Dietmar Maurer 2018-11-13 12:36:56 +01:00
parent 035cce945e
commit a974251ee6
2 changed files with 64 additions and 23 deletions

View File

@ -1,9 +1,10 @@
use std::fmt;
use failure::*; use failure::*;
use futures::future::*; use futures::future::*;
use crate::json_schema::*; use crate::json_schema::*;
use serde_json::{Value}; use serde_json::{Value};
use hyper::{Body, Response}; use hyper::{Body, Response, StatusCode};
use std::collections::HashMap; use std::collections::HashMap;
@ -15,6 +16,24 @@ pub struct ApiMethod {
pub async_handler: fn(Value, &ApiMethod) -> Box<Future<Item = Response<Body>, Error = Error> + Send> pub async_handler: fn(Value, &ApiMethod) -> Box<Future<Item = Response<Body>, Error = Error> + Send>
} }
#[derive(Debug, Fail)]
pub struct ApiError {
pub code: StatusCode,
pub message: String,
}
impl ApiError {
pub fn new(code: StatusCode, message: String) -> Self {
ApiError { code, message }
}
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Error {}: {}", self.code, self.message)
}
}
pub struct MethodInfo { pub struct MethodInfo {
pub get: Option<ApiMethod>, pub get: Option<ApiMethod>,
pub put: Option<ApiMethod>, pub put: Option<ApiMethod>,

View File

@ -3,8 +3,8 @@ extern crate apitest;
use failure::*; use failure::*;
use std::collections::HashMap; use std::collections::HashMap;
//use std::io; //use std::io;
use std::fs; //use std::fs;
use std::path::{Path,PathBuf}; use std::path::{PathBuf};
//use std::collections::HashMap; //use std::collections::HashMap;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -16,7 +16,7 @@ use apitest::json_schema::*;
//use serde_derive::{Serialize, Deserialize}; //use serde_derive::{Serialize, Deserialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
use futures::future::*; use futures::future::{self, Either};
//use tokio::prelude::*; //use tokio::prelude::*;
//use tokio::timer::Delay; //use tokio::timer::Delay;
use tokio::fs::File; use tokio::fs::File;
@ -27,7 +27,7 @@ use tokio_codec;
use hyper::http::request::Parts; use hyper::http::request::Parts;
use hyper::{Method, Body, Request, Response, Server, StatusCode}; use hyper::{Method, Body, Request, Response, Server, StatusCode};
use hyper::rt::{Future, Stream}; use hyper::rt::{Future, Stream};
use hyper::service::service_fn; //use hyper::service::service_fn;
use hyper::header; use hyper::header;
//use std::time::{Duration, Instant}; //use std::time::{Duration, Instant};
@ -45,7 +45,7 @@ macro_rules! error_response {
macro_rules! http_error_future { macro_rules! http_error_future {
($status:ident, $msg:expr) => {{ ($status:ident, $msg:expr) => {{
let resp = error_response!($status, $msg); let resp = error_response!($status, $msg);
return Box::new(ok(resp)); return Box::new(future::ok(resp));
}} }}
} }
@ -56,14 +56,14 @@ fn get_request_parameters_async<'a>(
) -> Box<Future<Item = Value, Error = failure::Error> + Send + 'a> ) -> Box<Future<Item = Value, Error = failure::Error> + Send + 'a>
{ {
let resp = req_body let resp = req_body
.map_err(|err| format_err!("Promlems reading request body: {}", err)) .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("Promlems reading request body: {}", err))))
.fold(Vec::new(), |mut acc, chunk| { .fold(Vec::new(), |mut acc, chunk| {
if acc.len() + chunk.len() < 64*1024 { //fimxe: max request body size? if acc.len() + chunk.len() < 64*1024 { //fimxe: max request body size?
acc.extend_from_slice(&*chunk); acc.extend_from_slice(&*chunk);
ok(acc) future::ok(acc)
} else {
err(format_err!("Request body too large"))
} }
//else { ok(acc) } //FIXMEEEEE
else { future::err(Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("Request body too large")))) }
}) })
.and_then(move |body| { .and_then(move |body| {
@ -171,11 +171,11 @@ fn handle_sync_api_request<'a>(
fn simple_static_file_download(filename: PathBuf) -> BoxFut { fn simple_static_file_download(filename: PathBuf) -> BoxFut {
Box::new(File::open(filename) Box::new(File::open(filename)
.map_err(|err| format_err!("File open failed: {}", err)) .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File open failed: {}", err))))
.and_then(|file| { .and_then(|file| {
let buf: Vec<u8> = Vec::new(); let buf: Vec<u8> = Vec::new();
tokio::io::read_to_end(file, buf) tokio::io::read_to_end(file, buf)
.map_err(|err| format_err!("File read failed: {}", err)) .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File read failed: {}", err))))
.and_then(|data| Ok(Response::new(data.1.into()))) .and_then(|data| Ok(Response::new(data.1.into())))
})) }))
} }
@ -183,7 +183,7 @@ fn simple_static_file_download(filename: PathBuf) -> BoxFut {
fn chuncked_static_file_download(filename: PathBuf) -> BoxFut { fn chuncked_static_file_download(filename: PathBuf) -> BoxFut {
Box::new(File::open(filename) Box::new(File::open(filename)
.map_err(|err| format_err!("File open failed: {}", err)) .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File open failed: {}", err))))
.and_then(|file| { .and_then(|file| {
let payload = tokio_codec::FramedRead::new(file, tokio_codec::BytesCodec::new()). let payload = tokio_codec::FramedRead::new(file, tokio_codec::BytesCodec::new()).
map(|bytes| { map(|bytes| {
@ -202,7 +202,7 @@ fn chuncked_static_file_download(filename: PathBuf) -> BoxFut {
fn handle_static_file_download(filename: PathBuf) -> BoxFut { fn handle_static_file_download(filename: PathBuf) -> BoxFut {
let response = tokio::fs::metadata(filename.clone()) let response = tokio::fs::metadata(filename.clone())
.map_err(|err| format_err!("File access problems: {}", err)) .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File access problems: {}", err))))
.and_then(|metadata| { .and_then(|metadata| {
if metadata.len() < 1024*32 { if metadata.len() < 1024*32 {
Either::A(simple_static_file_download(filename)) Either::A(simple_static_file_download(filename))
@ -316,6 +316,35 @@ fn initialize_directory_aliases() -> HashMap<String, PathBuf> {
basedirs basedirs
} }
struct ApiServer {
basedir: PathBuf,
}
impl hyper::service::Service for ApiServer {
type ReqBody = Body;
type ResBody = Body;
type Error = String;
type Future = Box<Future<Item = Response<Body>, Error = String> + Send>;
fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future {
Box::new(handle_request(req).then(|result| {
match result {
Ok(res) => Ok(res),
Err(err) => {
if let Some(apierr) = err.downcast_ref::<ApiError>() {
let mut resp = Response::new(Body::from(apierr.message.clone()));
*resp.status_mut() = apierr.code;
Ok(resp)
} else {
Ok(error_response!(BAD_REQUEST, err.to_string()))
}
}
}
}))
}
}
lazy_static!{ lazy_static!{
static ref DIR_ALIASES: HashMap<String, PathBuf> = initialize_directory_aliases(); static ref DIR_ALIASES: HashMap<String, PathBuf> = initialize_directory_aliases();
} }
@ -330,15 +359,8 @@ fn main() {
let addr = ([127, 0, 0, 1], 8007).into(); let addr = ([127, 0, 0, 1], 8007).into();
let new_svc = || { let new_svc = || {
service_fn(|req| { let service = ApiServer { basedir: "/var/www".into() };
// clumsy way to convert failure::Error to Response future::ok::<_,String>(service)
handle_request(req).then(|result| -> Result<Response<Body>, String> {
match result {
Ok(res) => Ok(res),
Err(err) => Ok(error_response!(BAD_REQUEST, err.to_string())),
}
})
})
}; };
let server = Server::bind(&addr) let server = Server::bind(&addr)