use actix_web::{App, HttpResponse, HttpServer, web, http, FromRequest, HttpRequest, HttpMessage}; use crate::controllers::routes::{get_routes, Route, RequestResult}; use crate::data::config::Config; use crate::data::http_error::HttpError; use crate::controllers::routes::Method::{GET, POST}; use crate::data::http_request_handler::{HttpRequestHandler, RequestValue}; use actix_web::dev::{PayloadStream, Payload, Decompress}; use actix_web::web::{BytesMut}; use futures::future::{ok, LocalBoxFuture, err}; use futures::{FutureExt, StreamExt, TryFutureExt}; use actix_web::error::{PayloadError, ErrorBadRequest}; use std::error::Error; use encoding_rs::UTF_8; use std::collections::HashMap; /// Main server functions /// /// @author Pierre Hubert /// Custom request value struct CustomRequest { req: web::HttpRequest, body: HashMap, } /// Process in our way incoming requests impl FromRequest for CustomRequest { type Error = actix_web::Error; type Future = LocalBoxFuture<'static, Result>; type Config = (); fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req = req.clone(); let payload = payload.take(); async move { let mut body_args = HashMap::new(); // Process "application/x-www-form-urlencoded" requests if req.content_type().eq("application/x-www-form-urlencoded") { // Maximum size of the request let limit = 16384; // Ready body let mut body = BytesMut::with_capacity(8192); let mut stream = Decompress::from_headers(payload, req.headers()); while let Some(item) = stream.next().await { let chunk = item?; if body.len() + chunk.len() > limit { return Err(actix_web::error::ErrorBadRequest("Overflow - too long")); } else { body.extend_from_slice(&chunk); } } let body = body.freeze(); // Decode body as a string let encoding = req.encoding()?; let body_str = if encoding == UTF_8 { String::from_utf8_lossy(body.as_ref()).to_string() } else { encoding .decode_without_bom_handling_and_without_replacement(&body) .map(|s| s.into_owned()) .ok_or_else(|| ErrorBadRequest("Can not decode body"))? }; // Parse body arguments (following the pattern key1=value1&key2=value2) if body_str.len() > 0 { for v in body_str.split("&") { if v.len() == 0 { continue; } let args: Vec<&str> = v.split("=").collect(); if args.len() != 2 { return Err(actix_web::error::ErrorBadRequest(format!("{} is invalid!", args[0]))); } // Add the value to the body body_args.insert( args[0].to_string(), RequestValue::string(args[1].to_string()) ); } } } Ok(CustomRequest { req: req.clone(), body: body_args, }) }.boxed_local() } } /// Process a "simple request" aka not a WebSocket request fn process_simple_route(route: &Route, req: &mut HttpRequestHandler) -> RequestResult { // Validate client token req.check_client_token()?; (route.func)(req) } /// Process an incoming request async fn process_request(custom_req: CustomRequest) -> HttpResponse { let req = &custom_req.req; let routes = get_routes(); // We search the appropriate route for the request let mut route: Option<&Route> = None; for el in &routes { // Check verb if !(req.method() == http::Method::GET && el.method == GET) && !(req.method() == http::Method::POST && el.method == POST) { continue; } // Check path if !el.uri.eq(req.uri()) { continue; } route = Some(el); break; } // Check if a route was found if let None = route { return HttpResponse::NotFound().json(HttpError::not_found("Method not found!")); } let route = route.unwrap(); // Execute the request let mut request = HttpRequestHandler::new(custom_req.req, custom_req.body); match process_simple_route(route, &mut request) { // Set default error response if required Err(e) => { if !request.has_response() { request.internal_error(e).unwrap_err(); } } // Set default success response if required Ok(_) => { if !request.has_response() { request.success("Success").unwrap() } } } request.response() } /// Given the configuration, start the server pub async fn start_server(conf: &Config) -> std::io::Result<()> { let addr = conf.server_listen_address(); println!("Start to listen on http://{}/", addr); HttpServer::new(|| { App::new() .route("**", web::get().to(process_request)) .route("**", web::post().to(process_request)) }).bind(&addr)?.run().await }