Trait Middleware
pub trait Middleware:
Send
+ Sync
+ 'static {
// Required method
fn handle(
&self,
request: Request<SendBody<'_>>,
next: MiddlewareNext<'_>,
) -> Result<Response<Body>, Error> ⓘ;
}
Available on crate feature
dep_ureq
only.Expand description
Chained processing of request (and response).
§Middleware as fn
The middleware trait is implemented for all functions that have the signature
Fn(Request, MiddlewareNext) -> Result<Response, Error>
That means the easiest way to implement middleware is by providing a fn
, like so
use ureq::{Body, SendBody};
use ureq::middleware::MiddlewareNext;
use ureq::http::{Request, Response};
fn my_middleware(req: Request<SendBody>, next: MiddlewareNext)
-> Result<Response<Body>, ureq::Error> {
// do middleware things to request
// continue the middleware chain
let res = next.handle(req)?;
// do middleware things to response
Ok(res)
}
§Adding headers
A common use case is to add headers to the outgoing request. Here an example of how.
use ureq::{Body, SendBody, Agent, config::Config};
use ureq::middleware::MiddlewareNext;
use ureq::http::{Request, Response, header::HeaderValue};
fn my_middleware(mut req: Request<SendBody>, next: MiddlewareNext)
-> Result<Response<Body>, ureq::Error> {
req.headers_mut().insert("X-My-Header", HeaderValue::from_static("value_42"));
// set my bespoke header and continue the chain
next.handle(req)
}
let mut config = Config::builder()
.middleware(my_middleware)
.build();
let agent: Agent = config.into();
let result: serde_json::Value =
agent.get("http://httpbin.org/headers").call()?.body_mut().read_json()?;
assert_eq!(&result["headers"]["X-My-Header"], "value_42");
§State
To maintain state between middleware invocations, we need to do something more elaborate than
the simple fn
and implement the Middleware
trait directly.
§Example with mutex lock
In the examples
directory there is an additional example count-bytes.rs
which uses
a mutex lock like shown below.
use std::sync::{Arc, Mutex};
use ureq::{Body, SendBody};
use ureq::middleware::{Middleware, MiddlewareNext};
use ureq::http::{Request, Response};
struct MyState {
// whatever is needed
}
struct MyMiddleware(Arc<Mutex<MyState>>);
impl Middleware for MyMiddleware {
fn handle(&self, request: Request<SendBody>, next: MiddlewareNext)
-> Result<Response<Body>, ureq::Error> {
// These extra brackets ensures we release the Mutex lock before continuing the
// chain. There could also be scenarios where we want to maintain the lock through
// the invocation, which would block other requests from proceeding concurrently
// through the middleware.
{
let mut state = self.0.lock().unwrap();
// do stuff with state
}
// continue middleware chain
next.handle(request)
}
}
§Example with atomic
This example shows how we can increase a counter for each request going through the agent.
use ureq::{Body, SendBody, Agent, config::Config};
use ureq::middleware::{Middleware, MiddlewareNext};
use ureq::http::{Request, Response};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
// Middleware that stores a counter state. This example uses an AtomicU64
// since the middleware is potentially shared by multiple threads running
// requests at the same time.
struct MyCounter(Arc<AtomicU64>);
impl Middleware for MyCounter {
fn handle(&self, req: Request<SendBody>, next: MiddlewareNext)
-> Result<Response<Body>, ureq::Error> {
// increase the counter for each invocation
self.0.fetch_add(1, Ordering::Relaxed);
// continue the middleware chain
next.handle(req)
}
}
let shared_counter = Arc::new(AtomicU64::new(0));
let mut config = Config::builder()
.middleware(MyCounter(shared_counter.clone()))
.build();
let agent: Agent = config.into();
agent.get("http://httpbin.org/get").call()?;
agent.get("http://httpbin.org/get").call()?;
// Check we did indeed increase the counter twice.
assert_eq!(shared_counter.load(Ordering::Relaxed), 2);