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::Server
はhyper::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::Router
やtower::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.com
とhttp://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/about
とhttp://example.com//about
は違うURLである。axumはこれに対応してくれない。
これも自前のNormalizePathLayerで対応した。
relative path問題
http://example.com/about/../abc
はhttp://example.com/abc
に等しい。axumはこれに対応している。よかった。
そういえば、昨日ctfに参加して、"../"
を""
に置換する処理は脆弱であることを学んだ。....//
が../
になる。こわいこわい。
normalizeについて
nginxのリバースプロキシにお任せする方法もあるのだけど、まあ基本は自前で処理すべきかと。