mini BanditとWeb開発に必要な機能実装 〜http2モドキ編〜
はじめに
mini Bandit開発の経緯
いろいろなライブラリをElixirChipで動かしたい
(現時点では、既存FPGAでそれなりに早く動かすことを目標とする)
→いきなり全部載せは難しい
→Webアプリ開発に絞ろう
→Phoenixだと豪華すぎるのでもっとシンプルなHTTPサーバーがいい
→Banditを参考にしよう
→mini Bandit
Bandit とは
https://github.com/mtrudel/bandit
TCP/TLS層はThousandIslandに任せて、HTTP1/HTTP2/WebSocket対応したシンプルなサーバー。
graph LR;
subgraph ThousandIsland
TCP-->TLS;
end
TLS-->HTTP1;
subgraph Bandit
HTTP1
HTTP2
WebSocket
end
TLS-->HTTP2;
TLS-->WebSocket;
mini Banditの開発方針
Webアプリ開発は最低限下記の機能があればできるはずなので作っていきたい
-
HTML/EExを表示
- Bandit + Plug.Route
-
DBアクセス(CRUD)
- Ecto
-
Web APIへのアクセス/JSON取得、加工
- Req
しかし、これらの前提となる通信プロトコルも課題になる
- TCP → :gen_tcp依存でElixir実装が存在しない
-
HTTP1 → TCP通信ができる前提で、BanditがHTTP処理をElixirのみで実装しているので、自前で簡易実装
- 前回行った→ElixirImp 2025/04/16
- HTTP2 → 同様にBanditで本LTで一部を実装
graph LR;
HTTP-Client-->HTTP-HOST
HTTP-HOST-->HTML/CSS/JS/EEx
HTTP-HOST-->DB
HTTP-HOST-->Web-API
LT内容
graph LR;
HTTP1-->HTML/CSS/JS/EEx
subgraph Today
HTTP2
end
HTTP2-->HTML/CSS/JS/EEx;
共通処理
-
レスポンスの作成
- HTTP2レスポンス
mini Bandit HTTP2モドキ対応
普通にWebアプリ作ってるだけだと意識しないので、HTTP1とHTTP2の違いから整理
機能 | HTTP/1.1 | HTTP/2 |
---|---|---|
多重化(Multiplex) | 一度に1リクエスト/レスポンス | 1つのTCP接続上で複数のリクエスト/レスポンスが並行処理 |
ヘッダ圧縮 | 毎回全ヘッダ送信 | HPACKによるヘッダ圧縮 |
サーバープッシュ | 無し | クライアントが要求する前にデータ送信可能 |
バイナリフレーム | ❌ テキストベース | バイナリフレーミング |
全部はよくわからんので、入力となるバイナリフレームをそれっぽく実装
こういう形らしい
引用:https://datatracker.ietf.org/doc/html/rfc7540#section-4.1
Bandit Code Reading
バイナリフレームを処理している場所が下記
画像と比較しても仕様と一致させやすいような作りになっている
def deserialize(
<>,
max_frame_size
)
when length <= max_frame_size do
type
|> case do
0x0 -> Bandit.HTTP2.Frame.Data.deserialize(flags, stream_id, payload)
0x1 -> Bandit.HTTP2.Frame.Headers.deserialize(flags, stream_id, payload)
0x2 -> Bandit.HTTP2.Frame.Priority.deserialize(flags, stream_id, payload)
0x3 -> Bandit.HTTP2.Frame.RstStream.deserialize(flags, stream_id, payload)
0x4 -> Bandit.HTTP2.Frame.Settings.deserialize(flags, stream_id, payload)
0x5 -> Bandit.HTTP2.Frame.PushPromise.deserialize(flags, stream_id, payload)
0x6 -> Bandit.HTTP2.Frame.Ping.deserialize(flags, stream_id, payload)
0x7 -> Bandit.HTTP2.Frame.Goaway.deserialize(flags, stream_id, payload)
0x8 -> Bandit.HTTP2.Frame.WindowUpdate.deserialize(flags, stream_id, payload)
0x9 -> Bandit.HTTP2.Frame.Continuation.deserialize(flags, stream_id, payload)
unknown -> Bandit.HTTP2.Frame.Unknown.deserialize(unknown, flags, stream_id, payload)
end
|> then(&{&1, rest})
end
実装と動作用スクリプト