1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2024-11-22 13:29:21 +00:00

Start to read POST request arguments

This commit is contained in:
Pierre HUBERT 2020-05-23 09:37:21 +02:00
parent fcc38afe42
commit 7c18f19674
6 changed files with 175 additions and 32 deletions

3
Cargo.lock generated
View File

@ -503,7 +503,10 @@ version = "0.1.0"
dependencies = [
"actix-rt",
"actix-web",
"encoding_rs",
"futures",
"mysql",
"percent-encoding",
"serde",
"yaml-rust",
]

View File

@ -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"

View File

@ -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()

View File

@ -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 {

View File

@ -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 &param.string {
None => Err(ExecError(format!("{} is not a string!", name))),
Some(s) => Ok(s.to_string())
}
}
}

View File

@ -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
}