From 9a16b6db938ec02a7dad188475fb2a2c650de398 Mon Sep 17 00:00:00 2001 From: Cameron Dershem Date: Mon, 17 Jun 2019 16:57:57 -0400 Subject: [PATCH] First pass at Middlewares. --- content/docs/middleware.md | 138 ++------------------- examples/Cargo.toml | 1 + examples/middleware/Cargo.toml | 11 ++ examples/middleware/src/default_headers.rs | 16 +++ examples/middleware/src/errorhandler.rs | 25 ++++ examples/middleware/src/logger.rs | 14 +++ examples/middleware/src/main.rs | 72 +++++++++++ examples/middleware/src/user_sessions.rs | 37 ++++++ 8 files changed, 189 insertions(+), 125 deletions(-) create mode 100644 examples/middleware/Cargo.toml create mode 100644 examples/middleware/src/default_headers.rs create mode 100644 examples/middleware/src/errorhandler.rs create mode 100644 examples/middleware/src/logger.rs create mode 100644 examples/middleware/src/main.rs create mode 100644 examples/middleware/src/user_sessions.rs diff --git a/content/docs/middleware.md b/content/docs/middleware.md index c024289..0a1c853 100644 --- a/content/docs/middleware.md +++ b/content/docs/middleware.md @@ -21,48 +21,14 @@ Typically, middleware is involved in the following actions: Middleware is registered for each application and executed in same order as registration. In general, a *middleware* is a type that implements the -[*Middleware trait*](../../actix-web/actix_web/middleware/trait.Middleware.html). -Each method in this trait has a default implementation. Each method can return +[*Service trait*](../../actix-web/actix_web/dev/trait.Service.html) and +[*Transform trait*](../../actix-web/actix_web/dev/trait.Transform.html). +Each method in the traits has a default implementation. Each method can return a result immediately or a *future* object. -The following demonstrates using middleware to add request and response headers: +The following demonstrates creating a simple middleware: -```rust -use http::{header, HttpTryFrom}; -use actix_web::{App, HttpRequest, HttpResponse, Result}; -use actix_web::middleware::{Middleware, Started, Response}; - -struct Headers; // <- Our middleware - -/// Middleware implementation, middlewares are generic over application state, -/// so you can access state with `HttpRequest::state()` method. -impl Middleware for Headers { - - /// Method is called when request is ready. It may return - /// future, which should resolve before next middleware get called. - fn start(&self, req: &HttpRequest) -> Result { - Ok(Started::Done) - } - - /// Method is called when handler returns response, - /// but before sending http message to peer. - fn response(&self, req: &HttpRequest, mut resp: HttpResponse) - -> Result - { - resp.headers_mut().insert( - header::HeaderName::try_from("X-VERSION").unwrap(), - header::HeaderValue::from_static("0.2")); - Ok(Response::Done(resp)) - } -} - -fn main() { - App::new() - // Register middleware, this method can be called multiple times - .middleware(Headers) - .resource("/", |r| r.f(|_| HttpResponse::Ok())); -} -``` +{{< include-example example="middleware" file="main.rs" section="main" >}} > Actix provides several useful middlewares, such as *logging*, *user sessions*, etc. @@ -85,21 +51,7 @@ Default `Logger` can be created with `default` method, it uses the default forma %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T ``` -```rust -extern crate env_logger; -use actix_web::App; -use actix_web::middleware::Logger; - -fn main() { - std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - - App::new() - .middleware(Logger::default()) - .middleware(Logger::new("%a %{User-Agent}i")) - .finish(); -} -``` +{{< include-example example="middleware" file="logger.rs" section="logger" >}} The following is an example of the default logging format: @@ -140,37 +92,23 @@ To set default response headers, the `DefaultHeaders` middleware can be used. Th *DefaultHeaders* middleware does not set the header if response headers already contain a specified header. -```rust -use actix_web::{http, middleware, App, HttpResponse}; - -fn main() { - let app = App::new() - .middleware( - middleware::DefaultHeaders::new() - .header("X-Version", "0.2")) - .resource("/test", |r| { - r.method(http::Method::GET).f(|req| HttpResponse::Ok()); - r.method(http::Method::HEAD).f(|req| HttpResponse::MethodNotAllowed()); - }) - .finish(); -} -``` +{{< include-example example="middleware" file="default_headers.rs" section="default-headers" >}} ## User sessions Actix provides a general solution for session management. The -[**SessionStorage**](../../actix-web/actix_web/middleware/session/struct.SessionStorage.html) middleware can be +[**actix-session**](https://docs.rs/actix-session/0.1.1/actix_session/) middleware can be used with different backend types to store session data in different backends. > By default, only cookie session backend is implemented. Other backend implementations > can be added. -[**CookieSessionBackend**](../../actix-web/actix_web/middleware/session/struct.CookieSessionBackend.html) +[**CookieSession**](../../actix-web/actix_web/middleware/session/struct.CookieSessionBackend.html) uses cookies as session storage. `CookieSessionBackend` creates sessions which are limited to storing fewer than 4000 bytes of data, as the payload must fit into a single cookie. An internal server error is generated if a session contains more than 4000 bytes. -A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor. +A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSession` constructor. A *signed* cookie may be viewed but not modified by the client. A *private* cookie may neither be viewed nor modified by the client. @@ -178,41 +116,13 @@ The constructors take a key as an argument. This is the private key for cookie s In general, you create a `SessionStorage` middleware and initialize it with specific backend implementation, -such as a `CookieSessionBackend`. To access session data, +such as a `CookieSession`. To access session data, [*HttpRequest::session()*](../../actix-web/actix_web/middleware/session/trait.RequestSession.html#tymethod.session) must be used. This method returns a [*Session*](../../actix-web/actix_web/middleware/session/struct.Session.html) object, which allows us to get or set session data. -```rust -use actix_web::{server, App, HttpRequest, Result}; -use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; - -fn index(req: &HttpRequest) -> Result<&'static str> { - // access session data - if let Some(count) = req.session().get::("counter")? { - println!("SESSION value: {}", count); - req.session().set("counter", count+1)?; - } else { - req.session().set("counter", 1)?; - } - - Ok("Welcome!") -} - -fn main() { - let sys = actix::System::new("basic-example"); - server::new( - || App::new().middleware( - SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]) - .secure(false) - ))) - .bind("127.0.0.1:59880").unwrap() - .start(); - let _ = sys.run(); -} -``` +{{< include-example example="middleware" file="user_sessions.rs" section="user-session" >}} # Error handlers @@ -223,26 +133,4 @@ for a specific status code. You can modify an existing response or create a comp one. The error handler can return a response immediately or return a future that resolves into a response. -```rust -use actix_web::{ - App, HttpRequest, HttpResponse, Result, - http, middleware::Response, middleware::ErrorHandlers}; - -fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { - let mut builder = resp.into_builder(); - builder.header(http::header::CONTENT_TYPE, "application/json"); - Ok(Response::Done(builder.into())) -} - -fn main() { - let app = App::new() - .middleware( - ErrorHandlers::new() - .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500)) - .resource("/test", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); - }) - .finish(); -} -``` +{{< include-example example="middleware" file="errorhandler.rs" section="error-handler" >}} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 94df1cc..ae1746f 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -21,4 +21,5 @@ exclude = [ "requests", "responses", "testing", + "middleware", ] diff --git a/examples/middleware/Cargo.toml b/examples/middleware/Cargo.toml new file mode 100644 index 0000000..dceb867 --- /dev/null +++ b/examples/middleware/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "middleware" +version = "0.1.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +actix-service = "0.4" +actix-session = "0.1" +futures = "0.1" +env_logger = "0.6" diff --git a/examples/middleware/src/default_headers.rs b/examples/middleware/src/default_headers.rs new file mode 100644 index 0000000..a81e7a1 --- /dev/null +++ b/examples/middleware/src/default_headers.rs @@ -0,0 +1,16 @@ +// +use actix_web::{http, middleware, web, App, HttpResponse}; + +fn main() { + App::new() + .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) + .service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) + .route( + web::method(http::Method::HEAD) + .to(|| HttpResponse::MethodNotAllowed()), + ), + ); +} +// diff --git a/examples/middleware/src/errorhandler.rs b/examples/middleware/src/errorhandler.rs new file mode 100644 index 0000000..dd3b440 --- /dev/null +++ b/examples/middleware/src/errorhandler.rs @@ -0,0 +1,25 @@ +// +use actix_web::middleware::errhandlers::{ErrorHandlerResponse, ErrorHandlers}; +use actix_web::{dev, http, web, App, HttpResponse, Result}; + +fn render_500(mut res: dev::ServiceResponse) -> Result> { + res.response_mut().headers_mut().insert( + http::header::CONTENT_TYPE, + http::HeaderValue::from_static("Error"), + ); + Ok(ErrorHandlerResponse::Response(res)) +} + +fn main() { + App::new() + .wrap( + ErrorHandlers::new() + .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), + ) + .service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::head().to(|| HttpResponse::MethodNotAllowed())), + ); +} +// diff --git a/examples/middleware/src/logger.rs b/examples/middleware/src/logger.rs new file mode 100644 index 0000000..e660723 --- /dev/null +++ b/examples/middleware/src/logger.rs @@ -0,0 +1,14 @@ +// +use actix_web::middleware::Logger; +use actix_web::App; +use env_logger; + +fn main() { + std::env::set_var("RUST_LOG", "actix_web=info"); + env_logger::init(); + + App::new() + .wrap(Logger::default()) + .wrap(Logger::new("%a %{User-Agent}i")); +} +// diff --git a/examples/middleware/src/main.rs b/examples/middleware/src/main.rs new file mode 100644 index 0000000..4d4168f --- /dev/null +++ b/examples/middleware/src/main.rs @@ -0,0 +1,72 @@ +mod default_headers; +mod errorhandler; +mod logger; +mod user_sessions; +//
+use actix_service::{Service, Transform}; +use actix_web::{dev::ServiceRequest, dev::ServiceResponse, web, App, Error}; +use futures::future::{ok, FutureResult}; +use futures::{Future, Poll}; + +// There are two steps in middleware processing. +// 1. Middleware initialization, middleware factory gets called with +// next service in chain as parameter. +// 2. Middleware's call method gets called with normal request. +pub struct SayHi; + +// Middleware factory is `Transform` trait from actix-service crate +// `S` - type of the next service +// `B` - type of response's body +impl Transform for SayHi +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = SayHiMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(SayHiMiddleware { service }) + } +} + +pub struct SayHiMiddleware { + service: S, +} + +impl Service for SayHiMiddleware +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, req: ServiceRequest) -> Self::Future { + println!("Hi from start. You requested: {}", req.path()); + + Box::new(self.service.call(req).and_then(|res| { + println!("Hi from response"); + Ok(res) + })) + } +} +//
+fn main() { + App::new().wrap(SayHi).service( + web::resource("/") + .to(|| "Hello, middleware! Check the console where the server is run."), + ); +} diff --git a/examples/middleware/src/user_sessions.rs b/examples/middleware/src/user_sessions.rs new file mode 100644 index 0000000..42fafff --- /dev/null +++ b/examples/middleware/src/user_sessions.rs @@ -0,0 +1,37 @@ +// +use actix_session::{CookieSession, Session}; +use actix_web::{middleware::Logger, web, App, HttpRequest, HttpServer, Result}; + +/// simple index handler with session +fn index(session: Session, req: HttpRequest) -> Result<&'static str> { + println!("{:?}", req); + + // RequestSession trait is used for session access + let mut counter = 1; + if let Some(count) = session.get::("counter")? { + println!("SESSION value: {}", count); + counter = count + 1; + session.set("counter", counter)?; + } else { + session.set("counter", counter)?; + } + + Ok("welcome!") +} + +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); + env_logger::init(); + + HttpServer::new(|| { + App::new() + // enable logger + .wrap(Logger::default()) + // cookie session middleware + .wrap(CookieSession::signed(&[0; 32]).secure(false)) + .service(web::resource("/").to(index)) + }) + .bind("127.0.0.1:8080")? + .run() +} +//