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:
15
middleware/various/Cargo.toml
Normal file
15
middleware/various/Cargo.toml
Normal 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"
|
36
middleware/various/README.md
Normal file
36
middleware/various/README.md
Normal 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).
|
51
middleware/various/src/main.rs
Normal file
51
middleware/various/src/main.rs
Normal 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
|
||||
}
|
74
middleware/various/src/read_request_body.rs
Normal file
74
middleware/various/src/read_request_body.rs
Normal 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)
|
||||
}
|
123
middleware/various/src/read_response_body.rs
Normal file
123
middleware/various/src/read_response_body.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
69
middleware/various/src/redirect.rs
Normal file
69
middleware/various/src/redirect.rs
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
63
middleware/various/src/simple.rs
Normal file
63
middleware/various/src/simple.rs
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user