Axum 및 Actix Web에서 사용자 정의 추출기 생성
Min-jun Kim
Dev Intern · Leapcell

사용자 정의 추출기로 요청 데이터 잠금 해제
Rust 웹 개발의 세계에서 들어오는 HTTP 요청에서 특정 정보를 추출하는 것은 기본적이고 자주 수행되는 작업입니다. 사용자 인증 토큰, 구문 분석된 JSON 본문 또는 쿼리 매개변수 등 이 데이터에 깔끔하고 인체공학적으로 액세스하는 기능은 애플리케이션 핸들러의 가독성과 유지보수성에 상당한 영향을 미칩니다. Rust의 두 가지 인기 있는 웹 프레임워크인 Axum과 Actix Web은 강력한 메커니즘을 제공합니다. 바로 FromRequest 트레이트입니다. 이 트레이트는 사용자 정의 추출기를 구축하는 초석이며, 개발자가 복잡한 데이터 추출 논리를 재사용 가능하고 관용적인 형식으로 캡슐화할 수 있습니다.
FromRequest를 이해하고 활용하면 상용구 코드를 넘어 고도로 표현력이 뛰어나고 관리 가능한 코드를 작성하여 더 깨끗하고 강력한 웹 API를 작성할 수 있습니다.
FromRequest의 힘
자체 추출기를 만들기 전에 몇 가지 핵심 개념을 명확히 이해해 봅시다.
- 추출기: 웹 프레임워크의 맥락에서 추출기는 HTTP 요청에서 자동으로 생성될 수 있도록 하는 특정 트레이트(
FromRequest등)를 구현하는 유형입니다. 이 프로세스는 핸들러 함수가 호출되기 전에 발생합니다. - 비동기 컨텍스트: Axum과 Actix Web은 모두 비동기 프레임워크입니다. 이는 요청에서 데이터를 추출하는 작업에 종종
await작업(예: 요청 본문 읽기)이 포함된다는 것을 의미합니다. 따라서FromRequest구현은 일반적으로Future를 반환하는 자체async함수가 됩니다. - 오류 처리: 추출기는 실패할 수 있습니다. 예를 들어, 필수 헤더가 누락되었거나 JSON 본문이 잘못 형식된 경우입니다. 강력한
FromRequest구현은 이러한 추출 실패가 어떻게 처리되고 어떤 종류의 오류 응답이 클라이언트에게 반환되는지 정의해야 합니다.
FromRequest 트레이트는 Axum과 Actix Web이 핸들러 함수에 구조화된 데이터를 원활하게 제공하는 방식의 핵심입니다. 요청이 도착하면 프레임워크는 핸들러의 인수 유형을 검사합니다. 인수의 유형이 FromRequest를 구현하는 경우 프레임워크는 from_request 메서드를 호출하여 들어오는 요청의 컨텍스트를 전달합니다. 그런 다음 이 메서드는 인수 유형의 인스턴스를 생성하려고 시도합니다. 성공하면 해당 인스턴스가 핸들러에 전달됩니다. 실패하면 핸들러가 전혀 호출되지 않고 적절한 오류 응답이 클라이언트에게 반환됩니다. 이 메커니즘은 관심사의 명확한 분리를 촉진하여 핸들러가 순전히 비즈니스 로직에 집중하도록 합니다.
Axum에서 사용자 정의 추출기 빌드
모든 요청에서 "Client-ID" 헤더를 추출하고 유효한 UUID인지 확인한다고 가정해 보겠습니다. 사용자 정의 ClientId 추출기를 만들 것입니다.
// Axum 예제 use axum::{ async_trait, extract::{FromRequestParts, Request}, http::request::Parts, response::{IntoResponse, Response}, Json, Router, }; use std::fmt; use uuid::Uuid; // ClientId 추출 실패를 위한 사용자 정의 오류 유형 #[derive(Debug)] struct ClientIdError; impl fmt::Display for ClientIdError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Invalid or missing Client-ID header") } } // 오류를 HTTP 응답으로 변환 impl IntoResponse for ClientIdError { fn into_response(self) -> Response { (StatusCode::BAD_REQUEST, "Missing or invalid Client-ID header").into_response() } } // 사용자 정의 추출기 구조체 #[derive(Debug)] pub struct ClientId(pub Uuid); // ClientId에 대한 FromRequestParts 구현 // Axum의 FromRequestParts는 요청 헤더, URI 등에서 데이터를 추출하는 데 사용됩니다. // FromRequest는 본문 또는 요청을 소비하는 기타 비동기 작업에서 추출하는 데 사용됩니다. #[async_trait] impl FromRequestParts for ClientId { type Rejection = ClientIdError; async fn from_request_parts(parts: &mut Parts, _state: &()) -> Result<Self, Self::Rejection> { let headers = &parts.headers; let client_id_header = headers .get("Client-ID") .ok_or(ClientIdError)? // 헤더가 누락되면 오류 발생 .to_str() .map_err(|_| ClientIdError)?; // 헤더가 유효한 UTF-8이 아니면 오류 발생 let client_id = Uuid::parse_str(client_id_header).map_err(|_| ClientIdError)?; // 유효한 UUID가 아니면 오류 발생 Ok(ClientId(client_id)) } } // 사용자 정의 추출기를 사용하는 예제 핸들러 async fn handle_request_with_client_id(ClientId(client_id): ClientId) -> Json<String> { Json(format!("Hello from client: {}", client_id)) } #[tokio::main] async fn main() { let app = Router::new().route("/hello", axum::routing::get(handle_request_with_client_id)); println!("Axum server running on http://127.0.0.1:3000"); axum::Server::bind(&"0.0.0.1:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); }
Axum 예제에서는 요청의 일부(헤더)만 보고 요청 본문을 소비하지 않으므로 FromRequestParts를 구현합니다. JSON 본문 구문을 분석하는 경우 대신 FromRequest를 구현합니다. ClientIdError가 IntoResponse를 구현하면 Axum이 오류 추출 실패를 적절한 HTTP 응답으로 자동 변환할 수 있다는 데 주목하십시오.
Actix Web에서 사용자 정의 추출기 빌드
이제 Actix Web에 대한 유사한 ClientId 추출기를 구현해 보겠습니다. Actix Web의 FromRequest 트레이트는 헤더 및 잠재적인 비동기 작업 모두를 직접 처리합니다.
// Actix Web 예제 use actix_web::{ dev::Payload, error::ResponseError, http::{header, StatusCode}, web, App, FromRequest, HttpRequest, HttpResponse, HttpServer, }; use futures::future::{ok, Ready}; use std::{fmt, ops::Deref}; use uuid::Uuid; // ClientId 추출 실패를 위한 사용자 정의 오류 유형 #[derive(Debug)] struct ClientIdError; impl fmt::Display for ClientIdError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Invalid or missing Client-ID header") } } // 오류를 HTTP 응답으로 변환 impl ResponseError for ClientIdError { fn error_response(&self) -> HttpResponse { HttpResponse::build(self.status_code()) .body(self.to_string()) } fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } } // 사용자 정의 추출기 구조체 #[derive(Debug)] pub struct ClientId(pub Uuid); // 내부 Uuid에 쉽게 액세스하기 위해 Deref 구현 impl Deref for ClientId { type Target = Uuid; fn deref(&self) -> &Self::Target { &self.0 } } // ClientId에 대한 FromRequest 구현 // Actix Web의 FromRequest는 헤더 및 본문 추출 모두에 작동합니다. impl FromRequest for ClientId { type Error = ClientIdError; type Future = Ready<Result<Self, Self::Error>>; type Config = (); fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future { let client_id_header = req.headers().get("Client-ID"); let result = match client_id_header { Some(header_value) => { header_value .to_str() .ok() .and_then(|s| Uuid::parse_str(s).ok()) .map(|uuid| ClientId(uuid)) .ok_or(ClientIdError) } None => Err(ClientIdError), }; ok(result) } } // 사용자 정의 추출기를 사용하는 예제 핸들러 async fn handle_request_with_client_id(client_id: ClientId) -> HttpResponse { HttpResponse::Ok().body(format!("Hello from client: {}", client_id.0)) } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new().service(web::resource("/hello").to(handle_request_with_client_id)) }) .bind("127.0.0.1:8080")? .run() .await }
Actix Web 예제에서는 FromRequest의 from_request 메서드가 HttpRequest 참조와 가변 Payload를 받습니다. ClientId 추출기가 헤더 정보만 필요하므로 Payload와 상호 작용할 필요가 없습니다. ClientId에 대한 Deref 구현은 client_id.0.to_string() 대신 client_id.to_string()을 직접 사용하여 ClientId를 사용할 수 있도록 하는 좋은 기능입니다.
애플리케이션 시나리오 및 모범 사례
사용자 정의 추출기는 매우 다재다능하며 수많은 시나리오에 적용할 수 있습니다.
- 인증 및 권한 부여: JWT, API 키 또는 세션 토큰을 추출하고 검증합니다.
- 테넌트 ID 추출: 다중 테넌트 애플리케이션에서 헤더, 하위 도메인 또는 경로에서 테넌트 ID를 추출합니다.
- 복잡한 쿼리 매개변수 구문 분석: 쿼리 매개변수에 내장된 추출기가 제공하는 것 이상의 특정 유효성 검사 또는 변환이 필요한 경우.
- 본문 사전 처리: 메인 핸들러에 도달하기 전에 요청 본문을 해독하거나 압축 해제합니다.
- 사용자 컨텍스트: 인증된 ID를 기반으로 데이터베이스에서
User구조체를 로드하고 핸들러에 주입합니다.
사용자 정의 추출기를 설계할 때는 다음 모범 사례를 고려하십시오.
- 명확한 오류 처리: 항상 추출기에 대한 특정 오류 유형을 정의하고 프레임워크의 오류 변환 트레이트(
Axum의 IntoResponse, Actix Web의ResponseError)를 구현합니다. 이렇게 하면 클라이언트에 대한 명확하고 실행 가능한 오류 메시지가 보장됩니다. - 모듈성: 추출기를 단일 책임에 집중시킵니다. 추출기가 너무 복잡해지면 더 작고 합성 가능한 단위로 분해하는 것을 고려하십시오.
- 성능: 추출기 내에서 성능에 중요한 작업, 특히 디스크 I/O 또는 네트워크 요청과 관련된 작업에 주의하십시오. 적절한 경우 결과를 캐시합니다.
- 테스트 용이성: 추출기를 격리하여 쉽게 테스트할 수 있도록 설계합니다. 모의하기 어려운 복잡한 프레임워크 내부 기능에 너무 많이 의존하지 마십시오.
- 문서화: 추출기가 예상하는 것(예: 필요한 헤더, 본문 형식)과 제공하는 것을 명확하게 문서화합니다.
웹 개발 인체공학 향상
Axum 및 Actix Web의 FromRequest 트레이트를 기반으로 구축된 사용자 정의 추출기는 Rust 웹 애플리케이션의 인체공학성, 모듈성 및 테스트 용이성을 크게 향상시키는 강력한 기능입니다. 요청 데이터 추출 및 유효성 검사 논리를 캡슐화함으로써 핸들러 함수가 깔끔하고 간결하며 순전히 비즈니스 로직에 집중할 수 있도록 하여 유지보수성이 뛰어나고 강력한 코드베이스를 만들 수 있습니다.

