diff --git a/content/docs/request.md b/content/docs/request.md index 1f2e9f8..35e3f79 100644 --- a/content/docs/request.md +++ b/content/docs/request.md @@ -9,9 +9,13 @@ weight: 200 Actix automatically *decompresses* payloads. The following codecs are supported: * Brotli +* Chunked +* Compress * Gzip * Deflate * Identity +* Trailers +* EncodingExt If request headers contain a `Content-Encoding` header, the request payload is decompressed according to the header value. Multiple codecs are not supported, @@ -22,80 +26,18 @@ i.e: `Content-Encoding: br, gzip`. There are several options for json body deserialization. The first option is to use *Json* extractor. First, you define a handler function -that accepts `Json` as a parameter, then, you use the `.with()` method for registering +that accepts `Json` as a parameter, then, you use the `.to()` method for registering this handler. It is also possible to accept arbitrary valid json object by using `serde_json::Value` as a type `T`. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Json, Result, http}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -/// extract `Info` using serde -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 -} -``` - -Another option is to use *HttpRequest::json()*. This method returns a -[*JsonBody*](../../actix-web/actix_web/dev/struct.JsonBody.html) object which resolves into -the deserialized value. - -```rust -#[derive(Debug, Serialize, Deserialize)] -struct MyObj { - name: String, - number: i32, -} - -fn index(req: &HttpRequest) -> Box> { - req.json().from_err() - .and_then(|val: MyObj| { - println!("model: {:?}", val); - Ok(HttpResponse::Ok().json(val)) // <- send response - }) - .responder() -} -``` +{{< include-example example="requests" file="main.rs" section="json-request" >}} You may also manually load the payload into memory and then deserialize it. In the following example, we will deserialize a *MyObj* struct. We need to load the request body first and then deserialize the json into an object. -```rust -extern crate serde_json; -use futures::{Future, Stream}; - -#[derive(Serialize, Deserialize)] -struct MyObj {name: String, number: i32} - -fn index(req: &HttpRequest) -> Box> { - // `concat2` will asynchronously read each chunk of the request body and - // return a single, concatenated, chunk - req.concat2() - // `Future::from_err` acts like `?` in that it coerces the error type from - // the future into the final error type - .from_err() - // `Future::and_then` can be used to merge an asynchronous workflow with a - // synchronous workflow - .and_then(|body| { - let obj = serde_json::from_slice::(&body)?; - Ok(HttpResponse::Ok().json(obj)) - }) - .responder() -} -``` +{{< include-example example="requests" file="manual.rs" section="json-manual" >}} > A complete example for both options is available in > [examples directory](https://github.com/actix/examples/tree/master/json/). @@ -117,31 +59,7 @@ for the current request. The following demonstrates multipart stream handling for a simple form: -```rust -use actix_web::*; - -fn index(req: &HttpRequest) -> Box> { - // get multipart and iterate over multipart items - req.multipart() - .and_then(|item| { - match item { - multipart::MultipartItem::Field(field) => { - println!("==== FIELD ==== {:?} {:?}", - field.headers(), - field.content_type()); - Either::A( - field.map(|chunk| { - println!("-- CHUNK: \n{}", - std::str::from_utf8(&chunk).unwrap());}) - .fold((), |_, _| result(Ok(())))) - }, - multipart::MultipartItem::Nested(mp) => { - Either::B(result(Ok(()))) - } - } - }) -} -``` +{{< include-example example="requests" file="multipart.rs" section="multipart" >}} > A full example is available in the > [examples directory](https://github.com/actix/examples/tree/master/multipart/). @@ -161,27 +79,7 @@ The *UrlEncoded* future can resolve into an error in several cases: * content-length is greater than 256k * payload terminates with error. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::*; -use futures::future::{Future, ok}; - -#[derive(Deserialize)] -struct FormData { - username: String, -} - -fn index(req: &HttpRequest) -> Box> { - req.urlencoded::() // <- get UrlEncoded future - .from_err() - .and_then(|data| { // <- deserialized instance - println!("USERNAME: {:?}", data.username); - ok(HttpResponse::Ok().into()) - }) - .responder() -} -# fn main() {} -``` +{{< include-example example="requests" file="urlencoded.rs" section="urlencoded" >}} # Streaming request @@ -190,20 +88,4 @@ body payload. In the following example, we read and print the request payload chunk by chunk: -```rust -use actix_web::*; -use futures::{Future, Stream}; - - -fn index(req: &HttpRequest) -> Box> { - req - .payload() - .from_err() - .fold((), |_, chunk| { - println!("Chunk: {:?}", chunk); - result::<_, error::PayloadError>(Ok(())) - }) - .map(|_| HttpResponse::Ok().finish()) - .responder() -} -``` +{{< include-example example="requests" file="streaming.rs" section="streaming" >}} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4a6c8ae..05f095a 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -17,5 +17,6 @@ exclude = [ "async-handlers", "extractors", "autoreload", - "errors" + "errors", + "requests", ] diff --git a/examples/requests/Cargo.toml b/examples/requests/Cargo.toml new file mode 100644 index 0000000..49a8984 --- /dev/null +++ b/examples/requests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "requests" +version = "0.1.0" +authors = ["Cameron Dershem "] +edition = "2018" + +[dependencies] +serde = "1.0" +serde_json = "1.0" +actix-web = "1.0" +futures = "0.1" +bytes = "0.4" diff --git a/examples/requests/src/json_two.rs b/examples/requests/src/json_two.rs new file mode 100644 index 0000000..659bd96 --- /dev/null +++ b/examples/requests/src/json_two.rs @@ -0,0 +1,21 @@ +// // +// use actix_web::{error::Error, HttpRequest, HttpResponse}; +// use futures::Future; +// use serde::{Deserialize, Serialize}; + +// #[derive(Debug, Serialize, Deserialize)] +// struct MyObj { +// name: String, +// number: i32, +// } + +// fn index(req: HttpRequest) -> Box> { +// req.json() +// .from_err() +// .and_then(|val: MyObj| { +// println!("model: {:?}", val); +// Ok(HttpResponse::Ok().json(val)) // <- send response +// }) +// .responder() +// } +// // diff --git a/examples/requests/src/main.rs b/examples/requests/src/main.rs new file mode 100644 index 0000000..9b3ad62 --- /dev/null +++ b/examples/requests/src/main.rs @@ -0,0 +1,23 @@ +mod json_two; +mod manual; +mod multipart; +mod streaming; +mod urlencoded; +// +use actix_web::{web, App, Result}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + username: String, +} + +/// extract `Info` using serde +fn index(info: web::Json) -> Result { + Ok(format!("Welcome {}!", info.username)) +} + +fn main() { + App::new().route("/index.html", web::post().to(index)); +} +// diff --git a/examples/requests/src/manual.rs b/examples/requests/src/manual.rs new file mode 100644 index 0000000..846f704 --- /dev/null +++ b/examples/requests/src/manual.rs @@ -0,0 +1,43 @@ +// +use actix_web::{error, web, Error, HttpResponse}; +use bytes::BytesMut; +use futures::{Future, Stream}; +use serde::{Deserialize, Serialize}; +use serde_json; + +#[derive(Serialize, Deserialize)] +struct MyObj { + name: String, + number: i32, +} + +const MAX_SIZE: usize = 262_144; // max payload size is 256k + +fn index_manual( + payload: web::Payload, +) -> impl Future { + // payload is a stream of Bytes objects + 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 + .and_then(|body| { + // body is loaded, now we can deserialize serde-json + let obj = serde_json::from_slice::(&body)?; + Ok(HttpResponse::Ok().json(obj)) // <- send response + }) +} +// diff --git a/examples/requests/src/multipart.rs b/examples/requests/src/multipart.rs new file mode 100644 index 0000000..0fa74d9 --- /dev/null +++ b/examples/requests/src/multipart.rs @@ -0,0 +1,25 @@ +// +// use actix_web::{error, Error, HttpRequest, HttpResponse}; +// use futures::Future; + +// fn index(req: HttpRequest) -> Box> { +// // get multipart and iterate over multipart items +// req.multipart().and_then(|item| match item { +// multipart::MultipartItem::Field(field) => { +// println!( +// "==== FIELD ==== {:?} {:?}", +// field.headers(), +// field.content_type() +// ); +// Either::A( +// field +// .map(|chunk| { +// println!("-- CHUNK: \n{}", std::str::from_utf8(&chunk).unwrap()); +// }) +// .fold((), |_, _| result(Ok(()))), +// ) +// } +// multipart::MultipartItem::Nested(mp) => Either::B(result(Ok(()))), +// }) +// } +// diff --git a/examples/requests/src/streaming.rs b/examples/requests/src/streaming.rs new file mode 100644 index 0000000..191d32a --- /dev/null +++ b/examples/requests/src/streaming.rs @@ -0,0 +1,15 @@ +// +// use actix_web::{error, web, Error, HttpResponse}; +// use futures::{future::result, Future, Stream}; + +// fn index(payload: web::Payload) -> Box> { +// payload +// .from_err() +// .fold((), |_, chunk| { +// println!("Chunk: {:?}", chunk); +// result::<_, error::PayloadError>(Ok(())) +// }) +// .map(|_| HttpResponse::Ok().finish()) +// .responder() +// } +// diff --git a/examples/requests/src/urlencoded.rs b/examples/requests/src/urlencoded.rs new file mode 100644 index 0000000..5439d0d --- /dev/null +++ b/examples/requests/src/urlencoded.rs @@ -0,0 +1,22 @@ +// +// use actix_web::{Error, HttpRequest, HttpResponse}; +// use futures::future::{ok, Future}; +// use serde::Deserialize; + +// #[derive(Deserialize)] +// struct FormData { +// username: String, +// } + +// fn index(req: &HttpRequest) -> Box> { +// req.urlencoded::() // <- get UrlEncoded future +// .from_err() +// .and_then(|data| { +// // <- deserialized instance +// println!("USERNAME: {:?}", data.username); +// ok(HttpResponse::Ok().into()) +// }) +// .responder() +// } +// +fn main() {}