axum,hyper,tower 格闘記

axum, hyper, towerあたりと格闘した記録。

make_service

axumのサーバーはこんな感じに使う。v0.6.0の話。

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

axum::Server::bind(&"0.0.0.0::3000".parse().unwrap())
    .serve(app.into_make_service())

axum::Serverhyper::Serverの再エクスポートである。hyper::Server::serve()MakeServiceを受け取る。

let make_service = make_service_fn(move |_| {
    async {
        Ok::<_, Infallible>(service_fn(move |req| {
            handle(req)
        }))
    }
});
hyper::Server::bind(&"0.0.0.0::3000".parse().unwrap())
    .serve(make_service)

まあ、axumを使っていればinto_make_service()が使えるので深く知る必要はない。この関数はService<R>トレイトに対して実装されているため、axum::Routertower::Service<hyper::Request<hyper::Body>>をmake_service化できる。

ここで、tower_http::trace::TraceLayerを使うと、into_make_service()できなくなる問題にぶちあたる。どうやら、レスポンスをtower_http::trace::ResponseBodyにしてしまうようだ。BodyChunkへの対応や、EOS/Failure時にエラーを返せるように作っているからかもしれない。型推論がどのように利いているのか全く理解できておらず、もしかしたら何とかなるのかもしれないけど、一旦諦めて自前のTraceLayerを実装した。

Router::layer()の罠

http://localhost:3000//にアクセスすると404になった。axumはv0.6.0からtrailing slashを消さなくなったようで、uri=//にマッチするルーティングが無いためである。

このようなuriは正規化される。少なくともnginxは正規化してくれたと記憶している。(..これは気のせいだった。正規化せずに転送するモードもある。)

だが、axumは正規化してくれない。なんて面倒な...。

それで、tower_http::normalize_path::NormalizePathLayer::trim_trailing_slash()を使って末尾/を除去しようとした。しかし思ったようにいかなかった。

axum::Router::layer()で登録したミドルウェアが実行されるのはマッチングの後ということを知った。ミドルウェアの中でuriを書き換えても間に合わない。ミドルウェアはルーティングの前に実行しなければならない。

それまでは:

Router()
    .route("/", get(get_index))
    .layer(ServiceBuilder![L1, L2, L3])

というチェーンを組んでいたのだが、

ServiceBuilder![
    L1, L2, L3, Router().route("/", get(get_index))
]

という風に、towerのServiceStackを最初に持ってくるための改造が必要だった(コードは適当)。冒頭のTraceLayerで沼ったのはこれのせい。

trailing slash問題

http://example.comhttp://example.com/はuriが異なる。

NormalizePathLayer::trim_trailing_slashはuriを書き換えるが、だが、異なるuriで同じコンテンツを返すのはCEO的に良くない。301リダイレクトした方がよい。

ほとんど調べていないのでふわふわの説明しかできないのだが、axumはuri無しでアクセスするとuri="/"に直してくれる(っぽい)。NormalizePathLayerは末尾スラッシュをすべて消去する。なので、"/"にアクセスするとuriが無くなってしまう。そのせいか分からないものの、無限リダイレクトになった。

NormalizePathLayerは使い物にならない。そう思い、自前のNormalizePathLayerを実装した。

double slash問題

trailing slashだけでなく、double slashは結構有名な問題らしい。

http://example.com/abouthttp://example.com//aboutは違うURLである。axumはこれに対応してくれない。

これも自前のNormalizePathLayerで対応した。

relative path問題

http://example.com/about/../abchttp://example.com/abcに等しい。axumはこれに対応している。よかった。

そういえば、昨日ctfに参加して、"../"""に置換する処理は脆弱であることを学んだ。....//../になる。こわいこわい。

normalizeについて

nginxのリバースプロキシにお任せする方法もあるのだけど、まあ基本は自前で処理すべきかと。