rust: tiny-http は非同期呼び出しできない
Rust で libp2p を使ったアプリを作ろうとしている。
P2P アプリというと身構えてしまうが、サーバとクライアントの両方を持つアプリと思っておけば良い。
クライアントとしては相手の listen しているポートにつなぎに行くだけだし、
サーバとしては接続があれば別のポートに変更してまた listen を続けるだけだ。
プロトコルが何でもよいのであれば libp2p は暗号化する前提になっていたりするのでよいだろう。
P2Pアプリへの指示
サーバアプリもそうかもしれないが、初期設定ではなくアプリの動作を止めずに挙動を変更したいことがある。
P2Pアプリの場合はクライアントも兼ねているので、クライアント機能に外部から指示する経路が必要だ。
私の作業分野では bitcoind などの daemon アプリというかノードアプリと bitcoin-cli のような CLI アプリの組み合わせしか見たことがなかったので、
P2Pアプリというのはそういうものなのだろうと思っている。
tiny-http
ChatGPT氏に JSON-RPC なり REST なりのライブラリはないかと訊いてみたところ、
axum が有名だという返事が返ってきた。
axum を使っておけば間違いないだろう、みたいな感じだったのだが、localhost でコマンドをたたくだけなのに大げさすぎないか?という気がした。
いや、動かした訳ではないのだが “axum is a web application framework that focuses on ergonomics and modularity” なんて力強く書いてあると、
apache や nginx を動かすようなことになってしまわないかと気になった。
(いま Gemini に訊くと axum は軽量だと言っていたので気にしなくてよかったのかもしれない。)
ずらずらと他の候補があって、シンプルなタイプというので紹介されたのが tiny_http だった。
ちょっとやってみるとすぐ動いたので、これを試すことにした。
非同期できない!
確かに今思えば、ChatGPT氏も非同期がどうのこうのと紹介していたように思う。
サンプルコードのコメントに “blocks until the next request is received” とあったので、
複数のクライアントからのリクエストを同時にさばけませんよという意味だろうと考えたのだ。
その側面もあったのかもしれない。
しかし、サーバの待ち受け処理を tokio::spawn() で別タスクにして CLI から要求があったら
P2P 側にメッセージを飛ばしてレスポンスを加工して CLI の結果にしようとしたのだが「メッセージを飛ばす」がどうやってもできなかった。
.await が入るから同期扱いでよいかと思ったのだが、どうもそういうことではないらしい。
tokio::spawn() しただけならよいのだが、tiny-http の処理の一部として行うのがダメらしい。
これを書いている今 ChatGPT(Claudflare?)が止まっていてどういうやりとりをしたのか見ることができないのだが、
回避させるやり方はありそうだったが何だかわかりづらいので、今回は tiny-http はやめて axum を使うことにした。
tiny-http は現時点(2025/11/18)での最終リリースが 2022年。微妙なところだ。
Axum にした
長いものに巻かれるように axum にした。 tokio-rs なので悪いことはなかろう。
axum がというよりは私の Rust への理解が不足しているからだろうが、
いろいろよくわからないままサンプルコードのまねをしたり AI に訊いたりしての実装になってしまった。
今後の課題ですな。
REST 用でも JSON-RPC 用でもないので、REST のエンドポイントが 1つで JSON だけやりとりして key にコマンドを持たせて分岐するようにした。
match でアームをいちいち書くのもなんだかなあと思って HashMap にしたのだが、anyhow::Result を戻す async の関数をどうやってよいかわからなかった
(注:axum のハンドラ自体は async でなくてもよい。こちらの都合。)。
そこは AI にお任せしたのだが、Box やら pin やら async move やら、さっぱりわからんものだらけになった。
- move は 13.1章
- “クロージャに環境で使用している値の所有権を強制的に奪わせることができる”
- スレッドがらみの move は 16.1章
- “あるスレッドから別のスレッドに値の所有権を移すために新しいスレッドを生成する” ???
実は Rust 的にはこういう実装方法を使わないので訳がわからないやり方になってしまった、という可能性も結構ある。
REST にして route ごとに関数を呼び出した方が楽だったんじゃないか。。
いや、JSON でやりたい場合もあるだろうから弱気になってはいかん。
でも JSON-RPCだし jsonrpsee などを使った方が良かったか。。
理解が進んでいないので弱気になってしまうね。
2025/11/20追記
The Rust Programming Language 日本語版を
読み返していた(まだ読み終わってもないが)。
10章までは一応目を通していて、11章はテストだからちょっと後回し、12章はコマンドラインとか書いてあるから後回し、としていたのだが、
12章はリファクタリングや clone の話があるし、Box や dyn も出てきてて、ここ最近でよくわからないと思っているのはここなんだーと思った。
まあ疑問が全部解消するわけではないだろうが、基本は押さえられそうだ。