src/server/rest.rs: cleanup async code
This commit is contained in:
@ -43,7 +43,7 @@ impl <E: RpcEnvironment + Clone> H2Service<E> {
|
||||
|
||||
let (path, components) = match tools::normalize_uri_path(parts.uri.path()) {
|
||||
Ok((p,c)) => (p, c),
|
||||
Err(err) => return Box::new(future::err(http_err!(BAD_REQUEST, err.to_string()))),
|
||||
Err(err) => return future::err(http_err!(BAD_REQUEST, err.to_string())).boxed(),
|
||||
};
|
||||
|
||||
self.debug(format!("{} {}", method, path));
|
||||
@ -55,17 +55,17 @@ impl <E: RpcEnvironment + Clone> H2Service<E> {
|
||||
match self.router.find_method(&components, method, &mut uri_param) {
|
||||
None => {
|
||||
let err = http_err!(NOT_FOUND, "Path not found.".to_string());
|
||||
Box::new(future::ok((formatter.format_error)(err)))
|
||||
future::ok((formatter.format_error)(err)).boxed()
|
||||
}
|
||||
Some(api_method) => {
|
||||
match api_method.handler {
|
||||
ApiHandler::Sync(_) => {
|
||||
crate::server::rest::handle_sync_api_request(
|
||||
self.rpcenv.clone(), api_method, formatter, parts, body, uri_param)
|
||||
self.rpcenv.clone(), api_method, formatter, parts, body, uri_param).boxed()
|
||||
}
|
||||
ApiHandler::Async(_) => {
|
||||
crate::server::rest::handle_async_api_request(
|
||||
self.rpcenv.clone(), api_method, formatter, parts, body, uri_param)
|
||||
self.rpcenv.clone(), api_method, formatter, parts, body, uri_param).boxed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use failure::*;
|
||||
use futures::future::{self, Either, FutureExt, TryFutureExt};
|
||||
use futures::future::{self, FutureExt, TryFutureExt};
|
||||
use futures::stream::TryStreamExt;
|
||||
use hyper::header;
|
||||
use hyper::http::request::Parts;
|
||||
@ -17,7 +17,7 @@ use tokio::fs::File;
|
||||
use url::form_urlencoded;
|
||||
|
||||
use proxmox::api::http_err;
|
||||
use proxmox::api::{ApiFuture, ApiHandler, ApiMethod, HttpError};
|
||||
use proxmox::api::{ApiHandler, ApiMethod, HttpError};
|
||||
use proxmox::api::{RpcEnvironment, RpcEnvironmentType};
|
||||
use proxmox::api::schema::{parse_simple_value, verify_json_object, parse_parameter_strings};
|
||||
|
||||
@ -125,7 +125,7 @@ impl tower_service::Service<Request<Body>> for ApiService {
|
||||
let method = req.method().clone();
|
||||
|
||||
let peer = self.peer;
|
||||
Pin::from(handle_request(self.api_config.clone(), req))
|
||||
handle_request(self.api_config.clone(), req)
|
||||
.map(move |result| match result {
|
||||
Ok(res) => {
|
||||
log_response(&peer, method, &path, &res);
|
||||
@ -149,13 +149,13 @@ impl tower_service::Service<Request<Body>> for ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_request_parameters_async<S: 'static + BuildHasher + Send>(
|
||||
async fn get_request_parameters_async<S: 'static + BuildHasher + Send>(
|
||||
info: &'static ApiMethod,
|
||||
parts: Parts,
|
||||
req_body: Body,
|
||||
uri_param: HashMap<String, String, S>,
|
||||
) -> Box<dyn Future<Output = Result<Value, failure::Error>> + Send>
|
||||
{
|
||||
) -> Result<Value, Error> {
|
||||
|
||||
let mut is_json = false;
|
||||
|
||||
if let Some(value) = parts.headers.get(header::CONTENT_TYPE) {
|
||||
@ -166,13 +166,11 @@ fn get_request_parameters_async<S: 'static + BuildHasher + Send>(
|
||||
Ok(Some("application/json")) => {
|
||||
is_json = true;
|
||||
}
|
||||
_ => {
|
||||
return Box::new(future::err(http_err!(BAD_REQUEST, "unsupported content type".to_string())));
|
||||
}
|
||||
_ => bail!("unsupported content type {:?}", value.to_str()),
|
||||
}
|
||||
}
|
||||
|
||||
let resp = req_body
|
||||
let body = req_body
|
||||
.map_err(|err| http_err!(BAD_REQUEST, format!("Promlems reading request body: {}", err)))
|
||||
.try_fold(Vec::new(), |mut acc, chunk| async move {
|
||||
if acc.len() + chunk.len() < 64*1024 { //fimxe: max request body size?
|
||||
@ -181,57 +179,55 @@ fn get_request_parameters_async<S: 'static + BuildHasher + Send>(
|
||||
} else {
|
||||
Err(http_err!(BAD_REQUEST, "Request body too large".to_string()))
|
||||
}
|
||||
})
|
||||
.and_then(move |body| async move {
|
||||
let utf8 = std::str::from_utf8(&body)?;
|
||||
}).await?;
|
||||
|
||||
let obj_schema = &info.parameters;
|
||||
let utf8 = std::str::from_utf8(&body)
|
||||
.map_err(|err| format_err!("Request body not uft8: {}", err))?;
|
||||
|
||||
if is_json {
|
||||
let mut params: Value = serde_json::from_str(utf8)?;
|
||||
for (k, v) in uri_param {
|
||||
if let Some((_optional, prop_schema)) = obj_schema.lookup(&k) {
|
||||
params[&k] = parse_simple_value(&v, prop_schema)?;
|
||||
}
|
||||
}
|
||||
verify_json_object(¶ms, obj_schema)?;
|
||||
return Ok(params);
|
||||
let obj_schema = &info.parameters;
|
||||
|
||||
if is_json {
|
||||
let mut params: Value = serde_json::from_str(utf8)?;
|
||||
for (k, v) in uri_param {
|
||||
if let Some((_optional, prop_schema)) = obj_schema.lookup(&k) {
|
||||
params[&k] = parse_simple_value(&v, prop_schema)?;
|
||||
}
|
||||
}
|
||||
verify_json_object(¶ms, obj_schema)?;
|
||||
return Ok(params);
|
||||
}
|
||||
|
||||
let mut param_list: Vec<(String, String)> = vec![];
|
||||
let mut param_list: Vec<(String, String)> = vec![];
|
||||
|
||||
if !utf8.is_empty() {
|
||||
for (k, v) in form_urlencoded::parse(utf8.as_bytes()).into_owned() {
|
||||
param_list.push((k, v));
|
||||
}
|
||||
}
|
||||
if !utf8.is_empty() {
|
||||
for (k, v) in form_urlencoded::parse(utf8.as_bytes()).into_owned() {
|
||||
param_list.push((k, v));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(query_str) = parts.uri.query() {
|
||||
for (k, v) in form_urlencoded::parse(query_str.as_bytes()).into_owned() {
|
||||
if k == "_dc" { continue; } // skip extjs "disable cache" parameter
|
||||
param_list.push((k, v));
|
||||
}
|
||||
}
|
||||
if let Some(query_str) = parts.uri.query() {
|
||||
for (k, v) in form_urlencoded::parse(query_str.as_bytes()).into_owned() {
|
||||
if k == "_dc" { continue; } // skip extjs "disable cache" parameter
|
||||
param_list.push((k, v));
|
||||
}
|
||||
}
|
||||
|
||||
for (k, v) in uri_param {
|
||||
param_list.push((k.clone(), v.clone()));
|
||||
}
|
||||
for (k, v) in uri_param {
|
||||
param_list.push((k.clone(), v.clone()));
|
||||
}
|
||||
|
||||
let params = parse_parameter_strings(¶m_list, obj_schema, true)?;
|
||||
let params = parse_parameter_strings(¶m_list, obj_schema, true)?;
|
||||
|
||||
Ok(params)
|
||||
}.boxed());
|
||||
|
||||
Box::new(resp)
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
struct NoLogExtension();
|
||||
|
||||
fn proxy_protected_request(
|
||||
async fn proxy_protected_request(
|
||||
info: &'static ApiMethod,
|
||||
mut parts: Parts,
|
||||
req_body: Body,
|
||||
) -> ApiFuture {
|
||||
) -> Result<Response<Body>, Error> {
|
||||
|
||||
let mut uri_parts = parts.uri.clone().into_parts();
|
||||
|
||||
@ -243,96 +239,77 @@ fn proxy_protected_request(
|
||||
|
||||
let request = Request::from_parts(parts, req_body);
|
||||
|
||||
let reload_timezone = info.reload_timezone;
|
||||
|
||||
let resp = hyper::client::Client::new()
|
||||
.request(request)
|
||||
.map_err(Error::from)
|
||||
.map_ok(|mut resp| {
|
||||
resp.extensions_mut().insert(NoLogExtension());
|
||||
resp
|
||||
});
|
||||
})
|
||||
.await?;
|
||||
|
||||
if reload_timezone { unsafe { tzset(); } }
|
||||
|
||||
let reload_timezone = info.reload_timezone;
|
||||
Box::new(async move {
|
||||
let result = resp.await;
|
||||
if reload_timezone {
|
||||
unsafe {
|
||||
tzset();
|
||||
}
|
||||
}
|
||||
result
|
||||
})
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub fn handle_sync_api_request<Env: RpcEnvironment, S: 'static + BuildHasher + Send>(
|
||||
pub async fn handle_sync_api_request<Env: RpcEnvironment, S: 'static + BuildHasher + Send>(
|
||||
mut rpcenv: Env,
|
||||
info: &'static ApiMethod,
|
||||
formatter: &'static OutputFormatter,
|
||||
parts: Parts,
|
||||
req_body: Body,
|
||||
uri_param: HashMap<String, String, S>,
|
||||
) -> ApiFuture
|
||||
{
|
||||
) -> Result<Response<Body>, Error> {
|
||||
|
||||
let handler = match info.handler {
|
||||
ApiHandler::Async(_) => {
|
||||
panic!("fixme");
|
||||
}
|
||||
ApiHandler::Async(_) => bail!("handle_sync_api_request: internal error (called with Async handler)"),
|
||||
ApiHandler::Sync(handler) => handler,
|
||||
};
|
||||
|
||||
let params = get_request_parameters_async(info, parts, req_body, uri_param);
|
||||
|
||||
let params = get_request_parameters_async(info, parts, req_body, uri_param).await?;
|
||||
|
||||
let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000);
|
||||
|
||||
let resp = Pin::from(params)
|
||||
.and_then(move |params| {
|
||||
let mut delay = false;
|
||||
|
||||
let resp = match (handler)(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;
|
||||
}
|
||||
}
|
||||
(formatter.format_error)(err)
|
||||
let mut delay = false;
|
||||
|
||||
let resp = match (handler)(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 info.reload_timezone {
|
||||
unsafe { tzset() };
|
||||
}
|
||||
(formatter.format_error)(err)
|
||||
}
|
||||
};
|
||||
|
||||
if delay {
|
||||
Either::Left(delayed_response(resp, delay_unauth_time))
|
||||
} else {
|
||||
Either::Right(future::ok(resp))
|
||||
}
|
||||
})
|
||||
.or_else(move |err| {
|
||||
future::ok((formatter.format_error)(err))
|
||||
});
|
||||
if info.reload_timezone { unsafe { tzset(); } }
|
||||
|
||||
Box::new(resp)
|
||||
if delay {
|
||||
tokio::timer::delay(delay_unauth_time).await;
|
||||
}
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub fn handle_async_api_request<Env: RpcEnvironment>(
|
||||
pub async fn handle_async_api_request<Env: RpcEnvironment>(
|
||||
rpcenv: Env,
|
||||
info: &'static ApiMethod,
|
||||
formatter: &'static OutputFormatter,
|
||||
parts: Parts,
|
||||
req_body: Body,
|
||||
uri_param: HashMap<String, String>,
|
||||
) -> ApiFuture
|
||||
{
|
||||
) -> Result<Response<Body>, Error> {
|
||||
|
||||
let handler = match info.handler {
|
||||
ApiHandler::Sync(_) => {
|
||||
panic!("fixme");
|
||||
}
|
||||
ApiHandler::Sync(_) => bail!("handle_async_api_request: internal error (called with Sync handler)"),
|
||||
ApiHandler::Async(handler) => handler,
|
||||
};
|
||||
|
||||
|
||||
// fixme: convert parameters to Json
|
||||
let mut param_list: Vec<(String, String)> = vec![];
|
||||
|
||||
@ -350,18 +327,14 @@ pub fn handle_async_api_request<Env: RpcEnvironment>(
|
||||
let params = match parse_parameter_strings(¶m_list, &info.parameters, true) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
let resp = (formatter.format_error)(Error::from(err));
|
||||
return Box::new(future::ok(resp));
|
||||
return Ok((formatter.format_error)(Error::from(err)));
|
||||
}
|
||||
};
|
||||
|
||||
match (handler)(parts, req_body, params, info, Box::new(rpcenv)) {
|
||||
Ok(future) => future,
|
||||
Err(err) => {
|
||||
let resp = (formatter.format_error)(err);
|
||||
Box::new(future::ok(resp))
|
||||
}
|
||||
}
|
||||
|
||||
let resp = (handler)(parts, req_body, params, info, Box::new(rpcenv)).await?;
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
fn get_index(username: Option<String>, token: Option<String>) -> Response<Body> {
|
||||
@ -491,9 +464,9 @@ async fn chuncked_static_file_download(filename: PathBuf) -> Result<Response<Bod
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_static_file_download(filename: PathBuf) -> ApiFuture {
|
||||
async fn handle_static_file_download(filename: PathBuf) -> Result<Response<Body>, Error> {
|
||||
|
||||
let response = tokio::fs::metadata(filename.clone())
|
||||
tokio::fs::metadata(filename.clone())
|
||||
.map_err(|err| http_err!(BAD_REQUEST, format!("File access problems: {}", err)))
|
||||
.and_then(|metadata| async move {
|
||||
if metadata.len() < 1024*32 {
|
||||
@ -501,9 +474,8 @@ fn handle_static_file_download(filename: PathBuf) -> ApiFuture {
|
||||
} else {
|
||||
chuncked_static_file_download(filename).await
|
||||
}
|
||||
});
|
||||
|
||||
Box::new(response)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn extract_auth_data(headers: &http::HeaderMap) -> (Option<String>, Option<String>) {
|
||||
@ -548,24 +520,12 @@ fn check_auth(method: &hyper::Method, ticket: &Option<String>, token: &Option<St
|
||||
Ok(username)
|
||||
}
|
||||
|
||||
async fn delayed_response(
|
||||
resp: Response<Body>,
|
||||
delay_unauth_time: std::time::Instant,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
tokio::timer::delay(delay_unauth_time).await;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> ApiFuture {
|
||||
pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<Response<Body>, Error> {
|
||||
|
||||
let (parts, body) = req.into_parts();
|
||||
|
||||
let method = parts.method.clone();
|
||||
|
||||
let (path, components) = match tools::normalize_uri_path(parts.uri.path()) {
|
||||
Ok((p,c)) => (p, c),
|
||||
Err(err) => return Box::new(future::err(http_err!(BAD_REQUEST, err.to_string()))),
|
||||
};
|
||||
let (path, components) = tools::normalize_uri_path(parts.uri.path())?;
|
||||
|
||||
let comp_len = components.len();
|
||||
|
||||
@ -580,13 +540,13 @@ pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> ApiFuture {
|
||||
if comp_len >= 1 && components[0] == "api2" {
|
||||
|
||||
if comp_len >= 2 {
|
||||
|
||||
let format = components[1];
|
||||
|
||||
let formatter = match format {
|
||||
"json" => &JSON_FORMATTER,
|
||||
"extjs" => &EXTJS_FORMATTER,
|
||||
_ => {
|
||||
return Box::new(future::err(http_err!(BAD_REQUEST, format!("Unsupported output format '{}'.", format))));
|
||||
}
|
||||
_ => bail!("Unsupported output format '{}'.", format),
|
||||
};
|
||||
|
||||
let mut uri_param = HashMap::new();
|
||||
@ -605,9 +565,8 @@ pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> ApiFuture {
|
||||
Err(err) => {
|
||||
// always delay unauthorized calls by 3 seconds (from start of request)
|
||||
let err = http_err!(UNAUTHORIZED, format!("permission check failed - {}", err));
|
||||
return Box::new(
|
||||
delayed_response((formatter.format_error)(err), delay_unauth_time)
|
||||
);
|
||||
tokio::timer::delay(delay_unauth_time).await;
|
||||
return Ok((formatter.format_error)(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -615,29 +574,29 @@ pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> ApiFuture {
|
||||
match api.find_method(&components[2..], method, &mut uri_param) {
|
||||
None => {
|
||||
let err = http_err!(NOT_FOUND, "Path not found.".to_string());
|
||||
return Box::new(future::ok((formatter.format_error)(err)));
|
||||
return Ok((formatter.format_error)(err));
|
||||
}
|
||||
Some(api_method) => {
|
||||
if api_method.protected && env_type == RpcEnvironmentType::PUBLIC {
|
||||
return proxy_protected_request(api_method, parts, body);
|
||||
return proxy_protected_request(api_method, parts, body).await;
|
||||
} else {
|
||||
match api_method.handler {
|
||||
ApiHandler::Sync(_) => {
|
||||
return handle_sync_api_request(rpcenv, api_method, formatter, parts, body, uri_param);
|
||||
return handle_sync_api_request(rpcenv, api_method, formatter, parts, body, uri_param).await;
|
||||
}
|
||||
ApiHandler::Async(_) => {
|
||||
return handle_async_api_request(rpcenv, api_method, formatter, parts, body, uri_param);
|
||||
return handle_async_api_request(rpcenv, api_method, formatter, parts, body, uri_param).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else {
|
||||
// not Auth required for accessing files!
|
||||
|
||||
if method != hyper::Method::GET {
|
||||
return Box::new(future::err(http_err!(BAD_REQUEST, "Unsupported method".to_string())));
|
||||
bail!("Unsupported HTTP method {}", method);
|
||||
}
|
||||
|
||||
if comp_len == 0 {
|
||||
@ -646,20 +605,21 @@ pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> ApiFuture {
|
||||
match check_auth(&method, &ticket, &token) {
|
||||
Ok(username) => {
|
||||
let new_token = assemble_csrf_prevention_token(csrf_secret(), &username);
|
||||
return Box::new(future::ok(get_index(Some(username), Some(new_token))));
|
||||
return Ok(get_index(Some(username), Some(new_token)));
|
||||
}
|
||||
_ => {
|
||||
return Box::new(delayed_response(get_index(None, None), delay_unauth_time));
|
||||
tokio::timer::delay(delay_unauth_time).await;
|
||||
return Ok(get_index(None, None));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Box::new(future::ok(get_index(None, None)));
|
||||
return Ok(get_index(None, None));
|
||||
}
|
||||
} else {
|
||||
let filename = api.find_alias(&components);
|
||||
return handle_static_file_download(filename);
|
||||
return handle_static_file_download(filename).await;
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(future::err(http_err!(NOT_FOUND, "Path not found.".to_string())))
|
||||
Err(http_err!(NOT_FOUND, "Path not found.".to_string()))
|
||||
}
|
||||
|
Reference in New Issue
Block a user