Skip to content

Latest commit

 

History

History
262 lines (209 loc) · 8.27 KB

File metadata and controls

262 lines (209 loc) · 8.27 KB

Router Migration Guide

atomic_http v0.10.0부터 router 피처를 통해 radix trie 기반 라우터를 제공합니다. 기존의 수동 match 패턴을 대체하여 경로 파라미터 추출, HTTP 메서드 디스패치, 스코프 기반 그룹핑을 지원합니다.

설치

[dependencies]
atomic_http = { version = "0.10", features = ["router"] }

router 피처는 matchit 크레이트(~500 LOC, 의존성 없음)를 추가합니다.


Before / After

Before: 수동 match

use atomic_http::*;
use http::StatusCode;

#[tokio::main]
async fn main() -> Result<(), SendableError> {
    let mut server = Server::new("127.0.0.1:8080").await?;

    loop {
        let accept = server.accept().await?;
        tokio::spawn(async move {
            let (request, mut response) = accept.parse_request().await?;
            let path = request.uri().path();

            match path {
                "/" => {
                    response.body_mut().body = "Hello".into();
                    *response.status_mut() = StatusCode::OK;
                }
                "/users" => match request.method() {
                    &http::Method::GET => { /* list users */ }
                    &http::Method::POST => { /* create user */ }
                    _ => { *response.status_mut() = StatusCode::METHOD_NOT_ALLOWED; }
                },
                path if path.starts_with("/files/") => {
                    let filename = &path[7..]; // 수동 파싱
                    // serve file...
                }
                _ => {
                    *response.status_mut() = StatusCode::NOT_FOUND;
                }
            }

            response.responser().await?;
            Ok::<(), SendableError>(())
        });
    }
}

After: Router

use atomic_http::router::Router;
use atomic_http::*;
use http::StatusCode;

// 1. 라우트 enum 정의
enum Route { Home, ListUsers, CreateUser, ServeFile }

#[tokio::main]
async fn main() -> Result<(), SendableError> {
    // 2. 라우터 구성 (서버 시작 전, 한 번만)
    let router: &'static Router<Route> = Box::leak(Box::new(
        Router::new()
            .get("/", Route::Home)
            .get("/users", Route::ListUsers)
            .post("/users", Route::CreateUser)
            .get("/files/{*path}", Route::ServeFile)
    ));

    let mut server = Server::new("127.0.0.1:8080").await?;

    loop {
        let accept = server.accept().await?;
        tokio::spawn(async move {
            let (request, mut response) = accept.parse_request().await?;

            // 3. match 블록을 router.find()로 교체
            match router.find(request.method(), request.uri().path()) {
                Some(m) => match m.value {
                    Route::Home => {
                        response.body_mut().body = "Hello".into();
                        *response.status_mut() = StatusCode::OK;
                    }
                    Route::ListUsers => { /* list users */ }
                    Route::CreateUser => { /* create user */ }
                    Route::ServeFile => {
                        let filename = m.params.get("path").unwrap_or("");
                        // serve file — 수동 &path[7..] 불필요
                    }
                },
                None => {
                    *response.status_mut() = StatusCode::NOT_FOUND;
                }
            }

            response.responser().await?;
            Ok::<(), SendableError>(())
        });
    }
}

변경 요약

Before After
match path { "/foo" => ... } router.find(method, path)match m.value
path.starts_with("/files/") + &path[7..] .get("/files/{*path}", ...) + m.params.get("path")
경로 안에서 match request.method() .get(), .post() 등으로 메서드별 등록
404: _ => 매치 암 404: None => 매치 암
같은 경로 다른 메서드: 수동 분기 .get("/users", ...) .post("/users", ...) 자동 디스패치
prefix 그룹핑 불가 .scope("/api/v1", |s| s.get(...))

경로 패턴

// Named parameter — 다음 세그먼트까지 매칭
.get("/users/{id}", Route::GetUser)
// m.params.get("id") => Some("42")

