src/server/rest.rs: switch to async
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
		| @ -1,31 +1,27 @@ | ||||
| use crate::tools; | ||||
| use crate::api_schema::*; | ||||
| use crate::api_schema::router::*; | ||||
| use crate::api_schema::config::*; | ||||
| use crate::auth_helpers::*; | ||||
| use super::environment::RestEnvironment; | ||||
| use super::formatter::*; | ||||
|  | ||||
| use std::path::{Path, PathBuf}; | ||||
| use std::sync::Arc; | ||||
| use std::collections::HashMap; | ||||
| use std::path::{Path, PathBuf}; | ||||
| use std::pin::Pin; | ||||
| use std::sync::Arc; | ||||
| use std::task::{Context, Poll}; | ||||
|  | ||||
| use failure::*; | ||||
| use futures::future::{self, Either, FutureExt, TryFutureExt}; | ||||
| use futures::stream::TryStreamExt; | ||||
| use hyper::header; | ||||
| use hyper::http::request::Parts; | ||||
| use hyper::rt::Future; | ||||
| use hyper::{Body, Request, Response, StatusCode}; | ||||
| use serde_json::{json, Value}; | ||||
| use tokio::fs::File; | ||||
| use url::form_urlencoded; | ||||
|  | ||||
| use futures::future::{self, Either}; | ||||
| //use tokio::prelude::*; | ||||
| //use tokio::timer::Delay; | ||||
| use tokio::fs::File; | ||||
| //use bytes::{BytesMut, BufMut}; | ||||
|  | ||||
| //use hyper::body::Payload; | ||||
| use hyper::http::request::Parts; | ||||
| use hyper::{Body, Request, Response, StatusCode}; | ||||
| use hyper::service::{Service, MakeService}; | ||||
| use hyper::rt::{Future, Stream}; | ||||
| use hyper::header; | ||||
| use super::environment::RestEnvironment; | ||||
| use super::formatter::*; | ||||
| use crate::api_schema::config::*; | ||||
| use crate::api_schema::router::*; | ||||
| use crate::api_schema::*; | ||||
| use crate::auth_helpers::*; | ||||
| use crate::tools; | ||||
|  | ||||
| extern "C"  { fn tzset(); } | ||||
|  | ||||
| @ -40,47 +36,48 @@ impl RestServer { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MakeService<&tokio_openssl::SslStream<tokio::net::TcpStream>> for RestServer | ||||
| { | ||||
|     type ReqBody = Body; | ||||
|     type ResBody = Body; | ||||
| impl tower_service::Service<&tokio_openssl::SslStream<tokio::net::TcpStream>> for RestServer { | ||||
|     type Response = ApiService; | ||||
|     type Error = Error; | ||||
|     type MakeError = Error; | ||||
|     type Service = ApiService; | ||||
|     type Future = Box<dyn Future<Item = Self::Service, Error = Self::MakeError> + Send>; | ||||
|     fn make_service(&mut self, ctx: &tokio_openssl::SslStream<tokio::net::TcpStream>) -> Self::Future { | ||||
|         match ctx.get_ref().get_ref().peer_addr() { | ||||
|     type Future = Pin<Box<dyn Future<Output = Result<ApiService, Error>> + Send>>; | ||||
|  | ||||
|     fn poll_ready(&mut self, _cx: &mut Context) -> Poll<Result<(), Self::Error>> { | ||||
|         Poll::Ready(Ok(())) | ||||
|     } | ||||
|  | ||||
|     fn call(&mut self, ctx: &tokio_openssl::SslStream<tokio::net::TcpStream>) -> Self::Future { | ||||
|         match ctx.get_ref().peer_addr() { | ||||
|             Err(err) => { | ||||
|                 Box::new(future::err(format_err!("unable to get peer address - {}", err))) | ||||
|                 future::err(format_err!("unable to get peer address - {}", err)).boxed() | ||||
|             } | ||||
|             Ok(peer) => { | ||||
|                 Box::new(future::ok(ApiService { peer, api_config: self.api_config.clone() })) | ||||
|                 future::ok(ApiService { peer, api_config: self.api_config.clone() }).boxed() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MakeService<&tokio::net::TcpStream> for RestServer | ||||
| { | ||||
|     type ReqBody = Body; | ||||
|     type ResBody = Body; | ||||
| impl tower_service::Service<&tokio::net::TcpStream> for RestServer { | ||||
|     type Response = ApiService; | ||||
|     type Error = Error; | ||||
|     type MakeError = Error; | ||||
|     type Service = ApiService; | ||||
|     type Future = Box<dyn Future<Item = Self::Service, Error = Self::MakeError> + Send>; | ||||
|     fn make_service(&mut self, ctx: &tokio::net::TcpStream) -> Self::Future { | ||||
|     type Future = Pin<Box<dyn Future<Output = Result<ApiService, Error>> + Send>>; | ||||
|  | ||||
|     fn poll_ready(&mut self, _cx: &mut Context) -> Poll<Result<(), Self::Error>> { | ||||
|         Poll::Ready(Ok(())) | ||||
|     } | ||||
|  | ||||
|     fn call(&mut self, ctx: &tokio::net::TcpStream) -> Self::Future { | ||||
|         match ctx.peer_addr() { | ||||
|             Err(err) => { | ||||
|                 Box::new(future::err(format_err!("unable to get peer address - {}", err))) | ||||
|                 future::err(format_err!("unable to get peer address - {}", err)).boxed() | ||||
|             } | ||||
|             Ok(peer) => { | ||||
|                 Box::new(future::ok(ApiService { peer, api_config: self.api_config.clone() })) | ||||
|                 future::ok(ApiService { peer, api_config: self.api_config.clone() }).boxed() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| pub struct ApiService { | ||||
|     pub peer: std::net::SocketAddr, | ||||
|     pub api_config: Arc<ApiConfig>, | ||||
| @ -109,19 +106,22 @@ fn log_response( | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Service for ApiService { | ||||
|     type ReqBody = Body; | ||||
|     type ResBody = Body; | ||||
| impl tower_service::Service<Request<Body>> for ApiService { | ||||
|     type Response = Response<Body>; | ||||
|     type Error = Error; | ||||
|     type Future = Box<dyn Future<Item = Response<Body>, Error = Self::Error> + Send>; | ||||
|     type Future = Pin<Box<dyn Future<Output = Result<Response<Body>, Self::Error>> + Send>>; | ||||
|  | ||||
|     fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future { | ||||
|     fn poll_ready(&mut self, _cx: &mut Context) -> Poll<Result<(), Self::Error>> { | ||||
|         Poll::Ready(Ok(())) | ||||
|     } | ||||
|  | ||||
|     fn call(&mut self, req: Request<Body>) -> Self::Future { | ||||
|         let path = req.uri().path().to_owned(); | ||||
|         let method = req.method().clone(); | ||||
|  | ||||
|         let peer = self.peer.clone(); | ||||
|         Box::new(handle_request(self.api_config.clone(), req).then(move |result| { | ||||
|             match result { | ||||
|         Pin::from(handle_request(self.api_config.clone(), req)) | ||||
|             .map(move |result| match result { | ||||
|                 Ok(res) => { | ||||
|                     log_response(&peer, method, &path, &res); | ||||
|                     Ok::<_, Self::Error>(res) | ||||
| @ -139,8 +139,8 @@ impl Service for ApiService { | ||||
|                         Ok(resp) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         })) | ||||
|             }) | ||||
|             .boxed() | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -149,7 +149,7 @@ fn get_request_parameters_async( | ||||
|     parts: Parts, | ||||
|     req_body: Body, | ||||
|     uri_param: HashMap<String, String>, | ||||
| ) -> Box<dyn Future<Item = Value, Error = failure::Error> + Send> | ||||
| ) -> Box<dyn Future<Output = Result<Value, failure::Error>> + Send> | ||||
| { | ||||
|     let mut is_json = false; | ||||
|  | ||||
| @ -169,15 +169,15 @@ fn get_request_parameters_async( | ||||
|  | ||||
|     let resp = req_body | ||||
|         .map_err(|err| http_err!(BAD_REQUEST, format!("Promlems reading request body: {}", err))) | ||||
|         .fold(Vec::new(), |mut acc, chunk| { | ||||
|         .try_fold(Vec::new(), |mut acc, chunk| async move { | ||||
|             if acc.len() + chunk.len() < 64*1024 { //fimxe: max request body size? | ||||
|                 acc.extend_from_slice(&*chunk); | ||||
|                 Ok(acc) | ||||
|             } else { | ||||
|                 Err(http_err!(BAD_REQUEST, format!("Request body too large"))) | ||||
|             } | ||||
|             else { Err(http_err!(BAD_REQUEST, format!("Request body too large"))) } | ||||
|         }) | ||||
|         .and_then(move |body| { | ||||
|  | ||||
|         .and_then(move |body| async move { | ||||
|             let utf8 = std::str::from_utf8(&body)?; | ||||
|  | ||||
|             let obj_schema = &info.parameters; | ||||
| @ -216,7 +216,7 @@ fn get_request_parameters_async( | ||||
|             let params = parse_parameter_strings(¶m_list, obj_schema, true)?; | ||||
|  | ||||
|             Ok(params) | ||||
|         }); | ||||
|         }.boxed()); | ||||
|  | ||||
|     Box::new(resp) | ||||
| } | ||||
| @ -227,8 +227,7 @@ fn proxy_protected_request( | ||||
|     info: &'static ApiMethod, | ||||
|     mut parts: Parts, | ||||
|     req_body: Body, | ||||
| ) -> BoxFut | ||||
| { | ||||
| ) -> BoxFut { | ||||
|  | ||||
|     let mut uri_parts = parts.uri.clone().into_parts(); | ||||
|  | ||||
| @ -243,19 +242,22 @@ fn proxy_protected_request( | ||||
|     let resp = hyper::client::Client::new() | ||||
|         .request(request) | ||||
|         .map_err(Error::from) | ||||
|         .map(|mut resp| { | ||||
|         .map_ok(|mut resp| { | ||||
|             resp.extensions_mut().insert(NoLogExtension()); | ||||
|             resp | ||||
|         }); | ||||
|  | ||||
|  | ||||
|     let resp = if info.reload_timezone { | ||||
|         Either::A(resp.then(|resp| {unsafe { tzset() }; resp })) | ||||
|     } else { | ||||
|         Either::B(resp) | ||||
|     }; | ||||
|   | ||||
|     return Box::new(resp); | ||||
|     let reload_timezone = info.reload_timezone; | ||||
|     Box::new(async move { | ||||
|         let result = resp.await; | ||||
|         if reload_timezone { | ||||
|             unsafe { | ||||
|                 tzset(); | ||||
|             } | ||||
|         } | ||||
|         result | ||||
|     }) | ||||
| } | ||||
|  | ||||
| pub fn handle_sync_api_request<Env: RpcEnvironment>( | ||||
| @ -271,14 +273,16 @@ pub fn handle_sync_api_request<Env: RpcEnvironment>( | ||||
|  | ||||
|     let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000); | ||||
|  | ||||
|     let resp = params | ||||
|     let resp = Pin::from(params) | ||||
|         .and_then(move |params| { | ||||
|             let mut delay = false; | ||||
|              let resp = match (info.handler.as_ref().unwrap())(params, info, &mut rpcenv) { | ||||
|             let resp = match (info.handler.as_ref().unwrap())(params, info, &mut rpcenv) { | ||||
|                 Ok(data) => (formatter.format_data)(data, &rpcenv), | ||||
|                 Err(err) => { | ||||
|                      if let Some(httperr) = err.downcast_ref::<HttpError>() { | ||||
|                         if httperr.code == StatusCode::UNAUTHORIZED { delay = true; } | ||||
|                     if let Some(httperr) = err.downcast_ref::<HttpError>() { | ||||
|                         if httperr.code == StatusCode::UNAUTHORIZED { | ||||
|                             delay = true; | ||||
|                         } | ||||
|                     } | ||||
|                     (formatter.format_error)(err) | ||||
|                 } | ||||
| @ -289,13 +293,13 @@ pub fn handle_sync_api_request<Env: RpcEnvironment>( | ||||
|             } | ||||
|  | ||||
|             if delay { | ||||
|                 Either::A(delayed_response(resp, delay_unauth_time)) | ||||
|                 Either::Left(delayed_response(resp, delay_unauth_time)) | ||||
|             } else { | ||||
|                 Either::B(future::ok(resp)) | ||||
|                 Either::Right(future::ok(resp)) | ||||
|             } | ||||
|         }) | ||||
|         .or_else(move |err| { | ||||
|             Ok((formatter.format_error)(err)) | ||||
|             future::ok((formatter.format_error)(err)) | ||||
|         }); | ||||
|  | ||||
|     Box::new(resp) | ||||
| @ -426,56 +430,58 @@ fn extension_to_content_type(filename: &Path) -> (&'static str, bool) { | ||||
|     ("application/octet-stream", false) | ||||
| } | ||||
|  | ||||
| fn simple_static_file_download(filename: PathBuf) ->  BoxFut { | ||||
| async fn simple_static_file_download(filename: PathBuf) -> Result<Response<Body>, Error> { | ||||
|  | ||||
|     let (content_type, _nocomp) = extension_to_content_type(&filename); | ||||
|  | ||||
|     Box::new(File::open(filename) | ||||
|         .map_err(|err| http_err!(BAD_REQUEST, format!("File open failed: {}", err))) | ||||
|         .and_then(move |file| { | ||||
|             let buf: Vec<u8> = Vec::new(); | ||||
|             tokio::io::read_to_end(file, buf) | ||||
|                 .map_err(|err| http_err!(BAD_REQUEST, format!("File read failed: {}", err))) | ||||
|                 .and_then(move |data| { | ||||
|                     let mut response = Response::new(data.1.into()); | ||||
|                     response.headers_mut().insert( | ||||
|                         header::CONTENT_TYPE, | ||||
|                         header::HeaderValue::from_static(content_type)); | ||||
|                     Ok(response) | ||||
|                 }) | ||||
|         })) | ||||
|     use tokio::io::AsyncReadExt; | ||||
|  | ||||
|     let mut file = File::open(filename) | ||||
|         .await | ||||
|         .map_err(|err| http_err!(BAD_REQUEST, format!("File open failed: {}", err)))?; | ||||
|  | ||||
|     let mut data: Vec<u8> = Vec::new(); | ||||
|     file.read_to_end(&mut data) | ||||
|         .await | ||||
|         .map_err(|err| http_err!(BAD_REQUEST, format!("File read failed: {}", err)))?; | ||||
|  | ||||
|     let mut response = Response::new(data.into()); | ||||
|     response.headers_mut().insert( | ||||
|         header::CONTENT_TYPE, | ||||
|         header::HeaderValue::from_static(content_type)); | ||||
|     Ok(response) | ||||
| } | ||||
|  | ||||
| fn chuncked_static_file_download(filename: PathBuf) ->  BoxFut { | ||||
|  | ||||
| async fn chuncked_static_file_download(filename: PathBuf) -> Result<Response<Body>, Error> { | ||||
|     let (content_type, _nocomp) = extension_to_content_type(&filename); | ||||
|  | ||||
|     Box::new(File::open(filename) | ||||
|         .map_err(|err| http_err!(BAD_REQUEST, format!("File open failed: {}", err))) | ||||
|         .and_then(move |file| { | ||||
|             let payload = tokio::codec::FramedRead::new(file, tokio::codec::BytesCodec::new()). | ||||
|                 map(|bytes| hyper::Chunk::from(bytes.freeze())); | ||||
|             let body = Body::wrap_stream(payload); | ||||
|     let file = File::open(filename) | ||||
|         .await | ||||
|         .map_err(|err| http_err!(BAD_REQUEST, format!("File open failed: {}", err)))?; | ||||
|  | ||||
|             // fixme: set other headers ? | ||||
|             Ok(Response::builder() | ||||
|                .status(StatusCode::OK) | ||||
|                .header(header::CONTENT_TYPE, content_type) | ||||
|                .body(body) | ||||
|                .unwrap()) | ||||
|         })) | ||||
|     let payload = tokio::codec::FramedRead::new(file, tokio::codec::BytesCodec::new()) | ||||
|         .map_ok(|bytes| hyper::Chunk::from(bytes.freeze())); | ||||
|     let body = Body::wrap_stream(payload); | ||||
|  | ||||
|     // fixme: set other headers ? | ||||
|     Ok(Response::builder() | ||||
|        .status(StatusCode::OK) | ||||
|        .header(header::CONTENT_TYPE, content_type) | ||||
|        .body(body) | ||||
|        .unwrap() | ||||
|     ) | ||||
| } | ||||
|  | ||||
| fn handle_static_file_download(filename: PathBuf) ->  BoxFut { | ||||
|  | ||||
|     let response = tokio::fs::metadata(filename.clone()) | ||||
|         .map_err(|err| http_err!(BAD_REQUEST, format!("File access problems: {}", err))) | ||||
|         .and_then(|metadata| { | ||||
|         .and_then(|metadata| async move { | ||||
|             if metadata.len() < 1024*32 { | ||||
|                 Either::A(simple_static_file_download(filename)) | ||||
|                 simple_static_file_download(filename).await | ||||
|             } else { | ||||
|                 Either::B(chuncked_static_file_download(filename)) | ||||
|              } | ||||
|                 chuncked_static_file_download(filename).await | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     return Box::new(response); | ||||
| @ -523,11 +529,13 @@ fn check_auth(method: &hyper::Method, ticket: &Option<String>, token: &Option<St | ||||
|     Ok(username) | ||||
| } | ||||
|  | ||||
| fn delayed_response(resp: Response<Body>, delay_unauth_time: std::time::Instant) -> BoxFut { | ||||
|  | ||||
|     Box::new(tokio::timer::Delay::new(delay_unauth_time) | ||||
|         .map_err(|err| http_err!(INTERNAL_SERVER_ERROR, format!("tokio timer delay error: {}", err))) | ||||
|         .and_then(|_| Ok(resp))) | ||||
| async fn delayed_response( | ||||
|     resp: Response<Body>, | ||||
|     delay_unauth_time: std::time::Instant, | ||||
| ) -> Result<Response<Body>, Error> { | ||||
|     tokio::timer::Delay::new(delay_unauth_time) | ||||
|         .await; | ||||
|     Ok(resp) | ||||
| } | ||||
|  | ||||
| pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> BoxFut { | ||||
| @ -579,7 +587,9 @@ pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> BoxFut { | ||||
|                     Err(err) => { | ||||
|                         // always delay unauthorized calls by 3 seconds (from start of request) | ||||
|                         let err = http_err!(UNAUTHORIZED, format!("permission check failed - {}", err)); | ||||
|                         return delayed_response((formatter.format_error)(err), delay_unauth_time); | ||||
|                         return Box::new( | ||||
|                             delayed_response((formatter.format_error)(err), delay_unauth_time) | ||||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @ -616,7 +626,9 @@ pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> BoxFut { | ||||
|                         let new_token = assemble_csrf_prevention_token(csrf_secret(), &username); | ||||
|                         return Box::new(future::ok(get_index(Some(username), Some(new_token)))); | ||||
|                     } | ||||
|                     _ => return delayed_response(get_index(None, None), delay_unauth_time), | ||||
|                     _ => { | ||||
|                         return Box::new(delayed_response(get_index(None, None), delay_unauth_time)); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 return Box::new(future::ok(get_index(None, None))); | ||||
|  | ||||
		Reference in New Issue
	
	Block a user