mirror of
				https://gitlab.com/comunic/comunicapiv3
				synced 2025-11-04 01:24:04 +00:00 
			
		
		
		
	Start to read POST request arguments
This commit is contained in:
		
							
								
								
									
										3
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -503,7 +503,10 @@ version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "actix-rt",
 | 
			
		||||
 "actix-web",
 | 
			
		||||
 "encoding_rs",
 | 
			
		||||
 "futures",
 | 
			
		||||
 "mysql",
 | 
			
		||||
 "percent-encoding",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "yaml-rust",
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -12,3 +12,6 @@ mysql = "18.2.0"
 | 
			
		||||
actix-web = "2.0.0"
 | 
			
		||||
actix-rt = "1.1.1"
 | 
			
		||||
serde = "1.0.110"
 | 
			
		||||
futures = "0.3.5"
 | 
			
		||||
encoding_rs = "0.8.23"
 | 
			
		||||
percent-encoding = "2.1.0"
 | 
			
		||||
@@ -1,17 +1,119 @@
 | 
			
		||||
use actix_web::{App, HttpResponse, HttpServer, web, http};
 | 
			
		||||
use actix_web::{App, HttpResponse, HttpServer, web, http, FromRequest, HttpRequest, HttpMessage};
 | 
			
		||||
 | 
			
		||||
use crate::controllers::routes::{get_routes, Route};
 | 
			
		||||
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;
 | 
			
		||||
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<String, RequestValue>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Process in our way incoming requests
 | 
			
		||||
impl FromRequest for CustomRequest {
 | 
			
		||||
    type Error = actix_web::Error;
 | 
			
		||||
    type Future = LocalBoxFuture<'static, Result<CustomRequest, actix_web::Error>>;
 | 
			
		||||
    type Config = ();
 | 
			
		||||
 | 
			
