ActixMiddleTechnical

Как группировать маршруты в 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.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics

Как группировать маршруты в Actix-web с помощью `web::scope()`? | Talanto