ActixMiddleTechnical

Как Actix-web обрабатывает конфликты маршрутов и каков приоритет сопоставления маршрутов?

Actix-web применяет маршруты в порядке регистрации — первый совпавший побеждает. Статические сегменты должны регистрироваться перед параметрическими (/users/me до /users/{id}), иначе специфичный маршрут никогда не будет достигнут.

Приоритет маршрутов и конфликты в Actix-web

Actix-web использует детерминированный алгоритм сопоставления маршрутов, основанный на порядке регистрации и специфичности паттерна. В отличие от некоторых других фреймворков, здесь нет автоматического ранжирования по «точности» — важен порядок, в котором маршруты добавлены.

Базовое правило: первый совпавший побеждает

use actix_web::{web, App, HttpResponse, HttpServer};

async fn specific() -> HttpResponse {
    HttpResponse::Ok().body("specific route")
}

async fn dynamic(path: web::Path<String>) -> HttpResponse {
    HttpResponse::Ok().body(format!("dynamic: {}", path))
}

// Правильный порядок: более специфичные маршруты сначала
App::new()
    .route("/users/me", web::get().to(specific))   // 1. статический
    .route("/users/{id}", web::get().to(dynamic))  // 2. с параметром

Scope и вложенные маршруты

Маршруты внутри web::scope() проверяются в порядке добавления. Scope сам сопоставляется первым по префиксу:

App::new()
    .service(
        web::scope("/api/v1")
            .route("/health", web::get().to(health))      // /api/v1/health
            .route("/jobs/facets", web::get().to(facets)) // /api/v1/jobs/facets
            .route("/jobs/{id}", web::get().to(job))      // /api/v1/jobs/{id}
    )
    .service(
        web::scope("/api/v2")
            .route("/jobs", web::get().to(jobs_v2))
    )

Статические сегменты vs параметры vs wildcards

Actix-web приоритизирует сопоставление следующим образом:

  • Точное совпадение (/users/me) — высший приоритет в пределах того же scope
  • Параметр (/users/{id}) — совпадает с любым непустым сегментом без /
  • Хвостовой wildcard (/files/{path:.*}) — совпадает со всем оставшимся путём включая /

Пример конфликта и его решение

// НЕПРАВИЛЬНО: /users/me никогда не будет достигнут
App::new()
    .route("/users/{id}", web::get().to(dynamic)) // поглощает /users/me
    .route("/users/me", web::get().to(specific))  // мёртвый маршрут

// ПРАВИЛЬНО: специфичный маршрут первым
App::new()
    .route("/users/me", web::get().to(specific))  // проверяется первым
    .route("/users/{id}", web::get().to(dynamic)) // для остальных

Resource vs route — использование web::resource()

// web::resource позволяет навешивать несколько методов на один путь
App::new()
    .service(
        web::resource("/users/{id}")
            .route(web::get().to(get_user))
            .route(web::put().to(update_user))
            .route(web::delete().to(delete_user))
    )

Хвостовые wildcards

// {tail:.*} совпадает с любым суффиксом включая /
async fn serve_file(path: web::Path<String>) -> HttpResponse {
    HttpResponse::Ok().body(format!("file: {}", path))
}

App::new()
    .route("/static/{tail:.*}", web::get().to(serve_file))
    // /static/css/main.css → path = "css/main.css"

Регулярные выражения в параметрах

// Параметр только для числовых ID
App::new()
    .route("/users/{id:\\d+}", web::get().to(get_user_by_id))
    .route("/users/{slug}", web::get().to(get_user_by_slug))

Подводные камни

  • Порядок не очевиден при рефакторинге — перестановка маршрутов при добавлении нового может молча сломать уже работающий маршрут.
  • scope() с trailing slashweb::scope("/api") и web::scope("/api/") ведут себя по-разному; первый совпадает с /api и /api/..., второй требует строгий слэш.
  • Параметры не совпадают через /{id} не захватит a/b; для этого нужен {path:.*}.
  • Несколько методов через route() без resource() — при использовании нескольких .route() с одним путём, но разными методами, они регистрируются как независимые маршруты; конфликтов нет, но это менее эффективно, чем web::resource().
  • Маршруты из разных плагинов/модулей — при динамической сборке App из нескольких модулей порядок добавления определяется порядком вызовов; неочевидные конфликты трудно отлаживать.
  • Отсутствие предупреждений о недостижимых маршрутах — Actix-web не предупреждает о мёртвых маршрутах; единственный способ обнаружить — тесты или внимательный code review.

Common mistakes

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

What the interviewer is testing

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics