Erlang で HTTP/2 サーバー Lucid を書きました.
https://github.com/tatsuhiro-t/lucid
この記事では特に HTTP/2 の仕様に関して説明はしませんので, HTTP/2 に興味ある方は HTTP/2 Advent Calendar を読む, あるいは HTTP/2 ID を読む等をおすすめします.
Lucid は私が初めて Erlang で書いたアプリケーションです. 私は Programming Erlang を 7 年程前に買いました. そのころは日本語訳がまだでていませんでした. 半分ほど読んでからほったらかしにして 7 年程たちました. その間一切 Erlang のコードは書いていません.
さて私の twitter TL において "すごい E 本" の日本語版出版の話題が連日盛り上がっていました. そんなに盛り上がるなら何かあるのだろうということで, 原文を Web で読み始めました. The Count of Applications の章まで読んで飽きがきました. しかしそれなりに分かった気になったので一つサーバーを書いてみようと思い立ちました. どのプロトコルを実装するかは大きな選択でしたが, まだだれもやっていなさそうである HTTP/2 を実装することにしました. HTTP/2 については C で nghttp2 を実装しているので, ワタシ HTTP/2 チョットデキル. そこで Buckets of Sockets の章を斜め読みし, supervisor や acceptor 辺りを参考に実装をはじめました.
いくつか HTTP/2 に関連した実装の話題を書きたいと思います.
TLS で HTTP/2 を使う場合, TLS 拡張の ALPN で HTTP/2 をネゴシエートしなければなりません. Erlang の ssl モジュールは ALPN をサポートしていません. NPN はサポートしているので今回は NPN を使います. NPN を使うのは簡単で ssl_options
に {next_protocols_advertised,[<<"h2-14">>]}
を追加するだけです. NPN ではクライアントがプロトコルを選択するので実際にネゴシエートしたプロトコルを得るには ssl:negotiated_next_protocol(Socket)
を使います.
サーバーの基本的なアーキテクチャは Buckets of Sockets の sockserv を踏襲しています. HTTP/2 では 1 接続上に複数のリクエスト (ストリーム) が多重化されています. HTTP/2 ではフレームという単位で接続上のデータを処理をしますが Erlang のすばらしいバイナリデータサポートによりパーサーは簡単です. リクエストを受け取ったらプロセスを作成して処理を任せます. それらプロセスからレスポンスデータが来たら, フレームにシリアライズしてクライアントへ送信します.
HTTP/2 のヘッダー圧縮 HPACK も実装しています. huffmandata.erl は巨大なファイルですが, ハフマン符号化のためのテーブルをパターンマッチングのために自動生成したものです. ハフマン符号化はビットレベルの操作なので C などではシフトなど面倒ですが, Erlang は強力な binary comprehension と bitstring のお陰でミスの少ないコードが書けます.
テスト用にこの HTTP/2 サーバーは https://nghttp2.org:3456 で動いています. Chrome M39 では spdy/4 を有効にしても ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY
というエラーでアクセスできません. これは Chrome が AEAD 暗号化スイートを要求するのに対し, Erlang の ssl モジュールがそれに非対応だからです (この制限は, HTTP/2 仕様からは取り除かれる可能性があります). Firefox も同様ですが, about:config で network.http.spdy.enforce-tls-profile
を false
にするとこの制限を外すことができてアクセス可能になります. nghttp2 に含まれる nghttp クライアントはチェックをしていないのでアクセス可能です.