Skip to content

Commit

Permalink
docs: add from_fn examples
Browse files Browse the repository at this point in the history
  • Loading branch information
robjtede committed Oct 15, 2024
1 parent ec05381 commit 03c65d9
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 0 deletions.
1 change: 1 addition & 0 deletions actix-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
128 changes: 128 additions & 0 deletions actix-web/examples/from_fn.rs
Original file line number Diff line number Diff line change
@@ -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<B>(req: ServiceRequest, next: Next<B>) -> Result<ServiceResponse<B>, Error> {
next.call(req).await
}

async fn print_range_header<B>(
range_header: Option<Header<Range>>,
req: ServiceRequest,
next: Next<B>,
) -> Result<ServiceResponse<B>, 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<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, 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<HashMap<String, String>>,
req: ServiceRequest,
next: Next<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, 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<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, 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<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, 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<S, B>(
self,
) -> impl Transform<
S,
ServiceRequest,
Response = ServiceResponse<impl MessageBody>,
Error = Error,
InitError = (),
>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, 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
}
3 changes: 3 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down

0 comments on commit 03c65d9

Please sign in to comment.