diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index ab70bf07ce2..9b10f14b1d4 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -163,6 +163,7 @@ serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" smallvec = "1.6.1" +tracing = "0.1.30" socket2 = "0.5" time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" diff --git a/actix-web/examples/from_fn.rs b/actix-web/examples/from_fn.rs new file mode 100644 index 00000000000..a6006d23c73 --- /dev/null +++ b/actix-web/examples/from_fn.rs @@ -0,0 +1,128 @@ +//! Shows a few of ways to use the `from_fn` middleware. + +use std::{collections::HashMap, io, rc::Rc, time::Duration}; + +use actix_web::{ + body::MessageBody, + dev::{Service, ServiceRequest, ServiceResponse, Transform}, + http::header::{self, HeaderValue, Range}, + middleware::{from_fn, Logger, Next}, + web::{self, Header, Query}, + App, Error, HttpResponse, HttpServer, +}; +use tracing::info; + +async fn noop(req: ServiceRequest, next: Next) -> Result, Error> { + next.call(req).await +} + +async fn print_range_header( + range_header: Option>, + req: ServiceRequest, + next: Next, +) -> Result, Error> { + if let Some(Header(range)) = range_header { + println!("Range: {range}"); + } else { + println!("No Range header"); + } + + next.call(req).await +} + +async fn mutate_body_type( + req: ServiceRequest, + next: Next, +) -> Result, Error> { + let res = next.call(req).await?; + Ok(res.map_into_left_body::<()>()) +} + +async fn mutate_body_type_with_extractors( + string_body: String, + query: Query>, + req: ServiceRequest, + next: Next, +) -> Result, Error> { + println!("body is: {string_body}"); + println!("query string: {query:?}"); + + let res = next.call(req).await?; + + Ok(res.map_body(move |_, _| string_body)) +} + +async fn timeout_10secs( + req: ServiceRequest, + next: Next, +) -> Result, Error> { + match tokio::time::timeout(Duration::from_secs(10), next.call(req)).await { + Ok(res) => res, + Err(_err) => Err(actix_web::error::ErrorRequestTimeout("")), + } +} + +struct MyMw(bool); + +impl MyMw { + async fn mw_cb( + &self, + req: ServiceRequest, + next: Next, + ) -> Result, Error> { + let mut res = match self.0 { + true => req.into_response("short-circuited").map_into_right_body(), + false => next.call(req).await?.map_into_left_body(), + }; + + res.headers_mut() + .insert(header::WARNING, HeaderValue::from_static("42")); + + Ok(res) + } + + pub fn into_middleware( + self, + ) -> impl Transform< + S, + ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + > + where + S: Service, Error = Error> + 'static, + B: MessageBody + 'static, + { + let this = Rc::new(self); + from_fn(move |req, next| { + let this = Rc::clone(&this); + async move { Self::mw_cb(&this, req, next).await } + }) + } +} + +#[actix_web::main] +async fn main() -> io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + let bind = ("127.0.0.1", 8080); + info!("staring server at http://{}:{}", &bind.0, &bind.1); + + HttpServer::new(|| { + App::new() + .wrap(from_fn(noop)) + .wrap(from_fn(print_range_header)) + .wrap(from_fn(mutate_body_type)) + .wrap(from_fn(mutate_body_type_with_extractors)) + .wrap(from_fn(timeout_10secs)) + // switch bool to true to observe early response + .wrap(MyMw(false).into_middleware()) + .wrap(Logger::default()) + .default_service(web::to(HttpResponse::Ok)) + }) + .workers(1) + .bind(bind)? + .run() + .await +} diff --git a/justfile b/justfile index 4ab2796c091..a970b62e74c 100644 --- a/justfile +++ b/justfile @@ -36,6 +36,9 @@ check-min: check-default: cargo hack --workspace check +# Run Clippy over workspace. +check toolchain="": && (clippy toolchain) + # Run Clippy over workspace. clippy toolchain="": cargo {{ toolchain }} clippy --workspace --all-targets {{ all_crate_features }}