あっぽログ
← 記事一覧に戻る

RustのWarpフレームワーク入門:フィルタベースのルーティングとハンドラを理解する

Warpとは何か

WarpはRustで書かれた軽量・高速なWebフレームワークです。Actix-webやAxumと並んでよく使われますが、Warpには独自の特徴があります。それが**フィルタ(Filter)**という概念です。

Warpでは「ルーティング」「リクエストの解析」「認証」などの処理をすべてフィルタとして定義し、それを組み合わせてアプリケーションを構築します。パズルのピースを組み合わせるようなイメージで、非常に柔軟な設計が可能です。

この記事では、Warpの基本的な使い方を実際のコード例とともに解説します。

セットアップ

まずCargoで新しいプロジェクトを作成し、Cargo.tomlに依存関係を追加します。

[package]
name = "warp-example"
version = "0.1.0"
edition = "2021"

[dependencies]
warp = "0.3"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Warpは内部でTokioを使った非同期処理を前提としているため、tokioも一緒に追加します。

最初のサーバーを起動する

まずはGET /helloにアクセスすると文字列を返す、シンプルなサーバーを作ってみましょう。

use warp::Filter;

#[tokio::main]
async fn main() {
    // GETメソッド + "/hello" パスにマッチするフィルタ
    let hello = warp::get()
        .and(warp::path("hello"))
        .map(|| "Hello, Warp!");

    println!("サーバーを起動します: http://127.0.0.1:3030");
    warp::serve(hello).run(([127, 0, 0, 1], 3030)).await;
}

warp::get()でGETリクエストにマッチするフィルタを作り、.and()で別のフィルタと合成しています。この「合成」がWarpの核心です。

パスパラメータを受け取る

URLのパスに含まれる動的な値(例: /users/42)を取り出すには、warp::path::param()を使います。

use warp::Filter;

#[tokio::main]
async fn main() {
    // GET /users/:id
    let user = warp::get()
        .and(warp::path("users"))
        .and(warp::path::param::<u32>()) // u32型として取り出す
        .and(warp::path::end())
        .map(|id: u32| {
            format!("ユーザーID: {}", id)
        });

    warp::serve(user).run(([127, 0, 0, 1], 3030)).await;
}

warp::path::param::<u32>()は、パスの次のセグメントをu32として解析します。型が合わなければ自動的に404を返してくれる点も便利です。

JSONレスポンスを返す

実際のAPIではJSONを返すことがほとんどです。SerdeのDerive macroとWarpを組み合わせると簡単に実現できます。

use serde::Serialize;
use warp::Filter;

#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
}

#[tokio::main]
async fn main() {
    // GET /users/:id → JSONを返す
    let user = warp::get()
        .and(warp::path("users"))
        .and(warp::path::param::<u32>())
        .and(warp::path::end())
        .map(|id: u32| {
            let user = User {
                id,
                name: format!("ユーザー{}", id),
            };
            warp::reply::json(&user)
        });

    warp::serve(user).run(([127, 0, 0, 1], 3030)).await;
}

warp::reply::json()Serializeを実装した構造体を渡すだけで、Content-Type: application/jsonヘッダ付きのレスポンスが返ります。

リクエストボディを受け取る

POSTリクエストでJSONボディを受け取るには、warp::body::json()フィルタを使います。

use serde::{Deserialize, Serialize};
use warp::Filter;

#[derive(Deserialize, Serialize)]
struct CreateUser {
    name: String,
}

#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
}

#[tokio::main]
async fn main() {
    // POST /users
    let create_user = warp::post()
        .and(warp::path("users"))
        .and(warp::path::end())
        .and(warp::body::json::<CreateUser>()) // ボディをDeserialize
        .map(|body: CreateUser| {
            let user = User {
                id: 1, // 実際はDBから採番
                name: body.name,
            };
            warp::reply::with_status(
                warp::reply::json(&user),
                warp::http::StatusCode::CREATED, // 201を返す
            )
        });

    warp::serve(create_user).run(([127, 0, 0, 1], 3030)).await;
}

warp::reply::with_status()でHTTPステータスコードを指定できます。

複数のルートを束ねる

実際のアプリでは複数のルートが必要です。.or()で複数のフィルタを束ねられます。

use warp::Filter;

#[tokio::main]
async fn main() {
    let get_users = warp::get()
        .and(warp::path("users"))
        .and(warp::path::end())
        .map(|| "ユーザー一覧");

    let get_user = warp::get()
        .and(warp::path("users"))
        .and(warp::path::param::<u32>())
        .and(warp::path::end())
        .map(|id: u32| format!("ユーザー: {}", id));

    // .or() でルートを合成する
    let routes = get_users.or(get_user);

    warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}

Warpはマッチするフィルタを先頭から順に試みます。マッチしなければ次のフィルタを試すという動作になります。

まとめ

Warpの基本をまとめると以下のとおりです。

機能使い方
GETルートwarp::get().and(warp::path("xxx"))
パスパラメータwarp::path::param::<T>()
JSONレスポンスwarp::reply::json(&data)
リクエストボディwarp::body::json::<T>()
ルートの合成.or()
ステータスコード指定warp::reply::with_status()

Warpのフィルタ合成という設計は最初は少し独特に感じますが、慣れると非常に柔軟で再利用しやすいコードが書けるようになります。まずは小さなAPIサーバーを作って、フィルタの組み合わせ方を試してみてください。

← 記事一覧に戻る