4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RustでWebサーバーを立てるためのフレームワークとしてaxumがあります。
そんなaxumの使い方を通してRustの構文などに触れていきたいと思います。
(某所でのLTの発表資料のままです…。)

もくじ

  1. axumとは?
  2. axumでどうやってサーバーを立てるのか?
  3. ハンドラーの実装の仕組み
  4. (おまけ)

そもそもaxumとは?

Rust製のウェブアプリケーションフレームワーク。

基本的にはhttpサーバーを作るためのライブラリ。

httpサーバーを作るためには

(アプリケーション層では)基本的にはこの機能が出来れてれば十分

  • http リクエストを受け取る
  • ルーティング
  • リクエストからレスポンスを作る
  • http レスポンスを送る

axumでhttpサーバーを作るためには

axumでは何が必要か

  • http リクエストを受け取る -> axum(hyper+tower)が用意
  • ルーティング -> axumが機能を提供
  • リクエストからレスポンスを作る -> axum(tower)が機能を提供
  • http レスポンスを送る -> axum(hyper)が用意

axumでhttpサーバーを作るためには

  • ルーティング
  • リクエストからレスポンスを作る(関数を作る)

この二つをやればいい!

axumで実装するには?

let app = Router::new()
    .route("/", get(root));

async fn root() {}

// run our app with hyper, listening globally on port 3000
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();

これだけ!

ルーター

このパスに来たらどんな関数を実行するかを登録する

// our router
let app = Router::new()
    .route("/", get(root))
    .route("/foo", get(get_foo).post(post_foo))
    .route("/foo/bar", get(foo_bar));

ハンドラー

リクエストに対してどんなレスポンスを返すかの関数のこと。

async fn get_foo(
    header: HeaderMap
    query: Query<GetFooQuery>,
    body: Json<GetFooRequestBody>,
) -> Result<(StatusCode, HeaderMap, Json<GetFooResponseBody>)> {
    /**/
} 

リクエストとして必要な情報を引数に書き、
レスポンスとして返却するものを戻り値にするだけ!

関数を登録するだけで機能するのは?

ある一定のルールを満たした関数なら何でもハンドラーとして登録できる。

-> どうして?

traitをうまく使っているから!

getの実装

pub fn get<H, T, S>(handler: H) -> MethodRouter<S, Infallible>
where
    H: Handler<T, S>,
    T: 'static,
    S: Clone + Send + Sync + 'static,

getの実装(概略)

pub fn get<H>(handler: H) -> MethodRouter<_, _>
where
    H: Handler<_, _>,

H は型パラメータで、型自体がパラメータとして入る
whereにはHが型として満たすべきものを指定している

Handler(1) Handlerの定義

pub trait Handler<T, S>: Clone + Send + Sized + 'static {
    type Future: Future<Output = Response> + Send + 'static;

    // Required method
    fn call(self, req: Request, state: S) -> Self::Future;

    // Provided methods
    fn layer<L>(self, layer: L) -> Layered<L, Self, T, S>
       where L: Layer<HandlerService<Self, T, S>> + Clone,
             L::Service: Service<Request> { ... }
    fn with_state(self, state: S) -> HandlerService<Self, T, S> { ... }
}

Handler(2) Handlerの定義(概略)

pub trait Handler<T, S>: Clone + Send + Sized + 'static {
    // Required method
    async fn call(self, req: Request, state: S) -> Response;
}

traitは基本的には関数の集合
Handler traitを名乗るには、callという関数を用意することを要求している

Handler(3) Handlerの実装(概略)

axumではHandlerのimplを 型 F に対して定義している

impl<T1, T2> Handler<(T1, T2)> for F
{ 
    fn call(self, req: Request, state: S) -> Future { ... }
}

Handler(4) Handlerの実装(概略)

axumではHandlerのimplを FnOnceを実装しているGenerics に対して定義している

impl<F, Fut, T1, T2> Handler<(T1, T2)> for F
where F: FnOnce(T1, T2) -> Fut, 
{ 
    fn call(self, req: Request, state: S) -> Self::Future { ... }
}

FnOnce

関数やクロージャーは実体のように扱うことができる。

fn function_1() { ... }
let x = function_1;

let y = || {} // クロージャー

条件によって、自動的にFnOnce, FnMut, Fnが実装(impl)され、
関数であるという制約として使うことができる
(これらだけは制約の表現が若干異なる)

Handler(4) trait制約

実際の定義→ 引数と戻り値に制約がある

impl<F, Fut, S, Res, M, T1, T2> Handler<(M, T1, T2), S> for F
where
    F: FnOnce(T1, T2) -> Fut + Clone + Send + 'static,
    Fut: Future<Output = Res> + Send,
    S: Send + Sync + 'static,
    Res: IntoResponse,
    T1: FromRequestParts<S> + Send,
    T2: FromRequest<S, M> + Send, 
    {/**/}

Handler(5) FromRequest

最後の引数はBodyから得られる情報を受け取るFromRequestが、
それ以外の引数は、その他のFromRequestPartsが要求される

FromRequest: Bytes, Json<T>

FromRequestParts: HeaderMap, Method, Uri, Query<T>, Version

Handler(6) serde

Json<T>型は具体的なJsonをデシリアライズした状態で受け取れる

#[derive(Deserialize)]
pub struct FooRequestBody {
    name: String,
    id: i64,
}
async get_foo(body: Json<FooRequestBody>) -> String {/**/ }

{ "name": "aaa", "id": 10 }のようなjsonを受け入れるようになる

Handler(7) IntoResponse

Responseを作れる状態の型に定義される

Json<T>, Html<T>, Redirect, Bytes, StatusCode

Response自体もimplしているので自力で作ってもいい

Handler(8) Future

非同期的に返すための値

最初はasync fnにしておけば気にしなくていい

まとめ

Handlerは大抵のリクエスト、レスポンスの形に合わせて、traitで抽象化して、関数を登録できるようになっている。

rust docを参考にするとどの型が何を実装しているかが見える

(おまけ)Handler実装で詰まったときは

詰まる例(1) FromRequestの場所が最後以外

Handlerのimplは、FromRequestが最後の引数のときだけに受け入れる
順番が逆になっているとコンパイルエラーになる

impl<F, Fut, S, Res, M, T1, T2, T3> Handler<(M, T1, T2, T3), S> for F
where
    F: FnOnce(T1, T2, T3) -> Fut + Clone + Send + 'static,
    Fut: Future<Output = Res> + Send,
    S: Send + Sync + 'static,
    Res: IntoResponse,
    T1: FromRequestParts<S> + Send,
    T2: FromRequestParts<S> + Send,
    T3: FromRequest<S, M> + Send,

詰まる例(2) FromRequestになっていない

Json<T>T: DeserializeOwnedが要求されている
そのためにはTがライフタイムを持ってはいけない

#[derive(serde::Deserialized)]
struct NotOwned<'a> {
  borrowed_str : &'a str,
  owned_string: String, 
}

詰まる例(3) Sendが消える

    Fut: Future<Output = Res> + Send,

基本的には非同期関数を通常通り書いていればSendになるが…
使うとSendが消えるものがある

Rcとか、rand::rngs::ThreadRngとか、!Sendになっているものを使うとasyncが!Sendになる

(おまけ)ステップアップするなら学ぶこと

Service

axumは、towerというClient,Serverを作るcrateの上に乗っかているので、
Serviceについてみておくとaxumがどのような原理で動いているかわかる。

Middleware

これもtowerのLayerの上に成り立っている。
リクエストのフィルター、ログ、認証など
汎用的に拡張できるので学んでおくといいかも
Extractorとかも知ることになる

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?