Route Guards
Conditional routing based on headers or state.
Learning Objectives
- Filter routes based on Content-Type
##Deciding which handler gets the request
Sometimes two routes share the same path, and the choice depends on something other than the URL: the HTTP method, a header or a hostname. A guard is a small condition that a request must satisfy for a route to match.
#Built-in guards
Actix ships guards for methods, headers and hosts:
use actix_web::{guard, web, App, HttpResponse};
App::new()
.route("/", web::get().to(|| async { "GET" }))
.route("/", web::post().to(|| async { "POST" }))
.route(
"/json-only",
web::route()
.guard(guard::Header("content-type", "application/json"))
.to(|| async { "json" }),
);web::get() and web::post() are shortcuts that add the corresponding method guard.
#Host-based routing
Guards make it easy to serve different apps by domain:
use actix_web::{guard, web, App, HttpResponse, HttpServer};
HttpServer::new(|| {
App::new()
.service(
web::scope("")
.guard(guard::Host("api.example.com"))
.route("/", web::to(|| async { HttpResponse::Ok().body("api") })),
)
.service(
web::scope("")
.guard(guard::Host("www.example.com"))
.route("/", web::to(|| async { HttpResponse::Ok().body("website") })),
)
})#Combining guards
Guards can be combined with guard::All, guard::Any and guard::Not:
use actix_web::guard;
web::route().guard(guard::All(guard::Get()).and(guard::Header("x-internal", "1")))#Writing a custom guard
A guard is any type that implements the Guard trait. Here is one that only allows requests carrying a specific API key header:
use actix_web::guard::{Guard, GuardContext};
struct ApiKeyGuard;
impl Guard for ApiKeyGuard {
fn check(&self, ctx: &GuardContext<'_>) -> bool {
ctx.head()
.headers()
.get("x-api-key")
.map(|v| v == "secret")
.unwrap_or(false)
}
}
// web::route().guard(ApiKeyGuard).to(handler)#Guards versus middleware
A guard only answers "does this route match?" If not, Actix tries the next route and may end with a 404. Middleware wraps the request and can reject it with a custom response, such as 401. Use guards for routing decisions and middleware or extractors for authorization.
"What If?" Experiment
Learning by doing. Modify the code to see what happens.
See what a failing guard returns.
Call the protected route without the header.
A 404 Not Found, not a 401.
Explanation: A guard only decides whether a route matches. It never writes an error response itself.
Common Mistakes
Anticipate and avoid these frequent pitfalls when working with this concept.
Relying on a guard for authentication
A failed guard just means "no match", so clients see 404 instead of 401.
Explanation: Authentication needs a proper rejection response, which middleware or a custom extractor can provide.
Ordering routes so a broad one wins
The first route that matches handles the request.
Explanation: Put the narrowest routes first so they get a chance to match before the general one.
Practical Exercise
Write a header guard
TaskCreate a route that only matches when the x-api-key header equals "secret".
Requirements
- Implement the Guard trait
- Return true only for the correct header value
- Register it on a route
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
Guards power virtual-host setups (api.example.com versus www.example.com) and content negotiation between JSON and form endpoints on the same path.
Key Takeaways
- guard::Get(), Header() and Host() are built in; web::get() adds a method guard for you.
- A custom guard implements the Guard trait and returns a bool.
- Guards decide whether a route matches; they do not produce error responses.
- Use middleware or extractors for authentication and authorization.
Finished reading?
Mark this lesson as done to track your progress and earn XP.