Как группировать маршруты в Actix-web с помощью web::scope()?
web::scope("/prefix") группирует маршруты с общим URL-префиксом и поддерживает вложенные scope, собственные middleware (.wrap()) и guard-ы; конфигурацию scope лучше выносить в отдельные функции по модулям.
Группировка маршрутов через web::scope()
web::scope(prefix) создаёт именованную группу маршрутов с общим URL-префиксом. Все маршруты внутри scope наследуют этот префикс, а также могут иметь собственные middleware, guard-ы и app_data, не затрагивающие остальное приложение.
Базовый пример
use actix_web::{web, App, HttpResponse, HttpServer, middleware};
async fn list_users() -> HttpResponse {
HttpResponse::Ok().json(vec!["alice", "bob"])
}
async fn get_user(path: web::Path<u64>) -> HttpResponse {
HttpResponse::Ok().body(format!("user {}", path.into_inner()))
}
async fn list_posts() -> HttpResponse {
HttpResponse::Ok().json(vec!["post1", "post2"])
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
// /api/v1/users и /api/v1/users/{id}
.service(
web::scope("/api/v1")
.service(
web::scope("/users")
.route("", web::get().to(list_users))
.route("/{id}", web::get().to(get_user)),
)
.service(
web::scope("/posts")
.route("", web::get().to(list_posts)),
),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Middleware на уровне scope
В отличие от App::wrap(), scope().wrap() применяет middleware только к маршрутам этой группы:
use actix_web::middleware::Logger;
web::scope("/api")
.wrap(Logger::new("%r %s %Dms")) // логируем только /api/*
.route("/health", web::get().to(|| async { HttpResponse::Ok() }))
Guard-ы на scope
Guard позволяет активировать scope только при выполнении условия — например, по заголовку Host (полезно для мультитенантности):
use actix_web::guard;
web::scope("/api")
.guard(guard::Header("X-Api-Version", "2"))
.route("/data", web::get().to(handler_v2))
Разбивка по модулям
Хорошая практика — выносить конфигурацию scope в отдельные функции-конфигураторы:
// users/routes.rs
pub fn users_scope() -> actix_web::Scope {
web::scope("/users")
.route("", web::get().to(list_users))
.route("", web::post().to(create_user))
.route("/{id}", web::get().to(get_user))
.route("/{id}", web::put().to(update_user))
.route("/{id}", web::delete().to(delete_user))
}
// main.rs
App::new().service(
web::scope("/api/v1")
.service(users_scope())
.service(posts_scope())
.service(auth_scope())
)
Использование web::resource() внутри scope
web::resource("/path") позволяет задать несколько методов для одного ресурса компактнее, чем несколько .route():
web::scope("/items")
.service(
web::resource("/{id}")
.route(web::get().to(get_item))
.route(web::put().to(update_item))
.route(web::delete().to(delete_item)),
)
Подводные камни
- Trailing slash:
web::scope("/api/v1")иweb::scope("/api/v1/")— разные префиксы; последний требует double-slash в URL (/api/v1//users). Никогда не добавляйте trailing slash в scope. - Порядок
.service()важен: Actix-web проверяет маршруты и scope в порядке регистрации; более специфичные ресурсы нужно регистрировать раньше общих wildcard-маршрутов. - Middleware в scope выполняется только для маршрутов этого scope; ошибочно ожидать, что аутентификационный middleware scope защитит маршруты верхнего уровня.
- Данные, добавленные через
scope.app_data(), перекрывают (shadow) данные того же типа с уровня App — это может приводить к неожиданному поведению при отладке. - Guard на scope, не пропустивший запрос, не генерирует 403 автоматически — Actix продолжает поиск других подходящих маршрутов, что может приводить к неожиданным 404.
- Нельзя добавлять маршруты к scope после его передачи в
.service()— scope иммутабелен после регистрации. - При использовании
actix-web-labили сторонних macro для роутинга убедитесь, что они совместимы с вашей версией actix-web; частая причина ошибок компиляции — mismatch версий.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.