👟

axum crate の Router (2) 設定済みの Router はどう動くのか

に公開

前回は axum crate の Router (1) で Router の route メソッドなどを見ました。 なんとなく Router の内部構造をつかみはじめたところです。

今回は fallback を見ていくと書いたのですが、すこし読んでみたところ、設定済みの Router がどのように動くのかを把握するほうが良さそうだと考えたので、そちらを確認していきます。

今回も axum crate のバージョンは 0.8.6 です。

serve からはじめる

前々回の axum crate のかんたんな例 の中で、 serve の例がありました。再掲します。

#[tokio::main]
async fn main() {
    let app = axum::Router::new()
        .route("/", axum::routing::get(root))
        .route("/users", axum::routing::post(create_user))
        .route("/users/{user_id}", axum::routing::get(get_user));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Routerserve の第二引数に指定し、サーバーを起動していました。ここを今回の出発点にします。

serve のシグネチャと make_service

https://docs.rs/axum/0.8.6/axum/fn.serve.html

pub fn serve<L, M, S>(listener: L, make_service: M) -> Serve<L, M, S>
where
    L: Listener,
    M: for<'a> Service<IncomingStream<'a, L>, Error = Infallible, Response = S>,
    S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
    S::Future: Send,

serve は思ったよりも複雑なシグネチャを持っています。

Router は、第二引数に指定できたことから M のトレイト境界 for<'a> Service<IncomingStream<'a, L>, Error = Infallible, Response = S> を満たしているはずです。

これはどういうことでしょう。 Servicetower_service crate の Service 。つまり tower::Service です。

https://docs.rs/tower-service/0.3.3/tower_service/trait.Service.html

IncomingStreamaxum::serve::IncomingStream です。

https://docs.rs/axum/0.8.6/axum/serve/struct.IncomingStream.html

Error = InfallibleInfalliblestd::convert::Infallible です。これは決して失敗しない (起こらない) エラーのことですね。

https://doc.rust-lang.org/std/convert/enum.Infallible.html

Response = SS はトレイト境界から HTTP リクエストを受け取って HTTP レスポンスを返すサービスです。

ややこしいですが、ここまでで Router (M) は Service を返す Service であることが分かります。

RouterServiceIncomingStream を受け取って Service を返します。ここで返された ServiceRequest を受け取って Response を返す Service ということです。

ああ、ややこしい。

impl Service<IncomingStream, ...> for Router<()>

さて、 RouterService<IncomingStream, ...> を実装している箇所を探します。

impl<L> Service<IncomingStream<'_, L>> for Router<()>Router のドキュメントの Trait Implementations から見つけられます。

https://docs.rs/axum/0.8.6/axum/struct.Router.html#impl-Service<IncomingStream<'_,+L>>-for-Router

ドキュメントにも記載されていますが、 Router<()> のように Router<S>S() である必要があります。すべての state が欠けていない Router が要求されているということですね。

実装は次のとおりです。

https://docs.rs/axum/0.8.6/src/axum/routing/mod.rs.html#549-566

impl<L> Service<serve::IncomingStream<'_, L>> for Router<()>
where
    L: serve::Listener,
{
    type Response = Self;
    type Error = Infallible;
    type Future = std::future::Ready<Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }

    fn call(&mut self, _req: serve::IncomingStream<'_, L>) -> Self::Future {
        // call `Router::with_state` such that everything is turned into `Route` eagerly
        // rather than doing that per request
        std::future::ready(Ok(self.clone().with_state(())))
    }
}

ready やらで wrap されていますが、 self.clone().with_state(()) したものを返しています。

with_state(())Router<()> を返します。

https://docs.rs/axum/0.8.6/axum/struct.Router.html#method.with_state

つまり、 Routerwith_state(()) したものは serveS (HTTP リクエストを受け取って HTTP レスポンスを返すサービス) を満たすことが分かります。

再掲: serveS

pub fn serve<L, M, S>(listener: L, make_service: M) -> Serve<L, M, S>
where
    L: Listener,
    M: for<'a> Service<IncomingStream<'a, L>, Error = Infallible, Response = S>,
    S: Service<Request, Response = Response, Error = Infallible> + Clone + Send + 'static,
    S::Future: Send,

impl Service<Request, ...> for Router

また Service の実装を探します。今度は Request を受け取って Response を返す ServiceRouter のための実装です。

impl<B> Service<Request<B>> for Router<()>Router のドキュメントから Trait Implementations から見つけられます。

https://docs.rs/axum/0.8.6/axum/struct.Router.html#impl-Service<Request<B>>-for-Router

実装は次のようになっています。

https://docs.rs/axum/0.8.6/src/axum/routing/mod.rs.html#569-588

impl<B> Service<Request<B>> for Router<()>
where
    B: HttpBody<Data = bytes::Bytes> + Send + 'static,
    B::Error: Into<axum_core::BoxError>,
{
    type Response = Response;
    type Error = Infallible;
    type Future = RouteFuture<Infallible>;

    #[inline]
    fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }

    #[inline]
    fn call(&mut self, req: Request<B>) -> Self::Future {
        let req = req.map(Body::new);
        self.call_with_state(req, ())
    }
}

axum::body::Body::newhttp::Request<B> を map し、 Router::call_with_state を呼び出しています。

ようやく HTTP リクエストを処理している箇所にたどりつきそうです。

Router::call_with_state

call_with_state は公開されている関数ではないので、ドキュメントを見ても分かりません。ソースコードを見ていきます。

