Erlang AdventCalendar 14日目です。ちょびえと申します。
普段はGoやPHPがメインなのですが、前々からErlangは気になっておりまして、今回は
初めてErlang/OTPにチャレンジしてみた、という記事を書いてみたいと思います。
まずはやってみよう、ということで。
Erlangへのモチベーション
Golangでも簡単に動くサーバーはつくれるのですが、TCP接続を保持しながらhotswapをするという機能をGoで書くのは(少なくとも私にとっては)非常に面倒ですし難しいです。
既にビルドして動作しているGoバイナリに対してPlugin的なことをするのもPlatform依存になってしまうのでなんともなー、というところがあります。
自分はGoでMiddlewareを書いたりしていますが、そこらへんの機能に対しての解は気合でメモリ状況のMigrationを書く、とかそういう感じぐらいしか思い浮かばないので落とせない・落としづらいサーバーにGoってのはホント向かないな、というのが最近の感想です。
その点Erlangでは動かしながらhotswapするのがそこまでハードルが高くなく、24x365動かし続けなければいけないものを作るのにはもってこい!なので以前から興味がありました。
作るもの
Erlang/OTP自体は信頼性の高いシステムを作る上でよく使われる振る舞いを抽象化したものであり、それ単体ではErlang内で閉じたシステムになってしまいます。自分のメインがWeb系なので今日はErlang/OTPを使いつつ、Cowboyを作ってHello Worldをやってみるまでトライしてみようと思います。
既にErlangを学習されている方にとっては当たり前、な所ばかりになってしまいますが初めてのトライだとよく躓くのでだれかの参考になれば、と思います
事前準備
OSXでの準備とします。Erlangのインストールをhomebrew経由で行います。
2014/12現在ではErlang/OTP 17がインストールされます。
brew install erlang
% erl
Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Eshell V6.3 (abort with ^G)
%% Ctrl + Gをおしてq, enterで閉じれる
cowboyのドキュメントではerlang.mkを使っているのですがrebarのほうがよく耳にするのでrebarを使ってみます。
git clone git://github.com/rebar/rebar.git
cd rebar
./bootstrap
cp rebar /usr/local/bin
これでひとまず必要なものは揃いました。
ひな形の作成
rebar.configファイルというのにdependencyを記入します。今回使うのはcowboyだけなので下記のとおりとなります。
rebar.configはerlang項となります。
mkdir hello_erlang
cd hello_erlang
cat > rebar.config << EOF
{deps, [
{cowboy, ".*",
{git, "git://github.com/extend/cowboy.git", "master"}}
]}.
EOF
続いて、rebarのテンプレート機能を使って単純なErlang/OTPのアプリケーションを作成していきます。
chobie% rebar create-app appid=hello_erlang
WARN: Expected /Users/chobie/src/hello_erlang/deps/cowboy to be an app dir (containing ebin/*.app), but no .app found.
==> hello_erlang (create-app)
Writing src/hello_erlang.app.src
Writing src/hello_erlang_app.erl
Writing src/hello_erlang_sup.erl
最初ここらへんの仕組みがよくわからなくなかなか食指がわかなかったのですが、よくよく読んでみたらapplicationとsupervisor behaviourを使っている2つのテンプレ+リソースファイルというだけでした。
再度飛行機本の18.5らへんから読み直して把握できました。
続いてdependenciesのインストールをrebar g-dコマンドで行います。
chobie% rebar g-d
WARN: Expected /Users/chobie/src/hello_erlang/deps/cowboy to be an app dir (containing ebin/*.app), but no .app found.
==> hello_erlang (get-deps)
WARN: Expected /Users/chobie/src/hello_erlang/deps/cowboy to be an app dir (containing ebin/*.app), but no .app found.
Pulling cowboy from {git,"git://github.com/extend/cowboy.git","master"}
Cloning into 'cowboy'...
WARN: Expected /Users/chobie/src/hello_erlang/deps/cowlib to be an app dir (containing ebin/*.app), but no .app found.
WARN: Expected /Users/chobie/src/hello_erlang/deps/ranch to be an app dir (containing ebin/*.app), but no .app found.
==> cowboy (get-deps)
WARN: Expected /Users/chobie/src/hello_erlang/deps/cowlib to be an app dir (containing ebin/*.app), but no .app found.
WARN: Expected /Users/chobie/src/hello_erlang/deps/ranch to be an app dir (containing ebin/*.app), but no .app found.
Pulling cowlib from {git,"git://github.com/ninenines/cowlib.git","1.0.0"}
Cloning into 'cowlib'...
Pulling ranch from {git,"git://github.com/ninenines/ranch.git","1.0.0"}
Cloning into 'ranch'...
==> cowlib (get-deps)
==> ranch (get-deps)
依存関係のインストールが終わりました。現在のフォルダの構成は下記のとおりになります
chobie% tree
.
├── deps
├── ... 割愛
├── rebar.config
└── src
├── hello_erlang.app.src (resource)
├── hello_erlang_app.erl (application)
└── hello_erlang_sup.erl (supervisor)
78 directories, 367 files
アプリケーションを動かすまで編集
まずはresourceファイルを編集します。今回はcowboyを使っているのでapplicationsにcowboyを追加します。
app名.app.srcというのがよく使われるリソースファイルらしく、ここで環境変数もセット出来たりするようです。
diff --git a/src/hello_erlang.app.src b/src/hello_erlang.app.src
index 334ddc5..54b959c 100644
--- a/src/hello_erlang.app.src
+++ b/src/hello_erlang.app.src
@@ -1,11 +1,12 @@
{application, hello_erlang,
[
- {description, ""},
+ {description, "Hello Erlang"},
{vsn, "1"},
{registered, []},
{applications, [
kernel,
- stdlib
+ stdlib,
+ cowboy
]},
{mod, { hello_erlang_app, []}},
{env, []}
続いてsrc/hello_erlang.erlを作成します。
-module(hello_erlang).
-export([start/0]).
start() ->
ok = application:start(crypto),
ok = application:start(ranch),
ok = application:start(cowlib),
ok = application:start(cowboy),
ok = application:start(hello_erlang).
現時点ではまだHTTP周りは動作しませんが、一度コンパイルして動作するかをチェックします。
application:which_applications/0を実行してhello_erlangが含まれていることを確認します。
chobie% rebar co
==> cowlib (compile)
==> ranch (compile)
==> cowboy (compile)
==> hello_erlang (compile)
# -paはコードパスの最初にディレクトリを追加する意味があります。-sはhello_erlang:start()を実行する指定です
chobie% erl -pa ebin deps/*/ebin -s hello_erlang
Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Eshell V6.3 (abort with ^G)
1> application:which_applications().
[{hello_erlang,"Hello Erlang","1"},
{cowboy,"Small, fast, modular HTTP server.","2.0.0-pre.1"},
{cowlib,"Support library for manipulating Web protocols.",
"1.0.0"},
{ranch,"Socket acceptor pool for TCP protocols.","1.0.0"},
{crypto,"CRYPTO","3.4.2"},
{stdlib,"ERTS CXC 138 10","2.3"},
{kernel,"ERTS CXC 138 10","3.1"}]
問題なさそうなのでHTTPサーバーを動かす部分を追加します。
hello_erlang_appにcowboyを動作させるコードを追加し、新たにhello_erlang_handlerモジュールを追加します。
diff --git a/src/hello_erlang_app.erl b/src/hello_erlang_app.erl
index b8a10a2..4b97d34 100644
--- a/src/hello_erlang_app.erl
+++ b/src/hello_erlang_app.erl
@@ -10,6 +10,14 @@
%% ===================================================================
start(_StartType, _StartArgs) ->
+ Dispatch = cowboy_router:compile([
+ {'_', [
+ {"/", hello_erlang_handler, []}
+ ]}
+ ]),
+ {ok, _} = cowboy:start_http(http, 100, [{port, 8081}], [
+ {env, [{dispatch, Dispatch}]}
+ ]),
hello_erlang_sup:start_link().
stop(_State) ->
diff --git a/src/hello_erlang_handler.erl b/src/hello_erlang_handler.erl
new file mode 100644
index 0000000..92b512a
--- /dev/null
+++ b/src/hello_erlang_handler.erl
@@ -0,0 +1,9 @@
+-module(hello_erlang_handler).
+
+-export([init/2]).
+
+init(Req, Opts) ->
+ Req2 = cowboy_req:reply(200, [
+ {<<"content-type">>, <<"text/plain">>}
+ ], <<"Hello world!">>, Req),
+ {ok, Req2, Opts}.
\ No newline at end of file
先程もやったとおり↓のコマンドで動かします
erl -pa ebin deps/*/ebin -s hello_erlang
小一時間ぐらい↓のようなエラーがでて動かないな、と思っていたんですが最近cowboyが2系になったようなのでcowboy1のやり方でやっていたので動作しなかっただけという(ヽ´ω`)
Error in process <0.154.0> with exit value: {[{reason,undef},{mfa,{hello_erlang_handler,init,2}},{stacktrace,[{hello_erlang_handler,init,[{http_req,#Port<0.1345>,ranch_tcp,keepalive,<0.154.0>,<<3 bytes>>,'HTTP/1.1',{{127,0,0,1},52047},<<9 bytes>>,undefined,8081,<<1 byte>>,undefined,<<0 bytes>>,[],[{<<4 bytes>>,<<14 bytes>>},{<<10 bytes>>,<<10 bytes>>},{<<13 bytes>>,<<9 bytes>>},{<<6 bytes>>,<<74 bytes>>},{<<10 bytes>>,<<119 bytes>>},{<<15 bytes>>,<<19 bytes>>},{<<15 bytes>>,<<23 bytes>>},{<<6 bytes>>,<<30 bytes>>}],[],waiting,<<0 bytes>>,undefined...
とりま、落ちてもsupervisorがちゃんと監視して戻してくれているんだなー、というのはわかりました。
最後に、curlで動作確認をしておしまいです。
chobie% curl http://localhost:8081/
Hello world!
動いた!
まとめ
- まずはapplication, supervisorあたりの仕組みを理解しよう
- ひな形から書き換えるところはそんなにないので臆病にならずにチャレンジしてみよう
- うごくと嬉しい
あと、課題としてはprintfデバッグどうやるんだっけ?ってのとerl_crash.dumpを自在に読める(少なくとも読んでエラーの原因が分かる)ようになればもうちょいマシにすすめるはず…
多分こっからweb系の機能をひと通りやれるように勉強しつつ、一般的なErlang/OTPアプリケーションの勉強をしていくとなんとなく作りたいものがはっきりとしてくるんだろうなぁ、と感じています。