actix / actix/actix-web

Overlapping scopes work differently than flat overlapping routes

Abierto
#2,904 8 comentarios 1 reacción 0 asignados Ver en GitHub
A-web C-docs
Lenguaje dominante
Rust
Estrellas
24.8k
Forks
1.9k
Merge medio
23 h 10 min
PR fusionados (30 d)
26

Descripción

## Expected Behavior

The docs seem to suggest that the `web::scope(...)` is a helper that groups different routes under the same prefix, but that otherwise the behaviour would be the same as with flattened routes. At least, I didn't see any mention of differences.

So, when I refactored my code from flat routes like in this minimal example:

```rust
use actix_web::{get, put, web, App, HttpServer, Responder};

#[get("/camera/dimensions")]
async fn camera_dimensions() -> impl Responder {
"800x600"
}

#[get("/camera/owner")]
async fn camera_owner() -> impl Responder {
"John Doe"
}

#[get("/focuser/distance")]
async fn focuser_focus() -> impl Responder {
"42"
}

#[put("/focuser/distance")]
async fn focuser_focus_set(value: web::Path) -> impl Responder {
format!("Set focus to {}", value)
}

#[get("/{device_type}/connected")]
async fn device_connected(device_type: web::Path) -> impl Responder {
format!("{} is connected", device_type)
}

#[put("/{device_type}/connect")]
async fn device_connect(device_type: web::Path) -> impl Responder {
format!("Connecting {}", device_type)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(camera_dimensions)
.service(camera_owner)
.service(focuser_focus)
.service(focuser_focus_set)
.service(device_connected)
.service(device_connect)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
```

to scoped groups like in this one:

```rust
use actix_web::{get, put, web, App, HttpServer, Responder};

#[get("/dimensions")]
async fn camera_dimensions() -> impl Responder {
"800x600"
}

#[get("/owner")]
async fn camera_owner() -> impl Responder {
"John Doe"
}

#[get("/distance")]
async fn focuser_focus() -> impl Responder {
"42"
}

#[put("/distance")]
async fn focuser_focus_set(value: web::Path) -> impl Responder {
format!("Set focus to {}", value)
}

#[get("/connected")]
async fn device_connected(device_type: web::Path) -> impl Responder {
format!("{} is connected", device_type)
}

#[put("/connect")]
async fn device_connect(device_type: web::Path) -> impl Responder {
format!("Connecting {}", device_type)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(
web::scope("/camera")
.service(camera_dimensions)
.service(camera_owner),
)
.service(
web::scope("/focuser")
.service(focuser_focus)
.service(focuser_focus_set),
)
.service(
web::scope("/{device_type}")
.service(device_connected)
.service(device_connect),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
```

I expected all routes to continue working like before.

## Current Behavior

Unfortunately, it seems that scopes are only matched against each other, and, once a scope is matched, no other scopes are tried even if it doesn't have the required method.

So, routes like `/camera/connected` and `/focuser/connected` worked before, but stop working after refactoring.

I thought reordering might help, putting the generic `/{device_type}` scope first, but that only seems to change which scope catches everything - now, routes like `/*/connected` work but specific device routes like `/camera/dimensions` don't.

Finally, I thought maybe I should use `/{device_type}` as `app.default_service()`, but that doesn't work either as `default_service()` seems to only accept a single handler and not a scope as its argument.

## Possible Solution

Flatten all routes at the app level when scopes are registered, so that there is no observable change in behaviour after grouping.

## Steps to Reproduce (for bugs)

Run the minimal examples provided above with suggested requests like `http://localhost:8080/camera/connected` and observe the working response "camera is connected" in the first implementation and 404 error in the 2nd one.

## Context

I'm trying to refactor my complex list of routes into grouped scopes in separate functions to make code a bit more maintainable.

## Your Environment

- Rust Version (I.e, output of `rustc -V`): rustc 1.63.0 (4b91a6ea7 2022-08-08)
- Actix Web Version: 4.2.1

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.