園児ニア日記

多言語対応Webサーバーインターフェース、Plamoをリリースしました

Plamo

Plamoとは

Plamoは多言語対応のWebサーバーインターフェース。
Rubyで言うところのRackみたいなやつ。
公式サイトは https://plamo.org
プラモデルみたいにミドルウェアを繋いで組み立てていく感じのイメージだったのでPlamoって名前にした。
公式サイトのデザインは@kanaktに、英文の添削は@matsumoto4aにやってもらった。

そもそもなんで作ろうと思ったのか

最初RustでWebアプリケーションフレームワーク作ろうと思って色々調べてみたんだけどRustにはRack的ポジションのやつがいなくてみんな好き勝手に作ってたから、まずはRack的なやつから作ろうと思ったのが始まり。
その後わき道にそれて今の形になる。

車輪の再発明では?

🦀
でもこれからPlamoのいいところ言うからちょっとまってて

Webサーバーインターフェースの役目

大きく分けて以下の2つだと思う(たぶん…)

  1. WebサーバーとWebアプリケーションフレームワークの間に入っていい感じに抽象化する
  2. レスポンスのgzip圧縮など、どのフレームワークでも使うような機能をフレームワークごとに作らないでいいようにする(ミドルウェアとかって呼ばれてたりする)

十人十色

既存のWebサーバーインターフェースは色々種類がある。
RubyのRack、PythonのWSGI、JavaScriptのConnectなどなどである。
……おわかりいただけただろうか…?
やりたいことはみんな大体同じなのに言語の数だけ異なる仕様が存在するのだ😇

どの言語からでも使えて、さらにミドルウェアを使い回せる最高の仕様が欲しい

多くの言語にはCの関数を呼べる機能が搭載されているので、これをいい感じに使えば多言語対応できるのでは?と思った。
また、ミドルウェアの使い回しは後述の方法で実現できる気がした。

実装に使う言語

Cの関数や構造体などをいい感じに定義できる言語はいくつかあるのだけど、今回はRustを使って実装した。
なぜならRustはイケイケで、ナウでヤングな僕にピッタリだからだ(すまん、適当なこと言った。それに本当は僕アラサーのおっさん🤫)
正直なところ言語をどれにするかは結構迷った。
C、C++、NimC2、Go、Rustのどれにするか(今だったらVとかZigも候補に挙がってたかも?)で迷ったのだけど、Cは標準のコンテナとか無くて、C++は怖い、そしてNimC2は尖りすぎ…?となるとGoかRustになるのだけど、その2つだったらRustのほうが好きかなってことで最終的にRustにしたけど、実際はこんなにさくっと決められず1週間以上悩んだ🤔

主な登場人物

  1. PlamoString - 文字列、基本的にPlamoで使われる文字列はこいつ(Ruby版ではユーザーが意識して使うことはなくてRubyの文字列を渡せば勝手にPlamoStringに変換されるようになってる)、実体はRustのCString
  2. PlamoStringArray - 文字列の配列、PlamoHttpHeaderやPlamoHttpQueryで使われてる
  3. PlamoByteArray - バイナリデータを入れるやつ、PlamoRequestやPlamoResponseのbody部分で使われてる(Plamoではリクエストやレスポンスのボディが文字列でも画像でもPlamoByteArrayを使う)
  4. PlamoHttpHeader - HTTPヘッダー、PlamoRequestやPlamoResponseで使われてる
  5. PlamoHttpQuery - HTTPクエリ、PlamoRequestで使われてる
  6. PlamoRequest - HTTPのリクエスト
  7. PlamoResponse - HTTPのレスポンス
  8. PlamoMiddleware - PlamoのミドルウェアでありPlamoの主役的存在、全てはこれのために存在するようなもの
  9. PlamoApp - PlamoMiddlewareを登録するやつ、これに登録された順にPlamoMiddlewareが実行される

PlamoMiddleware

主な登場人物でも書いたようにPlamoの主役的存在であり、Plamoが多言語対応できているのはこいつのおかげと言ってもいい。
PlamoMiddlewareを作成する時にcallbackconfigの2つを渡す。
callbackconfigPlamoRequestPlamoResponseが渡される関数のこと。 configの実体はvoid*なので基本的に何でも入れられて、こいつをcallback内でキャストなどすることでミドルウェアに設定等を与えることができるのだ🐹

処理の中断

PlamoMiddlewareのどこかでレスポンスコードが300番以上のものがあった場合、そこで処理が中断されレスポンスが返る。
もともとはコールバック関数の戻り値がfalseだった時に中断という仕様にしていたけど、毎回trueとかfalseとか書くの面倒だなと思ってレスポンスコード見るように変更した😅
書くのが楽になった代わりに300番以上、例えば404だけど後続のミドルウェアを実行したいっていうパターンに対応できなくなった😓

libplamo

Plamoの本体(https://github.com/plamo/libplamo
こいつを共有ライブラリとしてインストールして、言語ごとのラッパー経由で使う感じ。
今のところ多分Linux以外で動かないから(もしかしたらMacだと動くかも?)DockerやWSLなどを経由して使ってね。
WindowsやMac対応のプルリク待ってるぜ!😎

plamo-rust

Rust用のラッパー(https://github.com/plamo/plamo-rust
コードはlibplamoのヘッダーファイル(libplamo.h)から自動生成される。
RustからPlamoを使う時はlibplamoを直接使うのではなくplamo-rust経由で使うこと。

plamo-ruby

Ruby用のラッパー(https://github.com/plamo/plamo-ruby
最初はRuby-FFIを使って書いたのだけど、できたものがめっちゃ使いにくかったのでCでNative Extensionとして書き直した
こいつが作るの一番大変だった。
特にNative Extensionのマルチスレッド周りが全然わからなかった(普通に書くとRubyのProcを別のスレッドから呼ぶと落ちる)のだけど↓の記事にめっちゃ助けられた。
Asynchronous callbacks in Ruby C extensions
実は現在、ガベージコレクトされたときにセグフォするバグがあるのだけど、Done is better than perfectの精神でリリースしちゃった😋
誰か直してプリーズ😘

ドキュメントについて

そのうち作ります…すみません…許して…😭

Plamo対応Webサーバー

作ってる。
本当はPlamoと一緒に出す予定だったけど、思ってた以上に時間がかかってしまっているので先にPlamoだけ出すことにした😅
完成次第Webサーバーの方もリリース予定。

rust-jpへの感謝

Slackでいろんな質問に答えていただいた。
このコミュニティがなかったら途中で投げ出していたかもしれないので本当に感謝しています。

最後に好きなxkcdを1つ