diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..dbb27a9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "Actix" + ] +} \ No newline at end of file diff --git a/content/posts/2022.04.24.actix-web-url-dispatch-and-middleware.md b/content/posts/2022.04.24.actix-web-url-dispatch-and-middleware.md new file mode 100644 index 0000000..f483ce8 --- /dev/null +++ b/content/posts/2022.04.24.actix-web-url-dispatch-and-middleware.md @@ -0,0 +1,72 @@ +--- +title: "actix-web Url Dispatch and Middleware" +date: 2022-04-24T03:37:47-04:00 +draft: false +toc: false +images: +tags: + - dev + - rust +--- + +I've hit an issue with `actix-web` recently, and ended up learning a bit about +how it does routing (or URL dispatch as they name it). + +Here's a simplified version of my problem: + +```rust +// service code +#[get("/{path:.*}")] +pub async fn basic_handler(params: web::Path) -> HttpResponse { + // ... +} + +#[post("/auth/")] +pub async fn auth_handler() -> HttpResponse { + // ... +} + +// in main +let auth_service = web::scope("") + .wrap(auth_middleware) + .service(auth_handler); +App::new() + .service(authenticated_scope) + .service(basic_handler) +``` + +`auth_middleware` is a custom middleware I wrote which checks for the existence +of an auth token in a header or cookie, then validates the token. The middleware +responds early with a 401 if the token is missing, which ensures that the +protected handlers can only be reached by authenticated users. + +I expected Actix to realize that if a request doesn't match `/auth/`, it should +go to the `basic_handler` and the authentication middleware shouldn't run. But +the middleware did run even if the path had nothing to do with `/auth/`! That +would cause the middleware to respond with a 401 and stops it from propagating, +so it never reached `basic_handler`. + +The solution, it turns out, is using the `web::scope` to scope out the +authenticated routes. If the scope doesn't match, Actix then seems to skip over +that entire scope and never runs the middleware. Here's the same code, fixed: + +```rust +// service code +#[get("/{path:.*}")] +pub async fn basic_handler(params: web::Path) -> HttpResponse { + // ... +} + +#[post("/")] // <-- change here +pub async fn auth_handler() -> HttpResponse { + // ... +} + +// in main +let auth_service = web::scope("/auth") // <-- change here + .wrap(auth_middleware) + .service(auth_handler); +App::new() + .service(authenticated_scope) + .service(basic_handler) +```