actix / actix/actix-web

Overlapping scopes work differently than flat overlapping routes

Aperta
#2,904 8 commenti 1 reazione 0 assegnatari Vedi su GitHub
A-web C-docs
Lingua principale
Rust
Stelle
24.8k
Fork
1.9k
Merge medio
23h 10m
PR unite (30g)
26

Descrizione

## 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

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.