Adding Middleware
Logging and CORS.
Learning Objectives
- Add the Logger middleware
##Code that runs around every request
Some logic belongs to every request, not to one handler: logging, compression, authentication, CORS headers, request timing. Copying it into each handler would be tedious and error prone. Middleware wraps your handlers so that logic runs in one place.
#The onion model
Think of middleware as layers of an onion. A request travels inward through each layer to the handler, and the response travels back out through the same layers in reverse order. Each layer can inspect or change the request on the way in, and the response on the way out.
#Built-in middleware
Add middleware with .wrap(). These two ship with Actix Web:
use actix_web::{middleware, App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "info");
env_logger::init();
HttpServer::new(|| {
App::new()
.wrap(middleware::Logger::default())
.wrap(middleware::Compress::default())
// .service(...)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}Logger prints one line for each request. It needs the env_logger crate (or any other log backend) and the RUST_LOG variable. Compress automatically gzips responses when the client supports it.
#Order matters
The middleware registered last runs first on an incoming request. If you wrap A and then B, a request goes through B, then A, then your handler, and the response returns through A, then B. Put things like logging at the end of the chain so it sees every request.
#Scoping middleware
You do not have to apply a layer to the whole app. Attach it to a scope to protect only part of the API:
use actix_web::web;
App::new().service(
web::scope("/admin")
.wrap(middleware::Logger::default())
// .route(...)
);#Writing your own
The simplest custom middleware is a function with from_fn:
use actix_web::{
body::MessageBody,
dev::{ServiceRequest, ServiceResponse},
middleware::{from_fn, Next},
Error,
};
async fn timing(
req: ServiceRequest,
next: Next<impl MessageBody>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
let start = std::time::Instant::now();
let res = next.call(req).await?;
println!("request took {:?}", start.elapsed());
Ok(res)
}
// App::new().wrap(from_fn(timing))Code before next.call(req) runs on the way in, and code after it runs on the way out.
Interactive Code Inspector
Select any highlighted line of code below to deeply inspect its mechanics, understand why it exists, and what happens if you change it.
"What If?" Experiment
Learning by doing. Modify the code to see what happens.
See middleware ordering.
Add a println! at the start of each middleware and send one request.
b prints first and a second on the way in, then they return in the opposite order.
Explanation: The last layer registered is the outermost, so it handles the request first and the response last.
Common Mistakes
Anticipate and avoid these frequent pitfalls when working with this concept.
Logger prints nothing
Logger only emits log records; without a log backend and RUST_LOG nothing is displayed.
Explanation: Initialize a log backend such as env_logger before starting the server so the log lines are actually printed.
Assuming middleware runs in registration order
Actix wraps each new layer around the previous ones, so the newest is outermost.
Explanation: The last wrap() is the outermost layer and sees the request first. Register in the reverse of the order you want requests to flow.
Practical Exercise
Time every request
TaskWrite a from_fn middleware that prints how long each request took and register it on the App.
Requirements
- Use middleware::from_fn
- Measure with Instant before and after next.call
- Print the elapsed time
Need a hint?
Try solving the exercise in your local environment first. Once you have a solution, or if you get completely stuck, reveal the answer.
Real-World Application
Production services typically stack request-ID, logging, metrics, compression, CORS and authentication middleware, with the auth layer applied only to the protected scopes.
Key Takeaways
- wrap() adds middleware; the last one registered runs first on the way in.
- Logger and Compress are built in and cover most simple needs.
- Middleware can be applied to the whole App or just a web::scope.
- from_fn is the easiest way to write your own middleware.
Finished reading?
Mark this lesson as done to track your progress and earn XP.