atomic_http v0.10.0부터 router 피처를 통해 radix trie 기반 라우터를 제공합니다. 기존의 수동 match 패턴을 대체하여 경로 파라미터 추출, HTTP 메서드 디스패치, 스코프 기반 그룹핑을 지원합니다.
[dependencies]
atomic_http = { version = "0.10", features = ["router"] }
router피처는matchit크레이트(~500 LOC, 의존성 없음)를 추가합니다.
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>(())
});
}
}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에 저장되므로 런타임 오버헤드는 없습니다.
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 피처와 함께 사용할 때:
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는 모두 그대로입니다.
빌더 패턴 대신 런타임에 동적으로 라우트를 추가할 수 있습니다:
let mut router = Router::new();
router.insert(Method::GET, "/", Route::Home)?;
router.insert(Method::GET, "/users/{id}", Route::GetUser)?;
// InsertError 반환 — 충돌하는 패턴일 경우| 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() |
파라미터 개수 |