// 여러 파라미터
.get("/orgs/{org}/repos/{repo}", Route::GetRepo)
// m.params.get("org"), m.params.get("repo")

// Catch-all — 경로 끝까지 전부 매칭
.get("/files/{*path}", Route::ServeFile)
// m.params.get("path") => Some("images/logo.png")

스코프 (그룹핑)

반복되는 prefix를 scope()로 묶을 수 있습니다:

let router = Router::new()
    .get("/", Route::Home)
    .scope("/api/v1", |s| s
        .scope("/users", |s| s
            .get("/", Route::ListUsers)       // → /api/v1/users/
            .get("/{id}", Route::GetUser)     // → /api/v1/users/{id}
            .post("/", Route::CreateUser)     // → /api/v1/users/
            .delete("/{id}", Route::DeleteUser) // → /api/v1/users/{id}
        )
        .scope("/files", |s| s
            .get("/{*path}", Route::ServeFile) // → /api/v1/files/{*path}
        )
    );

스코프는 순수 등록 시점의 편의 기능입니다. 내부적으로 모든 라우트는 flat한 radix trie에 저장되므로 런타임 오버헤드는 없습니다.


Arena 모드

Router는 body 타입에 의존하지 않으므로 Arena 모드에서도 동일하게 사용됩니다:

// Standard
let (request, mut response) = accept.parse_request().await?;
match router.find(request.method(), request.uri().path()) { ... }
response.responser().await?;

// Arena — 라우터 코드는 동일
let (request, mut response) = accept.parse_request_arena_writer().await?;
match router.find(request.method(), request.uri().path()) { ... }
response.responser_arena().await?;

WebSocket + Router

websocket 피처와 함께 사용할 때:

match accept.stream_parse().await? {
    StreamResult::WebSocket(ws_stream, request, peer) => {
        // WebSocket 라우팅 — request.uri().path()로 분기
        match router.find(&http::Method::GET, request.uri().path()) {
            Some(m) => match m.value {
                Route::WsEcho => { /* echo handler */ }
                Route::WsChat => { /* chat handler */ }
                _ => {}
            },
            None => { /* unknown ws path */ }
        }
    }
    StreamResult::Http(request, mut response) => {
        // HTTP 라우팅 — 동일
        match router.find(request.method(), request.uri().path()) { ... }
        response.responser().await?;
    }
}

변경되지 않는 것

  • Server::new(), server.accept() — 동일
  • accept.parse_request() / parse_request_arena_writer() — 동일
  • response.responser() / responser_arena() — 동일
  • response.body_mut(), response.status_mut() — 동일
  • RequestUtils (get_json, get_text, get_multi_part) — 동일
  • ResponseUtil (set_arena_json, response_file) — 동일
  • Connection pool, zero-copy cache — 동일

Router는 match 블록만 대체합니다. 나머지 API는 모두 그대로입니다.


insert() (동적 등록)

빌더 패턴 대신 런타임에 동적으로 라우트를 추가할 수 있습니다:

let mut router = Router::new();
router.insert(Method::GET, "/", Route::Home)?;
router.insert(Method::GET, "/users/{id}", Route::GetUser)?;
// InsertError 반환 — 충돌하는 패턴일 경우

API Reference

Method Description
Router::new() 빈 라우터 생성
.get(path, value) GET 라우트 등록
.post(path, value) POST 라우트 등록
.put(path, value) PUT 라우트 등록
.delete(path, value) DELETE 라우트 등록
.patch(path, value) PATCH 라우트 등록
.head(path, value) HEAD 라우트 등록
.options(path, value) OPTIONS 라우트 등록
.route(method, path, value) 임의 메서드 라우트 등록
.scope(prefix, closure) 공통 prefix 그룹핑
.insert(method, path, value) 동적 등록 (Result 반환)
.find(method, path) 라우트 매칭 (Option 반환)
Match.value 매칭된 값 참조
Match.params.get(key) 경로 파라미터 조회
Match.params.iter() 모든 파라미터 순회
Match.params.len() 파라미터 개수