improve error handling, use a Service
This commit is contained in:
parent
035cce945e
commit
a974251ee6
|
@ -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>,
|
||||||
|
|
66
src/main.rs
66
src/main.rs
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue