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