From f922e8fb964150f594a3c6891d0c68ddc3180bdd Mon Sep 17 00:00:00 2001 From: Cameron Dershem Date: Sun, 16 Jun 2019 23:17:17 -0400 Subject: [PATCH] First pass at Extractors. --- content/docs/extractors.md | 194 ++-------------------- examples/Cargo.toml | 1 + examples/extractors/Cargo.toml | 11 ++ examples/extractors/src/custom_handler.rs | 18 ++ examples/extractors/src/form.rs | 20 +++ examples/extractors/src/json_one.rs | 18 ++ examples/extractors/src/json_two.rs | 34 ++++ examples/extractors/src/main.rs | 48 ++++++ examples/extractors/src/multiple.rs | 20 +++ examples/extractors/src/path_one.rs | 17 ++ examples/extractors/src/path_two.rs | 22 +++ examples/extractors/src/query.rs | 18 ++ 12 files changed, 238 insertions(+), 183 deletions(-) create mode 100644 examples/extractors/Cargo.toml create mode 100644 examples/extractors/src/custom_handler.rs create mode 100644 examples/extractors/src/form.rs create mode 100644 examples/extractors/src/json_one.rs create mode 100644 examples/extractors/src/json_two.rs create mode 100644 examples/extractors/src/main.rs create mode 100644 examples/extractors/src/multiple.rs create mode 100644 examples/extractors/src/path_one.rs create mode 100644 examples/extractors/src/path_two.rs create mode 100644 examples/extractors/src/query.rs diff --git a/content/docs/extractors.md b/content/docs/extractors.md index 684d84d..f6668f6 100644 --- a/content/docs/extractors.md +++ b/content/docs/extractors.md @@ -19,25 +19,8 @@ or a custom Handler type. An Extractor can be passed to a handler function as a function parameter *or* accessed within the function by calling the ExtractorType::<...>::extract(req) function. -```rust -// Option 1: passed as a parameter to a handler function -fn index((params, info): (Path<(String, String,)>, Json)) -> HttpResponse { - ... -} - - -// Option 2: accessed by calling extract() on the Extractor - -use actix_web::FromRequest; - -fn index(req: &HttpRequest) -> HttpResponse { - let params = Path::<(String, String)>::extract(req); - let info = Json::::extract(req); - - ... -} -``` +{{< include-example example="extractors" file="main.rs" section="main" >}} ## Within Custom Handler Types @@ -47,25 +30,7 @@ calling the ExtractorType::<...>::extract(&req) function. An Extractor Handler type must follow the ``handle`` function signature specified by the Handler trait it implements. -```rust - -struct MyHandler(String); - -impl Handler for MyHandler { - type Result = HttpResponse; - - /// Handle request - fn handle(&self, req: &HttpRequest) -> Self::Result { - let params = Path::<(String, String)>::extract(req); - let info = Json::::extract(req); - - ... - - HttpResponse::Ok().into() - } -} - -``` +{{< include-example example="extractors" file="custom_handler.rs" section="custom-handler" >}} # Path @@ -78,51 +43,13 @@ two segments could be deserialized, `userid` and `friend`. These segments could be extracted into a `tuple`, i.e. `Path<(u32, String)>` or any structure that implements the `Deserialize` trait from the *serde* crate. -```rust -use actix_web::{App, Path, Result, http}; - -/// extract path info from "/users/{userid}/{friend}" url -/// {userid} - - deserializes to a u32 -/// {friend} - deserializes to a String -fn index(info: Path<(u32, String)>) -> Result { - Ok(format!("Welcome {}! {}", info.1, info.0)) -} - -fn main() { - let app = App::new().resource( - "/users/{userid}/{friend}", // <- define path parameters - |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -} -``` - -Remember! A handler function that uses extractors has to be registered using the -[*Route::with()*](../../actix-web/actix_web/dev/struct.Route.html#method.with) method. +{{< include-example example="extractors" file="path_one.rs" section="path-one" >}} It is also possible to extract path information to a specific type that implements the `Deserialize` trait from *serde*. Here is an equivalent example that uses *serde* instead of a *tuple* type. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Path, Result, http}; - -#[derive(Deserialize)] -struct Info { - userid: u32, - friend: String, -} - -/// extract path info using serde -fn index(info: Path) -> Result { - Ok(format!("Welcome {}!", info.friend)) -} - -fn main() { - let app = App::new().resource( - "/users/{userid}/{friend}", // <- define path parameters - |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -} -``` +{{< include-example example="extractors" file="path_two.rs" section="path-two" >}} # Query @@ -130,26 +57,7 @@ Same can be done with the request's query. The [*Query*](../../actix-web/actix_web/struct.Query.html) type provides extraction functionality. Underneath it uses *serde_urlencoded* crate. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Query, http}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -// this handler get called only if the request's query contains `username` field -fn index(info: Query) -> String { - format!("Welcome {}!", info.username) -} - -fn main() { - let app = App::new().resource( - "/index.html", - |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -} -``` +{{< include-example example="extractors" file="query.rs" section="query" >}} # Json @@ -157,26 +65,7 @@ fn main() { a request body into a struct. To extract typed information from a request's body, the type `T` must implement the `Deserialize` trait from *serde*. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Json, Result, http}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -/// deserialize `Info` from request's body -fn index(info: Json) -> Result { - Ok(format!("Welcome {}!", info.username)) -} - -fn main() { - let app = App::new().resource( - "/index.html", - |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor -} -``` +{{< include-example example="extractors" file="json_one.rs" section="json-one" >}} Some extractors provide a way to configure the extraction process. Json extractor [*JsonConfig*](../../actix-web/actix_web/dev/struct.JsonConfig.html) type for configuration. @@ -186,34 +75,7 @@ payload as well as a custom error handler function. The following example limits the size of the payload to 4kb and uses a custom error handler. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Json, HttpResponse, Result, http, error}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -/// deserialize `Info` from request's body, max payload size is 4kb -fn index(info: Json) -> Result { - Ok(format!("Welcome {}!", info.username)) -} - -fn main() { - let app = App::new().resource( - "/index.html", |r| { - r.method(http::Method::POST) - .with_config(index, |cfg| { - cfg.limit(4096) // <- change json extractor configuration - cfg.error_handler(|err, req| { // <- create custom error response - error::InternalError::from_response( - err, HttpResponse::Conflict().finish()).into() - }) - }); - }); -} -``` +{{< include-example example="extractors" file="json_two.rs" section="json-two" >}} # Form @@ -224,23 +86,7 @@ the `Deserialize` trait from the *serde* crate. [*FormConfig*](../../actix-web/actix_web/dev/struct.FormConfig.html) allows configuring the extraction process. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Form, Result}; - -#[derive(Deserialize)] -struct FormData { - username: String, -} - -/// extract form data using serde -/// this handler gets called only if the content type is *x-www-form-urlencoded* -/// and the content of the request could be deserialized to a `FormData` struct -fn index(form: Form) -> Result { - Ok(format!("Welcome {}!", form.username)) -} -# fn main() {} -``` +{{< include-example example="extractors" file="form.rs" section="form" >}} # Multiple extractors @@ -249,32 +95,14 @@ whose elements implement `FromRequest`. For example we can use a path extractor and a query extractor at the same time. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Query, Path, http}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -fn index((path, query): (Path<(u32, String)>, Query)) -> String { - format!("Welcome {}!", query.username) -} - -fn main() { - let app = App::new().resource( - "/users/{userid}/{friend}", // <- define path parameters - |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -} -``` +{{< include-example example="extractors" file="multiple.rs" section="multi" >}} # Other Actix also provides several other extractors: -* [*State*](../../actix-web/actix_web/struct.State.html) - If you need - access to an application state. This is similar to a `HttpRequest::state()`. +* [*Data*](../../actix-web/actix_web/web/struct.Data.html) - If you need + access to an application state. This is similar to a `HttpRequest::app_data()`. * *HttpRequest* - *HttpRequest* itself is an extractor which returns self, in case you need access to the request. * *String* - You can convert a request's payload to a *String*. diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 328991a..711aaf6 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -15,4 +15,5 @@ members = [ exclude = [ "request-handlers", "async-handlers", + "extractors", ] diff --git a/examples/extractors/Cargo.toml b/examples/extractors/Cargo.toml new file mode 100644 index 0000000..f135edb --- /dev/null +++ b/examples/extractors/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "extractors" +version = "0.1.0" +authors = ["Cameron Dershem "] +edition = "2018" + +[dependencies] +actix-web = "1.0" +serde = "1.0" +futures = "0.1" +serde_json = "1.0" diff --git a/examples/extractors/src/custom_handler.rs b/examples/extractors/src/custom_handler.rs new file mode 100644 index 0000000..93596d9 --- /dev/null +++ b/examples/extractors/src/custom_handler.rs @@ -0,0 +1,18 @@ +use actix_web::{web, HttpRequest, HttpResponse}; + +struct MyHandler {} +struct MyInfo {} + +// +impl Handler for MyHandler { + type Result = HttpResponse; + + /// Handle request + fn handle(&self, req: &HttpRequest) -> Self::Result { + let params = web::Path::<(String, String)>::extract(req); + let info = web::Json::::extract(req); + + HttpResponse::Ok().into() + } +} +// diff --git a/examples/extractors/src/form.rs b/examples/extractors/src/form.rs new file mode 100644 index 0000000..1af09d4 --- /dev/null +++ b/examples/extractors/src/form.rs @@ -0,0 +1,20 @@ +//
+use actix_web::{web, App, Result}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct FormData { + username: String, +} + +/// extract form data using serde +/// this handler gets called only if the content type is *x-www-form-urlencoded* +/// and the content of the request could be deserialized to a `FormData` struct +fn index(form: web::Form) -> Result { + Ok(format!("Welcome {}!", form.username)) +} +// + +pub fn main() { + App::new().route("", web::post().to(index)); +} diff --git a/examples/extractors/src/json_one.rs b/examples/extractors/src/json_one.rs new file mode 100644 index 0000000..d54459b --- /dev/null +++ b/examples/extractors/src/json_one.rs @@ -0,0 +1,18 @@ +// +use actix_web::{web, App, Result}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + username: String, +} + +/// deserialize `Info` from request's body +fn index(info: web::Json) -> Result { + Ok(format!("Welcome {}!", info.username)) +} + +pub fn main() { + App::new().route("/", web::get().to(index)); +} +// diff --git a/examples/extractors/src/json_two.rs b/examples/extractors/src/json_two.rs new file mode 100644 index 0000000..c6464ac --- /dev/null +++ b/examples/extractors/src/json_two.rs @@ -0,0 +1,34 @@ +// +use actix_web::{error, web, App, FromRequest, HttpResponse, Responder}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + username: String, +} + +/// deserialize `Info` from request's body, max payload size is 4kb +fn index(info: web::Json) -> impl Responder { + format!("Welcome {}!", info.username) +} + +pub fn main() { + App::new().service( + web::resource("/") + .data( + // change json extractor configuration + web::Json::::configure(|cfg| { + cfg.limit(4096).error_handler(|err, _req| { + // <- create custom error response + error::InternalError::from_response( + err, + HttpResponse::Conflict().finish(), + ) + .into() + }) + }), + ) + .route(web::post().to(index)), + ); +} +// diff --git a/examples/extractors/src/main.rs b/examples/extractors/src/main.rs new file mode 100644 index 0000000..917f532 --- /dev/null +++ b/examples/extractors/src/main.rs @@ -0,0 +1,48 @@ +use actix_web::{web, App, FromRequest, HttpRequest, HttpServer, Responder}; +use futures::future::Future; +use serde::Deserialize; + +// mod custom_handler; +mod form; +mod json_one; +mod json_two; +mod multiple; +mod path_one; +mod path_two; +mod query; + +#[derive(Deserialize, Debug)] +struct MyInfo { + username: String, + id: u32, +} + +//
+// Option 1: passed as a parameter to a handler function +fn index(path: web::Path<(String, String)>, json: web::Json) -> impl Responder { + format!("{} {} {} {}", path.0, path.1, json.id, json.username) +} + +// Option 2: accessed by calling extract() on the Extractor +fn extract(req: HttpRequest) -> impl Responder { + let params = web::Path::<(String, String)>::extract(&req).unwrap(); + + let info = web::Json::::extract(&req) + .wait() + .expect("Err with reading json."); + + format!("{} {} {} {}", params.0, params.1, info.username, info.id) +} +//
+ +fn main() { + HttpServer::new(|| { + App::new() + .route("/{name}/{id}", web::post().to(index)) + .route("/{name}/{id}/extract", web::post().to(extract)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/extractors/src/multiple.rs b/examples/extractors/src/multiple.rs new file mode 100644 index 0000000..1d861d4 --- /dev/null +++ b/examples/extractors/src/multiple.rs @@ -0,0 +1,20 @@ +// +use actix_web::{web, App}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + username: String, +} + +fn index((_path, query): (web::Path<(u32, String)>, web::Query)) -> String { + format!("Welcome {}!", query.username) +} + +pub fn main() { + App::new().route( + "/users/{userid}/{friend}", // <- define path parameters + web::get().to(index), + ); +} +// diff --git a/examples/extractors/src/path_one.rs b/examples/extractors/src/path_one.rs new file mode 100644 index 0000000..eb46b9b --- /dev/null +++ b/examples/extractors/src/path_one.rs @@ -0,0 +1,17 @@ +// +use actix_web::{web, App, Result}; + +/// extract path info from "/users/{userid}/{friend}" url +/// {userid} - - deserializes to a u32 +/// {friend} - deserializes to a String +fn index(info: web::Path<(u32, String)>) -> Result { + Ok(format!("Welcome {}! {}", info.1, info.0)) +} + +pub fn main() { + App::new().route( + "/users/{userid}/{friend}", // <- define path parameters + web::get().to(index), + ); +} +// diff --git a/examples/extractors/src/path_two.rs b/examples/extractors/src/path_two.rs new file mode 100644 index 0000000..7919173 --- /dev/null +++ b/examples/extractors/src/path_two.rs @@ -0,0 +1,22 @@ +// +use actix_web::{web, App, Result}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + userid: u32, + friend: String, +} + +/// extract path info using serde +fn index(info: web::Path) -> Result { + Ok(format!("Welcome {}!", info.friend)) +} + +pub fn main() { + App::new().route( + "/users/{userid}/{friend}", // <- define path parameters + web::get().to(index), + ); +} +// diff --git a/examples/extractors/src/query.rs b/examples/extractors/src/query.rs new file mode 100644 index 0000000..ff1883b --- /dev/null +++ b/examples/extractors/src/query.rs @@ -0,0 +1,18 @@ +// +use actix_web::{web, App}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + username: String, +} + +// this handler get called only if the request's query contains `username` field +fn index(info: web::Query) -> String { + format!("Welcome {}!", info.username) +} + +pub fn main() { + App::new().route("/", web::get().to(index)); +} +//