最近、ElixirのPhoenixを使う機会が増えて来ました。そんなPhoenixですが内部的にErlangのHTTPサーバであるCowboyが使われています。内部的に使われてるので一度、Cowboyを使ってみたいと思い、Erlangで簡単なサンプルを実装してみましたのでその時のメモを書いていきます。
さらっとCowboyの特徴
モダンなHTTPサーバになります。モダンと言ってるだけあってHTTP/2とWebSocketを対応しているようです。遅延とメモリ消費量が少なくコード量自体も少ないHTTPサーバになります。なので簡単なAPIサーバを実装するときにPhoenixでは重過ぎる場合とかに使えるかなぁと思います。
実行した環境
- Erlang/OTP 21
- rebar3 3.6.1
- Cowboy 2.4.0
実装
環境準備
Erlangとrebar3
まずは環境から。そもそものErlangの環境が必要になるので、Erlangが動作する + rebar3が動作する環境を準備します。自分はDockerで環境を準備しました。
docker pull erlang
これだけです。これでErlangの環境が手に入りました。便利ですねぇ
次は手に入れた環境を動かしてみます。
docker run -id -d -p 8010:8010 --name erlang erlang
これで、ローカルの8010ポートとDockerの8010ポートをマッピングしましたので、ローカルの8010にアクセスすると起動したErlangコンテナの8010にアクセスが行くようになります。なのでErlangコンテナ内ではCowboyを8010ポートで起動させるようにします。
公式のErlangイメージにはrebar3がすでに入っている状態です。
$ docker exec -it erlang rebar3 version
rebar 3.6.1 on Erlang/OTP 21 Erts 10.0.5
プロジェクトの作成
次は、rebar3でプロジェクトを作成します。
次のコマンドをコンテナ内で実行することでプロジェクトの雛形が作成されます。
rebar3 new app sandbox
今回はsandbox
っと言う名前のプロジェクトを作成しました。
後日に詳しく書こうかと思いますが、rebar3でプロジェクトを作成する方法はいくつかあります。rebar3 new app [アプリケーション名]
でプロジェクトを作成すると単独で動作するプロジェクトが作成されます。
ディレクトリ構成
作成したばかりのプロジェクトのディレクトリ構造は以下のようになります。
sandobx
|- .gitignore
|- LICENSE
|- README.md
|- rebar.config
|- src
|- sandbox.app.src
|- sandbox_app.erl
|- sandbox_sup.erl
ここで、軽くそれぞれのファイルとディレクトリについて説明しておきます。
rebar.configはその名の通り、設定ファイルになります。ここに今回のプロジェクトに依存するライブラリを追加したり、コンパイル時のオプションを指定したりします。
sandbox.app.srcではプロジェクトの情報が記述されています。バージョンだったりとか依存関係のあるアプリケーションなどが記述されています。
sandbox_app.erlに実際にアプリケーションとして動かすコードを記述していきます。ここのstart
関数が一番最初に呼ばれます。
sandbox_sup.erlはプロジェクトのトップレベルのsupervisorになります。
ハンドリングとコネクション
ではここから実際にアプリケーションを作成していきます。
sandbox_app.erlのstart
関数内に以下のコードを追加します。
start(_StartType, _StartArgs) ->
Dispatch = cowboy_router:compile([
{'_', [
{"/", toppage_handler, []}
]}
]),
{ok, _} = cowboy:start_clear(http, [{port, 8010}], #{
env => #{dispatch => Dispatch}
}),
sandbox_sup:start_link().
これはCowboyでのルーティングの設定になります。
cowboy_router:compile
でルーティングの初期設定を行なっています。compile
関数の引数でリストを渡した場合は、タプルの配列を渡します。上記のタプルでは {HOST、PATH}
で渡しています。'_'
は動作しているホスト自信を指定しています。{"/", toppage_handler, []}
ではルートパスにアクセスが来たときにtoppage_handlerが実行されるようなパスの設定になります。
cowboy:start_clear
関数でTCPでの接続設定を行います。
http
は単なる名前です。
[{port, 8010}]
はリッスンするポートを指定します。
#{env => #{dispatch => Dispatch}}
ではプロトコルの設定を行います。今回はHTTP/1.1を使うのでcowboy_http
の設定になります。dispatchで指定したものがルーティングで使われます。
ハンドラーの設定
次にハンドラーの設定をしていきます。今回は、Helloをレスポンスした後に1秒スリープをし、次にWrokdをレスポンス。もう一度1秒スリープした後にCunked!をレスポンスするようにしました。
-module(toppage_handler).
-export([init/2]).
init(Req0, Opts) ->
Req = cowboy_req:stream_reply(200, Req0),
cowboy_req:stream_body("Hello\r\n", nofin, Req),
timer:sleep(1000),
cowboy_req:stream_body("World\r\n", nofin, Req),
timer:sleep(1000),
cowboy_req:stream_body("Cunked!\r\n", fin, Req),
{ok, Req, Opts}.
cowboy_req:stream_reply
でレスポンスするヘッダーを作成します。
cowboy_req:stream_body
レスポンスするbodyを作成します。第二引数のnofin
では送信するデータが続くことを意味し、fin
は送信データが終了したことを意味するようになります。
実行
最後にアプリケーションを実行させます。
rebar3 shell
shellで実行させ、curl http://127.0.0.1:8010
を実行してみます。
curl http://127.0.0.1:8010
Hello
World
Cunked!
無事に、レスポンスが返って来ました!!
まとめ
Cowboyとrebar3の情報が少なく苦戦しました。。。特にrebar3でのプロジェクトの作成、設定ファイルなどの情報がなく、ひたすらドキュメントをみてました。逆にドキュメントさえ読めば進められるので、ドキュメントに感謝です。Cowboyは割とサクサクとできましたし、サンプルも公開されているのでサンプルを見ながらすれば余裕です。
次は、もう少し、webっぽい動きのものを作っていきます。