Extract path arguments in middleware before processing the request
As per https://github.com/actix/actix-web/issues/1784#issue-739668855, the path arguments in middleware are not accessible until after the request is processed.
Expected Behavior
actix_web::web::Path::<String>::from_request(request, payload) in a service call returns path argument(s) at any point in the service call.
Current Behavior
Output of request.match_info(), request.match_pattern() and actix_web::web::Path::<String>::from_request(request, payload) from inside the service call: "wrong number of parameters: 0 expected 1".
Steps to Reproduce (for bugs)
- Implement a middleware service, with the Path extractor
- Call the middleware on a route which takes a path argument
Context
I am trying to implement guards on the URL path to ensure that users can only access what they are allowed to access. Only being able to access the patch arguments after the request means that it is not possible to use this method for endpoints that update information
Your Environment
Rust Version: 1.50.0
Actix Web Version: 3.3.2
Wasn't able to reproduce. Make sure you've set your wrapper under a scope to capture parameters. my test code:
use actix_web::dev::{ResourceDef, Service};
use actix_web::{error, web, App, HttpResponse, HttpServer};
use actix_web_codegen::{get, post};
#[get("/")]
async fn dummy() -> &'static str {
"OK\n"
}
#[actix_web::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let server = HttpServer::new(move || {
App::new().service(
web::scope("/{uid}") // <= Make sure you have set the upper scope
.wrap_fn(|req, srv| {
dbg!(req.match_info().get("uid")); // <= Works as expected!
srv.call(req)
})
.service(dummy),
)
})
.bind("127.0.0.1:7311")?
.run()?
.await?;
Ok(())
}
Sorry, I am talking middleware service added via App::new().wrap(Middleware::new())
If you want to access a path argument in middleware, you you should add the middleware to a scope that defines such argument (as in the example above), not to the app root.
After having a play, if I have to use scopes to access the path in the middleware, then I don't think what I want to do will work.
Basically I want the middleware to check every {uid} to ensure it is valid. IE the whole path might be /user/{uid} or /company/{uid} and the same middleware checks both. Is there another way?
Using the examples you gave, you have many options: You can add the same middleware to multiple scopes. You can also merge these scopes into one: scope("/{domain:user|company}/{uid}") or, if you want more fine-grain contol you can add the middleware to every resource individually:
App::new().service(
web::resource("/company/{uid}/say-hello")
.wrap(MyWrapper::new())
.to(say_hello_handler),
)
The rule still applies: "If you want to use a path parameter in a middleware, the parameter should've been defined before"
I hope this helped?
Any update on this issue? I am trying to build a middleware that checks for path parameters named storage and repository
If they are available add them to the extensions. But currently. match_info is running this
[api::authentication::middleware] Trace: Path { path: Url { uri: /api/admin/repositories/public, path: None }, skip: 4, segments: []}