https://github.com/tokio-rs/axum/blob/axum-v0.8.6/axum/src/routing/mod.rs#L417-L432

pub(crate) fn call_with_state(&self, req: Request, state: S) -> RouteFuture<Infallible> {
    let (req, state) = match self.inner.path_router.call_with_state(req, state) {
        Ok(future) => return future,
        Err((req, state)) => (req, state),
    };


    let (req, state) = match self.inner.fallback_router.call_with_state(req, state) {
        Ok(future) => return future,
        Err((req, state)) => (req, state),
    };


    self.inner
        .catch_all_fallback
        .clone()
        .call_with_state(req, state)
}

来ましたね。

self.inner.path_router.call_with_state を呼び出し、成功すれば返して終了、失敗すれば次へ。

self.inner.fallback_router.call_with_state を呼び出し、成功すれば返して終了、失敗すれば次へ。

self.inner.catch_all_fallback.call_with_state を呼び出す。

前回は分からなかった RouterInner のフィールド がなんとなく見え、当初今回のゴールに設定していた fallback も近づいてきましたね。

PathRouter::call_with_state

今回は PathRouter を見て終わりにしましょう。

https://github.com/tokio-rs/axum/blob/axum-v0.8.6/axum/src/routing/path_router.rs#L371-L420

pub(super) fn call_with_state(
    &self,
    #[cfg_attr(not(feature = "original-uri"), allow(unused_mut))] mut req: Request,
    state: S,
) -> Result<RouteFuture<Infallible>, (Request, S)> {
    #[cfg(feature = "original-uri")]
    {
        use crate::extract::OriginalUri;


        if req.extensions().get::<OriginalUri>().is_none() {
            let original_uri = OriginalUri(req.uri().clone());
            req.extensions_mut().insert(original_uri);
        }
    }


    let (mut parts, body) = req.into_parts();


    match self.node.at(parts.uri.path()) {
        Ok(match_) => {
            let id = *match_.value;


            if !IS_FALLBACK {
                #[cfg(feature = "matched-path")]
                crate::extract::matched_path::set_matched_path_for_request(
                    id,
                    &self.node.route_id_to_path,
                    &mut parts.extensions,
                );
            }


            url_params::insert_url_params(&mut parts.extensions, match_.params);


            let endpoint = self
                .routes
                .get(&id)
                .expect("no route for id. This is a bug in axum. Please file an issue");


            let req = Request::from_parts(parts, body);
            match endpoint {
                Endpoint::MethodRouter(method_router) => {
                    Ok(method_router.call_with_state(req, state))
                }
                Endpoint::Route(route) => Ok(route.clone().call_owned(req)),
            }
        }
        // explicitly handle all variants in case matchit adds
        // new ones we need to handle differently
        Err(MatchError::NotFound) => Err((Request::from_parts(parts, body), state)),
    }
}

今回の引用では最長ですね。

ざっくりと見ると次のようになっています。

  1. req.into_parts() で HTTP ボディとそれ以外に分けて
  2. self.node.at(parts.uri.path()) でマッチするルート (のID) を探して
  3. あれば self.routes.get(&id)Endpoint を得て、なければ Err
  4. EndpointMethodRouter なら call_with_state 、ネストした Route なら call_owned でさらに呼び出し

Node::at

self.node.at (Node::at) を見ると、 self.inner.at に丸投げされています。

https://github.com/tokio-rs/axum/blob/axum-v0.8.6/axum/src/routing/path_router.rs#L501-L506

fn at<'n, 'p>(
    &'n self,
    path: &'p str,
) -> Result<matchit::Match<'n, 'p, &'n RouteId>, MatchError> {
    self.inner.at(path)
}

Node::innermatchit crate の Router です。

https://github.com/tokio-rs/axum/blob/axum-v0.8.6/axum/src/routing/path_router.rs#L478-L482

struct Node {
    inner: matchit::Router<RouteId>,
    route_id_to_path: HashMap<RouteId, Arc<str>>,
    path_to_route_id: HashMap<Arc<str>, RouteId>,
}

matchit は axum crate とは別のクレートです。

こちらの詳細は別の機会に読むことにします。

残りの処理

PathRouter::call_with_state に戻り、 Node::at でマッチしたルートの value (RouteId) と params を得たところから見ます。

得られた id: RouteIdRouter::routes から Endpoint を得ます。 self.routes.get(&id) ですね。

Endpoint の variant で分岐して、 MethodRouter::call_with_stateRoute::call_owned を呼び分けます。

そして…… MethodRouter 以降は……省略! 力尽きました!

このあとは Route::oneshot_inner_owned を呼び出して、 tower::util::BoxCloneSyncService::oneshot の呼び出しへとつながっていくようです。ここはまたハンドラーかミドルウェアのタイミングでまた出てくると思います。

おわりに

今回は axum crate の学び直しの第 3 回として、設定した Router がどう動くのかを追いかけてみました。

RouterService を返す Service であり、そこで返された ServiceRequest を受けて Response を返すものだと分かりました。

Router::call_with_stateRouter のフィールドにある path_router, fallback_router, catch_all_fallback へと処理を転送していました。

path_router (PathRouter) は設定されたルート (Node) から matchit crate でマッチするものを選んで、 MethodRouter などにさらに処理を転送していました。

MethodRouter からは Route に設定されたサービスの呼び出しにつながっていきそうな点を見ることができました。

GitHubで編集を提案
ドクターメイト

Discussion