actix-web url dispatch and middleware

This commit is contained in:
Kaan Barmore-Genç 2022-04-24 03:51:22 -04:00
parent 2dca209684
commit 44d6a24fc8
No known key found for this signature in database
GPG Key ID: 362529CF3A326F16
2 changed files with 77 additions and 0 deletions

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"Actix"
]
}

View File

@ -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<String>) -> 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<String>) -> 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)
```