今からでも間に合うErlang入門


Ubuntu 16.04でErlangのインストールから簡単なサンプルアプリケーションの作成まで行ってみます。

## Erlang開発環境構築（初期設定）

### 依存ファイルをインストール

```shell-session
sudo apt-get install build-essential libncurses5-dev openssl libssl-dev
sudo apt-get install curl git-core
```

### Erlangのバージョン管理、Kerlをインストール

```shell-session
mkdir -p ~/local/bin
cd ~/local/bin
curl -O https://raw.githubusercontent.com/kerl/kerl/master/kerl
chmod a+x kerl
```

### kerlでErlang 19.1をインストール

```shell-session
./kerl list releases
./kerl install 19.1 19.1
./kerl build 19.1 19.1
./kerl install 19.1 ~/erlang/19.1
```

インストールした Erlang 19.1 を有効化します。

```shell-session
. ~/erlang/19.1/activate
```

whichでerlコマンドが使える事が分かり開発環境が整いました。

```shell-session
which erl
# /home/username/erlang/19.1/bin/erl
```

## 簡単なネットワークアプリケーションの開発
Erlangで簡単なhttpdサーバを作ってみます。

### ビルドツールのrebarを使う
rebarを使うとコンパイル、テスト、配布用のtar.gzの作成などがコマンド一つでできるので便利です。

```shell-session
mkdir project
cd project
wget https://s3.amazonaws.com/rebar3/rebar3
chmod 0755 rebar3
```

### rebarで定形ファイル作成

```shell-session
./rebar3 new app httpd
# ===> Writing httpd/src/httpd_app.erl
# ===> Writing httpd/src/httpd_sup.erl
# ===> Writing httpd/src/httpd.app.src
# ===> Writing httpd/rebar.config
# ===> Writing httpd/.gitignore
# ===> Writing httpd/LICENSE
# ===> Writing httpd/README.md
```

## 実装
今回は簡単なhttpサーバを実装してみます。
処理の流れは以下のイメージ。

1. httpd_tcp_listener が新規接続してきたHTTPクライアントを受付
2. httpd_tcp_worker_sup の start_child() で1つのTCPストリームを処理するプロセスを起動（Erlangのプロセスはマイクロスレッドを意味します）
3. 起動したプロセスは httpd_tcp_worker の start_link() でTCPストリームからHTTPリクエストを読み込み、HTTPレスポンスを書き込む

以下の3ファイルを追加します。

```erlang:src/httpd_tcp_listener.erl
-module(httpd_tcp_listener).

-export([start_link/0]).

start_link() ->
    Pid = spawn_link(fun init/0),
    {ok, Pid}.

init() ->
    Port = 8888,
    Backlog = 10244,
    Options = [binary,
              inet6, % support both ipv4 and ipv6
              {active, false},
              {reuseaddr, true},
              {backlog, Backlog}
             ],
    {ok, Listen} = gen_tcp:listen(Port, Options),
    accept(Listen).

accept(Listen) ->
    case gen_tcp:accept(Listen) of
        {ok, Socket} ->
            {ok, Pid} = httpd_tcp_worker_sup:start_child(Socket),
            gen_tcp:controlling_process(Socket, Pid);
        {error, Reason} ->
            io:format("fail accept ~p~n", [Reason])
    end,
    accept(Listen).
```

```erlang:src/httpd_tcp_worker.erl
-module(httpd_tcp_worker).

-export([start_link/1]).

start_link(Socket)->
    Pid = spawn_link(fun() -> process(Socket, 0, 30 * 1000) end),
    {ok, Pid}.

process(Socket, Size, Timeout) ->
    case gen_tcp:recv(Socket, Size, Timeout) of
        {ok, Packet} ->
            Response = response(Packet),
            gen_tcp:send(Socket, Response),
            gen_tcp:close(Socket);
        {error, Reason} ->
            io:format("fail recv ~p~n", [Reason]),
            gen_tcp:close(Socket)
    end.

response(_Request) ->
    <<"HTTP/1.0 200 OK\r\nDate: Tue, 25 Oct 2016 10:21:33 GMT\r\nConnection: close\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 11\r\n\r\nhello world">>.
```

```erlang:src/httpd_tcp_worker_sup.erl
-module(httpd_tcp_worker_sup).

-export([start_link/0, start_child/1, init/1]).

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

start_child(Socket) ->
    supervisor:start_child(?MODULE, [Socket]).

init([]) ->
    Children = [
                {"httpd_tcp_worker", {httpd_tcp_worker, start_link, []}, temporary, 5, worker, []}
               ],
    {ok, {{simple_one_for_one, 0, 1}, Children}}.
```

また httpd_sup.erl の init([]) を編集し、追加実装した部分の起動処理を加えます。

```erlang:src/httpd_sup.erl
%% ...(省略)...
init([]) ->
    Children = [
                {"httpd_tcp_listener", {httpd_tcp_listener, start_link, []}, permanent, 5, worker, []},
                {"httpd_tcp_worker_sup", {httpd_tcp_worker_sup, start_link, []}, permanent, 5, supervisor, []}
               ],
    {ok, { {one_for_all, 0, 1}, Children} }.
```


## 動作確認
アプリケーションの実装が終わったので起動してみます。

```shell-session
../rebar3 compile && erl -pa _build/default/lib/*/ebin -eval 'application:start(httpd).' -noshell
```

ブラウザからアクセスするとhello worldと表示されました。
![hello_world.png](https://qiita-image-store.s3.amazonaws.com/0/39587/986a234b-d268-b499-6806-0aedc98279c6.png)

## まとめ
Erlangでネットワーク・サーバを実装するのは難しくない印象です。
ただsupervisorを理解して使いこなすのはある程度の経験が必要そうです。