		||||
    fn from_request(req: &HttpRequest, payload: &mut Payload<PayloadStream>) -> 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(req: web::HttpRequest) -> HttpResponse {
 | 
			
		||||
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
 | 
			
		||||
@@ -40,9 +142,9 @@ async fn process_request(req: web::HttpRequest) -> HttpResponse {
 | 
			
		||||
    let route = route.unwrap();
 | 
			
		||||
 | 
			
		||||
    // Execute the request
 | 
			
		||||
    let mut request = HttpRequestHandler::new(req);
 | 
			
		||||
    let mut request = HttpRequestHandler::new(custom_req.req, custom_req.body);
 | 
			
		||||
 | 
			
		||||
    match (route.func)(&mut request) {
 | 
			
		||||
    match process_simple_route(route, &mut request) {
 | 
			
		||||
 | 
			
		||||
        // Set default error response if required
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
@@ -57,10 +159,6 @@ async fn process_request(req: web::HttpRequest) -> HttpResponse {
 | 
			
		||||
                request.success("Success").unwrap()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // I use this to be quiet with IntelliJ
 | 
			
		||||
        #[allow(unreachable_patterns)]
 | 
			
		||||
        _ => {println!("Unexpected case (server.rs)!")}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    request.response()
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,10 @@ use std::error;
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Simple result type
 | 
			
		||||
pub type ResultExecError<E> = Result<E, ExecError>;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct ExecError(pub String);
 | 
			
		||||
 | 
			
		||||
@@ -17,6 +21,10 @@ impl ExecError {
 | 
			
		||||
    pub fn boxed_new(msg: &str) -> Box<ExecError> {
 | 
			
		||||
        Box::new(ExecError(msg.to_string()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn boxed_string(msg: String) -> Box<ExecError> {
 | 
			
		||||
        Box::new(ExecError(msg))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for ExecError {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,27 @@ use crate::data::http_error::HttpError;
 | 
			
		||||
use std::convert::TryFrom;
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use crate::data::error::{ResultExecError, ExecError};
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
/// Http request handler
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
/// Single request body  value
 | 
			
		||||
pub struct RequestValue {
 | 
			
		||||
    pub string: Option<String>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RequestValue {
 | 
			
		||||
    /// Build a string value
 | 
			
		||||
    pub fn string(s: String) -> RequestValue {
 | 
			
		||||
        RequestValue {
 | 
			
		||||
            string: Some(s)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize)]
 | 
			
		||||
struct SuccessMessage {
 | 
			
		||||
    success: String
 | 
			
		||||
@@ -16,16 +32,17 @@ struct SuccessMessage {
 | 
			
		||||
 | 
			
		||||
pub struct HttpRequestHandler {
 | 
			
		||||
    request: web::HttpRequest,
 | 
			
		||||
    response: Option<web::HttpResponse>
 | 
			
		||||
    body: HashMap<String, RequestValue>,
 | 
			
		||||
    response: Option<web::HttpResponse>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HttpRequestHandler {
 | 
			
		||||
 | 
			
		||||
    /// Construct a new request handler
 | 
			
		||||
    pub fn new(req: HttpRequest) -> HttpRequestHandler {
 | 
			
		||||
    pub fn new(req: HttpRequest, body: HashMap<String, RequestValue>) -> HttpRequestHandler {
 | 
			
		||||
        HttpRequestHandler {
 | 
			
		||||
            request: req,
 | 
			
		||||
            response: None
 | 
			
		||||
            body,
 | 
			
		||||
            response: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -53,4 +70,34 @@ impl HttpRequestHandler {
 | 
			
		||||
            HttpError::internal_error("Internal server error.")));
 | 
			
		||||
        Err(Box::try_from(actix_web::error::ErrorInternalServerError(error)).unwrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// Check login tokens
 | 
			
		||||
    pub fn check_client_token(&mut self) -> ResultExecError<()> {
 | 
			
		||||
 | 
			
		||||
        println!("me = {}", self.post_string("me")?);
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Check if a POST parameter was present in the request or not
 | 
			
		||||
    pub fn has_post_parameter(&self, name: &str) -> bool {
 | 
			
		||||
        self.body.contains_key(name)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get a post parameter
 | 
			
		||||
    pub fn post_parameter(&self, name: &str) -> Result<&RequestValue, ExecError> {
 | 
			
		||||
        self.body.get(name)
 | 
			
		||||
            .ok_or(ExecError(format!("POST parameter {} not found in request!", name)))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get a post string
 | 
			
		||||
    pub fn post_string(&self, name: &str) -> Result<String, ExecError> {
 | 
			
		||||
        let param = self.post_parameter(name)?;
 | 
			
		||||
 | 
			
		||||
        match ¶m.string {
 | 
			
		||||
            None => Err(ExecError(format!("{} is not a string!", name))),
 | 
			
		||||
            Some(s) => Ok(s.to_string())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,13 +1,7 @@
 | 
			
		||||
use comunic_server::controllers::server;
 | 
			
		||||
use comunic_server::data::config::{conf, Config};
 | 
			
		||||
use comunic_server::helpers::database;
 | 
			
		||||
use comunic_server::controllers::server;
 | 
			
		||||
use comunic_server::helpers::database::QueryInfo;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct User {
 | 
			
		||||
    id : i64,
 | 
			
		||||
    name: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[actix_rt::main]
 | 
			
		||||
async fn main() -> std::io::Result<()> {
 | 
			
		||||
@@ -18,16 +12,6 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
    // Connect to the database
 | 
			
		||||
    database::connect(&conf().database).expect("Could not connect to database!");
 | 
			
		||||
 | 
			
		||||
    let mut query = QueryInfo::new("user");
 | 
			
		||||
    query.cond("age", "190");
 | 
			
		||||
    //query.cond("id", "1");
 | 
			
		||||
    let res = database::query_row(query, |res| Ok(User {
 | 
			
		||||
        id: res.get_int64("id")?,
 | 
			
		||||
        name: res.get_str("name")?,
 | 
			
		||||
    })).unwrap();
 | 
			
		||||
 | 
			
		||||
    println!("{:#?}", res);
 | 
			
		||||
 | 
			
		||||
    // Start the server
 | 
			
		||||
    server::start_server(conf()).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user