From d3a69f0c58f2df583adea59a79969a8c23a03a2a Mon Sep 17 00:00:00 2001 From: Firstyear Date: Thu, 31 Jan 2019 19:12:27 +1300 Subject: [PATCH] Add nonfunctional complex example to diesel (#63) --- diesel/Cargo.toml | 1 + diesel/src/main.rs | 95 ++++++++++++++++++++++++++++++++++++++++++--- diesel/test.db | Bin 20480 -> 0 bytes 3 files changed, 91 insertions(+), 5 deletions(-) delete mode 100644 diesel/test.db diff --git a/diesel/Cargo.toml b/diesel/Cargo.toml index 69e3db66..a2f309b8 100644 --- a/diesel/Cargo.toml +++ b/diesel/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Nikolay Kim "] workspace = "../" [dependencies] +bytes = "0.4" env_logger = "0.5" actix = "0.7" diff --git a/diesel/src/main.rs b/diesel/src/main.rs index a94f65ab..d30519d6 100644 --- a/diesel/src/main.rs +++ b/diesel/src/main.rs @@ -16,16 +16,20 @@ extern crate env_logger; extern crate futures; extern crate r2d2; extern crate uuid; +extern crate bytes; +// extern crate json; + +use bytes::BytesMut; use actix::prelude::*; use actix_web::{ - http, middleware, server, App, AsyncResponder, FutureResponse, HttpResponse, Path, - State, + http, middleware, server, App, AsyncResponder, FutureResponse, HttpResponse, Path, Error, HttpRequest, + State, HttpMessage, error, Json }; use diesel::prelude::*; use diesel::r2d2::ConnectionManager; -use futures::Future; +use futures::{future, Future, Stream}; mod db; mod models; @@ -39,7 +43,7 @@ struct AppState { } /// Async request handler -fn index( +fn add( (name, state): (Path, State), ) -> FutureResponse { // send async `CreateUser` message to a `DbExecutor` @@ -56,6 +60,73 @@ fn index( .responder() } +#[derive(Debug, Serialize, Deserialize)] +struct MyUser { + name: String +} + +const MAX_SIZE: usize = 262_144; // max payload size is 256k + +/// This handler manually load request payload and parse json object +fn index_add((req, state): (HttpRequest, State)) -> impl Future { + // HttpRequest::payload() is stream of Bytes objects + req.payload() + // `Future::from_err` acts like `?` in that it coerces the error type from + // the future into the final error type + .from_err() + + // `fold` will asynchronously read each chunk of the request body and + // call supplied closure, then it resolves to result of closure + .fold(BytesMut::new(), move |mut body, chunk| { + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + Err(error::ErrorBadRequest("overflow")) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + // `Future::and_then` can be used to merge an asynchronous workflow with a + // synchronous workflow + // + // Douman NOTE: + // The return value in this closure helps, to clarify result for compiler + // as otheriwse it cannot understand it + .and_then(move |body| -> Box> { + // body is loaded, now we can deserialize serde-json + let r_obj = serde_json::from_slice::(&body); + + // Send to the db for create + match r_obj { + Ok(obj) => { + let res = state.db.send(CreateUser { name: obj.name, }) + .from_err() + .and_then(|res| match res { + Ok(user) => Ok(HttpResponse::Ok().json(user)), + Err(_) => Ok(HttpResponse::InternalServerError().into()), + }); + + Box::new(res) + } + Err(_) => Box::new(future::err(error::ErrorBadRequest("Json Decode Failed"))) + } + }) +} + +fn add2((item, state): (Json, State)) -> impl Future { + state.db + .send(CreateUser { + // into_inner to move into the reference, then accessing name to + // move the name out. + name: item.into_inner().name, + }) + .from_err() + .and_then(|res| match res { + Ok(user) => Ok(HttpResponse::Ok().json(user)), + Err(_) => Ok(HttpResponse::InternalServerError().into()), + }) +} + fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); @@ -74,7 +145,21 @@ fn main() { App::with_state(AppState{db: addr.clone()}) // enable logger .middleware(middleware::Logger::default()) - .resource("/{name}", |r| r.method(http::Method::GET).with(index)) + // This can be called with: + // curl -S --header "Content-Type: application/json" --request POST --data '{"name":"xyz"}' http://127.0.0.1:8080/add + // Use of the extractors makes some post conditions simpler such + // as size limit protections and built in json validation. + .resource("/add2", |r| { + r.method(http::Method::POST) + .with_async_config(add2, |(json_cfg, )| { + json_cfg.0.limit(4096); // <- limit size of the payload + }) + }) + // Manual parsing would allow custom error construction, use of + // other parsers *beside* json (for example CBOR, protobuf, xml), and allows + // an application to standardise on a single parser implementation. + .resource("/add", |r| r.method(http::Method::POST).with_async(index_add)) + .resource("/add/{name}", |r| r.method(http::Method::GET).with(add)) }).bind("127.0.0.1:8080") .unwrap() .start(); diff --git a/diesel/test.db b/diesel/test.db deleted file mode 100644 index 65e590a6e5f8f16622eee5da4c64c5a18f7b3423..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeI%!A{gb7zgm_b`c?AiwDyfFLN+)H&E=f-EB)^Vuh;21+layoSM>3VH0*YZVT~> z2VR7`K8zRjAv~LSakfDMAx$`NQ1hR3I@8XV>3qNR(&^6I{-ESEA5Vr!NlmgyB#Atu zln^p=UPV)thB!CR`_o3c)UWH#kd?(>3(8N@Y^{^lH|4Cg-uhG*jQbFP00bZa0SG_< z0uX=z1R(Ht3mnL^s;WvSPs(KPkRKI%QdFnrTHt%3Pebo{->20r+McI$kkNNuu=dIe z=+>K%Zbkh*-3~T3y$S4`|YeDm!PVv8v#RGwA0Je!isNj+3w{_E=>Z=m@o=y|Ny@=^RMd|&uB^X4j<%0Q%3`iR zD{go7&gG0Q(p;V#jbafOZfyEHp|`nxF+$h<7hcp4=~@&7{#F=YgmiWqchr5aF6sJZ z#jJiz7H`zu>07lRs-%1;;y{4_1Rwwb2tWV=5P$##AOHafK;WMfcqGXk)6ki%GsCK? zF}>25p)r^0`l@cM>TF)*B`H6MI8Yz}0SG_<0uX=z1Rwwb2tWV=5cn?y?#Z3Gt6Kuo z|NpXbOq4ImnP^ZT009U<00Izz00bZa0SG_<0uZ=0fhAdv?z^eu8cx^J^4xVaE8&*r zxQ?l%Y2MwlvR(vYKvSvSp`ZpJj1Cx&LF%+lP%N;K1AJ>E4^vzO}0Xj~rf z$&w@{=PcuyZR?utnzm+fwx>C)=V-ZYr)J7b*UGEOr~m(D<&-F=%4g;4ttE(wAOHaf qKmY;|fB*y_009U<00I!WCeW56=_cC&@-*-!TLF#7ax{07J%HZ}Gw{Cv