1
0
mirror of https://github.com/actix/examples synced 2025-06-26 17:17:42 +02:00

chore: rename middleware dirs

This commit is contained in:
Rob Ede
2023-10-29 23:51:05 +00:00
parent 5d36d72976
commit 0de1c02762
21 changed files with 43 additions and 38 deletions

View File

@ -0,0 +1,15 @@
[package]
name = "middleware-various"
version = "1.0.0"
publish.workspace = true
edition.workspace = true
[dependencies]
actix-http.workspace = true
actix-web.workspace = true
actix-web-lab.workspace = true
env_logger.workspace = true
futures-util.workspace = true
log.workspace = true
pin-project = "1"

View File

@ -0,0 +1,36 @@
# Middleware: Various
This example showcases a bunch of different uses of middleware.
See also the [Middleware guide](https://actix.rs/docs/middleware).
## Usage
```sh
cd middleware/various
cargo run
```
Look in `src/main.rs` and comment the different middleware in/out to see how they function.
## Middleware
### `redirect::CheckLogin`
A middleware implementing a request guard which sketches a rough approximation of what a login could look like.
### `read_request_body::Logging`
A middleware demonstrating how to read out the incoming request body.
### `read_response_body::Logging`
A middleware demonstrating how to read out the outgoing response body.
### `simple::SayHi`
A minimal middleware demonstrating the sequence of operations in an actix middleware. There is a second version of the same middleware using `wrap_fn` which shows how easily a middleware can be implemented in actix.
## See Also
- The `from_fn` middleware constructor from [`actix-web-lab`](https://crates.io/crates/actix-web-lab).

View File

@ -0,0 +1,51 @@
use std::time::Duration;
use actix_http::body::MessageBody;
use actix_web::{dev, rt::time, web, App, Error, HttpServer};
use actix_web_lab::middleware::{from_fn, Next};
mod read_request_body;
mod read_response_body;
mod redirect;
mod simple;
// See more examples of from_fn middleware here:
// https://github.com/robjtede/actix-web-lab/blob/main/actix-web-lab/examples/from_fn.rs
async fn timeout_10secs(
req: dev::ServiceRequest,
next: Next<impl MessageBody + 'static>,
) -> Result<dev::ServiceResponse<impl MessageBody>, Error> {
match time::timeout(Duration::from_secs(10), next.call(req)).await {
Ok(res) => res,
Err(_err) => Err(actix_web::error::ErrorRequestTimeout("")),
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
log::info!("starting HTTP server at http://localhost:8080");
HttpServer::new(|| {
App::new()
.wrap(redirect::CheckLogin)
.wrap(read_request_body::Logging)
.wrap(read_response_body::Logging)
.wrap(simple::SayHi)
.wrap(from_fn(timeout_10secs))
.service(web::resource("/login").to(|body: String| async move {
println!("request body (handler): {body}");
"You are on /login. Go to src/redirect.rs to change this behavior."
}))
.service(
web::resource("/").to(|| async {
"Hello, middleware! Check the console where the server is run."
}),
)
})
.bind(("127.0.0.1", 8080))?
.workers(1)
.run()
.await
}

View File

@ -0,0 +1,74 @@
use std::{
future::{ready, Ready},
rc::Rc,
};
use actix_http::h1;
use actix_web::{
dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
web, Error,
};
use futures_util::future::LocalBoxFuture;
pub struct Logging;
impl<S: 'static, B> Transform<S, ServiceRequest> for Logging
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = LoggingMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(LoggingMiddleware {
service: Rc::new(service),
}))
}
}
pub struct LoggingMiddleware<S> {
// This is special: We need this to avoid lifetime issues.
service: Rc<S>,
}
impl<S, B> Service<ServiceRequest> for LoggingMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
dev::forward_ready!(service);
fn call(&self, mut req: ServiceRequest) -> Self::Future {
let svc = self.service.clone();
Box::pin(async move {
// extract bytes from request body
let body = req.extract::<web::Bytes>().await.unwrap();
println!("request body (middleware): {body:?}");
// re-insert body back into request to be used by handlers
req.set_payload(bytes_to_payload(body));
let res = svc.call(req).await?;
println!("response: {:?}", res.headers());
Ok(res)
})
}
}
fn bytes_to_payload(buf: web::Bytes) -> dev::Payload {
let (_, mut pl) = h1::Payload::create(true);
pl.unread_data(buf);
dev::Payload::from(pl)
}

