1
0
mirror of https://github.com/actix/actix-website synced 2025-01-22 16:15:56 +01:00

extractors: done-ish.

This commit is contained in:
Cameron Dershem 2019-06-20 04:20:12 -04:00
parent 59f010461a
commit c4c32091a6
13 changed files with 199 additions and 142 deletions

View File

@ -6,37 +6,26 @@ weight: 170
# Type-safe information extraction # Type-safe information extraction
Actix provides facility for type-safe request information extraction. By default, Actix-web provides a facility for type-safe request information access called *extractors*
actix provides several extractor implementations. (ie, `impl FromRequest`). By default, actix-web provides several extractor implementations.
# Accessing Extractors ## Extractors Within Handler Functions
How you access an Extractor depends on whether you are using a handler function An extractor can be accessed in a few different ways.
or a custom Handler type.
## Within Handler Functions Option 1 - passed as a parameter to a handler function:
An Extractor can be passed to a handler function as a function parameter {{< include-example example="extractors" file="main.rs" section="option-one" >}}
*or* accessed within the function by calling the ExtractorType::<...>::extract(req)
function.
{{< include-example example="extractors" file="main.rs" section="main" >}} Option 2 - accessed by calling `extract()` on the Extractor
## Within Custom Handler Types {{< include-example example="extractors" file="main.rs" section="option-two" >}}
Like a handler function, a custom Handler type can *access* an Extractor by
calling the ExtractorType::<...>::extract(&req) function. An Extractor
*cannot* be passed as a parameter to a custom Handler type because a custom
Handler type must follow the ``handle`` function signature specified by the
Handler trait it implements.
{{< include-example example="extractors" file="custom_handler.rs" section="custom-handler" >}}
# Path # Path
[*Path*](../../actix-web/actix_web/struct.Path.html) provides information that can [*Path*](https://docs.rs/actix-web/1.0.2/actix_web/dev/struct.Path.html) provides
be extracted from the Request's path. You can deserialize any variable information that can be extracted from the Request's path. You can deserialize any
segment from the path. variable segment from the path.
For instance, for resource that registered for the `/users/{userid}/{friend}` path For instance, for resource that registered for the `/users/{userid}/{friend}` path
two segments could be deserialized, `userid` and `friend`. These segments two segments could be deserialized, `userid` and `friend`. These segments
@ -51,27 +40,31 @@ instead of a *tuple* type.
{{< include-example example="extractors" file="path_two.rs" section="path-two" >}} {{< include-example example="extractors" file="path_two.rs" section="path-two" >}}
It is also possible to `get` or `query` the request for path parameters by name:
{{< include-example example="extractors" file="path_three.rs" section="path-three" >}}
# Query # Query
Same can be done with the request's query. The [*Query*](https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Query.html)
The [*Query*](../../actix-web/actix_web/struct.Query.html) type provides extraction functionality for the request's query parameters. Underneath it
type provides extraction functionality. Underneath it uses *serde_urlencoded* crate. uses *serde_urlencoded* crate.
{{< include-example example="extractors" file="query.rs" section="query" >}} {{< include-example example="extractors" file="query.rs" section="query" >}}
# Json # Json
[*Json*](../../actix-web/actix_web/struct.Json.html) allows to deserialize [*Json*](https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Json.html) allows to deserialize
a request body into a struct. To extract typed information from a request's body, 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*. the type `T` must implement the `Deserialize` trait from *serde*.
{{< include-example example="extractors" file="json_one.rs" section="json-one" >}} {{< include-example example="extractors" file="json_one.rs" section="json-one" >}}
Some extractors provide a way to configure the extraction process. Json extractor Some extractors provide a way to configure the extraction process. Json extractor
[*JsonConfig*](../../actix-web/actix_web/dev/struct.JsonConfig.html) type for configuration. [*JsonConfig*](https://docs.rs/actix-web/1.0.2/actix_web/web/struct.JsonConfig.html) type
When you register a handler using `Route::with()`, it returns a configuration instance. In case of for configuration. When you register a handler using `Route::with()`, it returns a
a *Json* extractor it returns a *JsonConfig*. You can configure the maximum size of the json configuration instance. In case of a *Json* extractor it returns a *JsonConfig*. You can
payload as well as a custom error handler function. configure the maximum size of the json 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. The following example limits the size of the payload to 4kb and uses a custom error handler.
@ -83,14 +76,14 @@ At the moment only url-encoded forms are supported. The url-encoded body
could be extracted to a specific type. This type must implement could be extracted to a specific type. This type must implement
the `Deserialize` trait from the *serde* crate. the `Deserialize` trait from the *serde* crate.
[*FormConfig*](../../actix-web/actix_web/dev/struct.FormConfig.html) allows [*FormConfig*](https://docs.rs/actix-web/1.0.2/actix_web/web/struct.FormConfig.html) allows
configuring the extraction process. configuring the extraction process.
{{< include-example example="extractors" file="form.rs" section="form" >}} {{< include-example example="extractors" file="form.rs" section="form" >}}
# Multiple extractors # Multiple extractors
Actix provides extractor implementations for tuples (up to 10 elements) Actix-web provides extractor implementations for tuples (up to 10 elements)
whose elements implement `FromRequest`. whose elements implement `FromRequest`.
For example we can use a path extractor and a query extractor at the same time. For example we can use a path extractor and a query extractor at the same time.
@ -99,15 +92,42 @@ For example we can use a path extractor and a query extractor at the same time.
# Other # Other
Actix also provides several other extractors: Actix-web also provides several other extractors:
* [*Data*](../../actix-web/actix_web/web/struct.Data.html) - If you need * [*Data*](https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Data.html) - If you need
access to an application state. This is similar to a `HttpRequest::app_data()`. access to an application state.
* *HttpRequest* - *HttpRequest* itself is an extractor which returns self, * *HttpRequest* - *HttpRequest* itself is an extractor which returns self,
in case you need access to the request. in case you need access to the request.
* *String* - You can convert a request's payload to a *String*. * *String* - You can convert a request's payload to a *String*.
[*Example*](../../actix-web/actix_web/trait.FromRequest.html#example-1) [*Example*](https://docs.rs/actix-web/1.0.2/actix_web/trait.FromRequest.html#example-2)
is available in doc strings. is available in doc strings.
* *bytes::Bytes* - You can convert a request's payload into *Bytes*. * *bytes::Bytes* - You can convert a request's payload into *Bytes*.
[*Example*](../../actix-web/actix_web/trait.FromRequest.html#example) [*Example*](https://docs.rs/actix-web/1.0.2/actix_web/trait.FromRequest.html#example-4)
is available in doc strings. is available in doc strings.
* *Payload* - You can access a request's payload.
[*Example*](https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Payload.html)
# Async Data Access
Application state is accessible from the handler with the `web::Data` extractor;
however, state is accessible as a read-only reference. If you need mutable access to state,
it must be implemented.
> **Beware**, actix creates multiple copies of the application state and the handlers,
> unique for each thread. If you run your application in several threads, actix will
> create the same amount as number of threads of application state objects and handler
> objects.
Here is an example of a handler that stores the number of processed requests:
{{< include-example example="request-handlers" file="main.rs" section="data" >}}
Although this handler will work, `self.0` will be different depending on the number of threads and
number of requests processed per thread. A proper implementation would use `Arc` and `AtomicUsize`.
{{< include-example example="request-handlers" file="handlers_arc.rs" section="arc" >}}
> Be careful with synchronization primitives like `Mutex` or `RwLock`. The `actix-web` framework
> handles requests asynchronously. By blocking thread execution, all concurrent
> request handling processes would block. If you need to share or update some state
> from multiple threads, consider using the [actix](https://actix.github.io/actix/actix/) actor system.

View File

@ -1,18 +0,0 @@
use actix_web::{web, HttpRequest, HttpResponse};
struct MyHandler {}
struct MyInfo {}
// <custom-handler>
impl<S> Handler<S> for MyHandler {
type Result = HttpResponse;
/// Handle request
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
let params = web::Path::<(String, String)>::extract(req);
let info = web::Json::<MyInfo>::extract(req);
HttpResponse::Ok().into()
}
}
// </custom-handler>

View File

@ -1,5 +1,5 @@
// <form> // <form>
use actix_web::{web, App, Result}; use actix_web::{web, App, HttpServer, Result};
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -16,5 +16,9 @@ fn index(form: web::Form<FormData>) -> Result<String> {
// </form> // </form>
pub fn main() { pub fn main() {
App::new().route("", web::post().to(index)); HttpServer::new(|| App::new().route("/", web::post().to(index)))
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
} }

View File

@ -1,5 +1,5 @@
// <json-one> // <json-one>
use actix_web::{web, App, Result}; use actix_web::{web, App, HttpServer, Result};
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -11,8 +11,12 @@ struct Info {
fn index(info: web::Json<Info>) -> Result<String> { fn index(info: web::Json<Info>) -> Result<String> {
Ok(format!("Welcome {}!", info.username)) Ok(format!("Welcome {}!", info.username))
} }
// </json-one>
pub fn main() { pub fn main() {
App::new().route("/", web::get().to(index)); HttpServer::new(|| App::new().route("/", web::post().to(index)))
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
} }
// </json-one>

View File

@ -1,5 +1,5 @@
// <json-two> // <json-two>
use actix_web::{error, web, App, FromRequest, HttpResponse, Responder}; use actix_web::{error, web, App, FromRequest, HttpResponse, HttpServer, Responder};
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -13,6 +13,7 @@ fn index(info: web::Json<Info>) -> impl Responder {
} }
pub fn main() { pub fn main() {
HttpServer::new(|| {
App::new().service( App::new().service(
web::resource("/") web::resource("/")
.data( .data(
@ -29,6 +30,11 @@ pub fn main() {
}), }),
) )
.route(web::post().to(index)), .route(web::post().to(index)),
); )
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
} }
// </json-two> // </json-two>

View File

@ -8,6 +8,7 @@ pub mod json_one;
pub mod json_two; pub mod json_two;
pub mod multiple; pub mod multiple;
pub mod path_one; pub mod path_one;
pub mod path_three;
pub mod path_two; pub mod path_two;
pub mod query; pub mod query;
@ -17,13 +18,13 @@ struct MyInfo {
id: u32, id: u32,
} }
// <main> // <option-one>
// Option 1: passed as a parameter to a handler function
fn index(path: web::Path<(String, String)>, json: web::Json<MyInfo>) -> impl Responder { fn index(path: web::Path<(String, String)>, json: web::Json<MyInfo>) -> impl Responder {
format!("{} {} {} {}", path.0, path.1, json.id, json.username) format!("{} {} {} {}", path.0, path.1, json.id, json.username)
} }
// </option-one>
// Option 2: accessed by calling extract() on the Extractor // <option-two>
fn extract(req: HttpRequest) -> impl Responder { fn extract(req: HttpRequest) -> impl Responder {
let params = web::Path::<(String, String)>::extract(&req).unwrap(); let params = web::Path::<(String, String)>::extract(&req).unwrap();
@ -33,7 +34,7 @@ fn extract(req: HttpRequest) -> impl Responder {
format!("{} {} {} {}", params.0, params.1, info.username, info.id) format!("{} {} {} {}", params.0, params.1, info.username, info.id)
} }
// </main> // </option-two>
fn main() { fn main() {
HttpServer::new(|| { HttpServer::new(|| {

View File

@ -1,5 +1,5 @@
// <multi> // <multi>
use actix_web::{web, App}; use actix_web::{web, App, HttpServer};
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -7,14 +7,23 @@ struct Info {
username: String, username: String,
} }
fn index((_path, query): (web::Path<(u32, String)>, web::Query<Info>)) -> String { fn index((path, query): (web::Path<(u32, String)>, web::Query<Info>)) -> String {
format!("Welcome {}!", query.username) format!(
"Welcome {}, friend {}, useri {}!",
query.username, path.1, path.0
)
} }
pub fn main() { pub fn main() {
HttpServer::new(|| {
App::new().route( App::new().route(
"/users/{userid}/{friend}", // <- define path parameters "/users/{userid}/{friend}", // <- define path parameters
web::get().to(index), web::get().to(index),
); )
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
} }
// </multi> // </multi>

View File

@ -1,17 +1,23 @@
// <path-one> // <path-one>
use actix_web::{web, App, Result}; use actix_web::{web, App, HttpServer, Result};
/// extract path info from "/users/{userid}/{friend}" url /// extract path info from "/users/{userid}/{friend}" url
/// {userid} - - deserializes to a u32 /// {userid} - - deserializes to a u32
/// {friend} - deserializes to a String /// {friend} - deserializes to a String
fn index(info: web::Path<(u32, String)>) -> Result<String> { fn index(info: web::Path<(u32, String)>) -> Result<String> {
Ok(format!("Welcome {}! {}", info.1, info.0)) Ok(format!("Welcome {}, userid {}!", info.1, info.0))
} }
pub fn main() { pub fn main() {
HttpServer::new(|| {
App::new().route( App::new().route(
"/users/{userid}/{friend}", // <- define path parameters "/users/{userid}/{friend}", // <- define path parameters
web::get().to(index), web::get().to(index),
); )
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
} }
// </path-one> // </path-one>

View File

@ -1,5 +1,5 @@
// <path-two> // <path-two>
use actix_web::{web, App, Result}; use actix_web::{web, App, HttpServer, Result};
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -14,9 +14,15 @@ fn index(info: web::Path<Info>) -> Result<String> {
} }
pub fn main() { pub fn main() {
HttpServer::new(|| {
App::new().route( App::new().route(
"/users/{userid}/{friend}", // <- define path parameters "/users/{userid}/{friend}", // <- define path parameters
web::get().to(index), web::get().to(index),
); )
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
} }
// </path-two> // </path-two>

View File

@ -1,5 +1,5 @@
// <query> // <query>
use actix_web::{web, App}; use actix_web::{web, App, HttpServer};
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -11,8 +11,12 @@ struct Info {
fn index(info: web::Query<Info>) -> String { fn index(info: web::Query<Info>) -> String {
format!("Welcome {}!", info.username) format!("Welcome {}!", info.username)
} }
// </query>
pub fn main() { pub fn main() {
App::new().route("/", web::get().to(index)); HttpServer::new(|| App::new().route("/", web::get().to(index)))
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
} }
// </query>

View File

@ -1,8 +1,7 @@
[package] [package]
name = "request-handlers" name = "request-handlers"
version = "0.7.0" version = "1.0.0"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-web = "0.7" actix-web = "1.0"
actix = "0.7"

View File

@ -1,34 +1,37 @@
// <arc> // <arc>
use actix_web::{dev::Handler, server, App, HttpRequest, HttpResponse}; use actix_web::{web, App, HttpServer, Responder};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
struct MyHandler(Arc<AtomicUsize>); #[derive(Clone)]
struct AppState {
count: Arc<AtomicUsize>,
}
impl<S> Handler<S> for MyHandler { fn show_count(data: web::Data<AppState>) -> impl Responder {
type Result = HttpResponse; format!("count: {}", data.count.load(Ordering::Relaxed))
}
/// Handle request fn add_one(data: web::Data<AppState>) -> impl Responder {
fn handle(&self, _req: &HttpRequest<S>) -> Self::Result { data.count.fetch_add(1, Ordering::Relaxed);
self.0.fetch_add(1, Ordering::Relaxed);
HttpResponse::Ok().into() format!("count: {}", data.count.load(Ordering::Relaxed))
}
} }
pub fn main() { pub fn main() {
let sys = actix::System::new("example"); let data = AppState {
count: Arc::new(AtomicUsize::new(0)),
};
let inc = Arc::new(AtomicUsize::new(0)); HttpServer::new(move || {
App::new()
server::new(move || { .data(data.clone())
let cloned = inc.clone(); .route("/", web::to(show_count))
App::new().resource("/", move |r| r.h(MyHandler(cloned))) .route("/add", web::to(add_one))
}) })
.bind("127.0.0.1:8088") .bind("127.0.0.1:8088")
.unwrap() .unwrap()
.start(); .run()
.unwrap();
println!("Started http server: 127.0.0.1:8088");
let _ = sys.run();
} }
// </arc> // </arc>

View File

@ -1,25 +1,38 @@
mod handlers_arc; pub mod handlers_arc;
// <handler> // <data>
use actix_web::{dev::Handler, server, App, HttpRequest, HttpResponse}; use actix_web::{web, App, HttpServer, Responder};
use std::cell::Cell; use std::cell::Cell;
struct MyHandler(Cell<usize>); #[derive(Clone)]
struct AppState {
count: Cell<i32>,
}
impl<S> Handler<S> for MyHandler { fn show_count(data: web::Data<AppState>) -> impl Responder {
type Result = HttpResponse; format!("count: {}", data.count.get())
}
/// Handle request fn add_one(data: web::Data<AppState>) -> impl Responder {
fn handle(&self, _req: &HttpRequest<S>) -> Self::Result { let count = data.count.get();
let i = self.0.get(); data.count.set(count + 1);
self.0.set(i + 1);
HttpResponse::Ok().into() format!("count: {}", data.count.get())
}
} }
fn main() { fn main() {
server::new(|| App::new().resource("/", |r| r.h(MyHandler(Cell::new(0))))) //use r.h() to bind handler, not the r.f() let data = AppState {
count: Cell::new(0),
};
HttpServer::new(move || {
App::new()
.data(data.clone())
.route("/", web::to(show_count))
.route("/add", web::to(add_one))
})
.bind("127.0.0.1:8088") .bind("127.0.0.1:8088")
.unwrap() .unwrap()
.run(); .run()
.unwrap();
} }
// </handler> // </data>