View File

@ -0,0 +1,123 @@
use std::{
future::{ready, Future, Ready},
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use actix_web::{
body::{BodySize, MessageBody},
dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
web::{Bytes, BytesMut},
Error,
};
pub struct Logging;
impl<S: 'static, B> Transform<S, ServiceRequest> for Logging
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody + 'static,
{
type Response = ServiceResponse<BodyLogger<B>>;
type Error = Error;
type InitError = ();
type Transform = LoggingMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(LoggingMiddleware { service }))
}
}
pub struct LoggingMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for LoggingMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody,
{
type Response = ServiceResponse<BodyLogger<B>>;
type Error = Error;
type Future = WrapperStream<S, B>;
dev::forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
WrapperStream {
fut: self.service.call(req),
_t: PhantomData,
}
}
}
#[pin_project::pin_project]
pub struct WrapperStream<S, B>
where
B: MessageBody,
S: Service<ServiceRequest>,
{
#[pin]
fut: S::Future,
_t: PhantomData<(B,)>,
}
impl<S, B> Future for WrapperStream<S, B>
where
B: MessageBody,
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{
type Output = Result<ServiceResponse<BodyLogger<B>>, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = futures_util::ready!(self.project().fut.poll(cx));
Poll::Ready(res.map(|res| {
res.map_body(move |_, body| BodyLogger {
body,
body_accum: BytesMut::new(),
})
}))
}
}
#[pin_project::pin_project(PinnedDrop)]
pub struct BodyLogger<B> {
#[pin]
body: B,
body_accum: BytesMut,
}
#[pin_project::pinned_drop]
impl<B> PinnedDrop for BodyLogger<B> {
fn drop(self: Pin<&mut Self>) {
println!("response body: {:?}", self.body_accum);
}
}
impl<B: MessageBody> MessageBody for BodyLogger<B> {
type Error = B::Error;
fn size(&self) -> BodySize {
self.body.size()
}
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
let this = self.project();
match this.body.poll_next(cx) {
Poll::Ready(Some(Ok(chunk))) => {
this.body_accum.extend_from_slice(&chunk);
Poll::Ready(Some(Ok(chunk)))
}
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
}

View File

@ -0,0 +1,69 @@
use std::future::{ready, Ready};
use actix_web::{
body::EitherBody,
dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
http, Error, HttpResponse,
};
use futures_util::future::LocalBoxFuture;
pub struct CheckLogin;
impl<S, B> Transform<S, ServiceRequest> for CheckLogin
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type InitError = ();
type Transform = CheckLoginMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(CheckLoginMiddleware { service }))
}
}
pub struct CheckLoginMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for CheckLoginMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
dev::forward_ready!(service);
fn call(&self, request: ServiceRequest) -> Self::Future {
// Change this to see the change in outcome in the browser.
// Usually this boolean would be acquired from a password check or other auth verification.
let is_logged_in = false;
// Don't forward to `/login` if we are already on `/login`.
if !is_logged_in && request.path() != "/login" {
let (request, _pl) = request.into_parts();
let response = HttpResponse::Found()
.insert_header((http::header::LOCATION, "/login"))
.finish()
// constructed responses map to "right" body
.map_into_right_body();
return Box::pin(async { Ok(ServiceResponse::new(request, response)) });
}
let res = self.service.call(request);
Box::pin(async move {
// forwarded responses map to "left" body
res.await.map(ServiceResponse::map_into_left_body)
})
}
}

View File

@ -0,0 +1,63 @@
use std::future::{ready, Ready};
use actix_web::{
dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
use futures_util::future::LocalBoxFuture;
// 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<S, B> Transform<S, ServiceRequest> for SayHi
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = SayHiMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(SayHiMiddleware { service }))
}
}
pub struct SayHiMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for SayHiMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
dev::forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
println!("Hi from start. You requested: {}", req.path());
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
println!("Hi from response");
Ok(res)
})
}
}