Elixir: PlugCowboyで簡単なルーティングを試す

[PlugCowboy](https://github.com/elixir-plug/plug_cowboy#plugcowboy)は、ErlangのwebサーバーCowboy用のPlugアダプタです。Pulgのドキュメントは「[Installation](https://hexdocs.pm/plug/readme.html#installation)」でPlugアダプタにPlugCowboyを使うものとしています。もっとも、[リリースが2018年10月21日](https://github.com/elixir-plug/plug_cowboy/releases)と日が浅いため、まとまった情報が少なく、比較的新しい記事でも古い情報だったりします。本稿では、インストールから簡単なルーティングまでの流れをご紹介します。

[追記: 2019/06/04] 公式ドキュメント「[Plug](https://hexdocs.pm/plug/readme.html)」にもとづいて、本文を大幅に加筆し、テストのコードを加えました。

# mixでElixirプロジェクトをつくる
まずは、[`mix new`](https://hexdocs.pm/mix/Mix.Tasks.New.html)コマンドでElixirプロジェクトをつくりましょう。`--sup`オプションを加えると、監視ツリーの含まれたOTPアプリケーションとしてPlugを起動できるひな形ができ上がります。つくられるファイルは以下のとおりです。

```
$ mix new my_plug --sup
```

```
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/my_plug.ex
* creating lib/my_plug/application.ex
* creating test
* creating test/test_helper.exs
* creating test/my_plug_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd my_plug
    mix test

Run "mix help" for more commands.
```


# PlugCowboyをインストールする
アプリケーションのディレクトリ(`my_plug`)に切り替えたら、`mix.exs`につぎのようなPlugCowboyの依存関係を加えます。

```elixir:mix.exs
defmodule MyPlug.MixProject do
  # ...[中略]...

  defp deps do
    [
      {:plug_cowboy, "~> 2.0"}  # 追加
    ]
  end
end
```
そして、`mix deps.get`コマンドで依存関係を解決してください。

```
$ mix deps.get
```

```
Resolving Hex dependencies...
Dependency resolution completed:
Unchanged:
  cowboy 2.6.3
  cowlib 2.7.3
  mime 1.3.1
  plug 1.8.2
  plug_cowboy 2.0.2
  plug_crypto 1.0.0
  ranch 1.7.1
```


# サンプルコードを動かす
mixプロジェクトのファイル`lib/my_plug.ex`のモジュール`MyPlug`に、つぎのコード001のテスト用関数を定めます(「[Hello world](https://hexdocs.pm/plug/readme.html#hello-world)」参照)。

#### コード001■`lib/my_plug.ex`
```elixir:lib/my_plug.ex
defmodule MyPlug do
  import Plug.Conn

  def init(options) do
    # optionsの初期化
    options
  end

  def call(conn, _opts) do
    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, "Hello world")
  end
end
```

そして、mixプロジェクトを`iex`のセッションで開いてください。

```
$ iex -S mix
```

Plugアプリケーションの中で前掲モジュールを動かすのがつぎのコードです。ターミナルからサーバーが起ち上がります。[http://localhost:4000/](http://localhost:4000/)のURLを開くと、ページに"Hello world"のテキストが示されるでしょう。

```elixir
iex> {:ok, _} = Plug.Cowboy.http MyPlug, []
{:ok, #PID<0.593.0>}
```

#### 図001■ローカルサーバーで開いたアプリケーションのページ
![elixir_plug_009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262045/054f0005-7f2e-0f3e-8f3b-0090983355c0.png)



# `Plug.Conn`構造体
前掲コード001について、説明を加えましょう。Plugモジュールは、つぎのふたつの関数を実装しなければなりません。

- `init/1`: 引数に受け取ったオプション(`options`)を初期化します。
- `call/2`: 接続(`conn`)と初期化されたオプション(`_opts`)を受け取って、新たな接続にして返します(接続はイミュータブルです)。

データは接続から直に読み込まれ、パターンマッチングなどにより処理が加えられます。接続を表すのが[`Plug.Conn`](https://hexdocs.pm/plug/Plug.Conn.html)構造体(`%Plug.Conn{}`)です。Plugモジュールは、データを処理するために`Plug.Conn`に定められた関数が使えます。前掲コード001では、[`put_resp_content_type/3`](https://hexdocs.pm/plug/Plug.Conn.html#put_resp_content_type/3)と[`send_resp/3`](https://hexdocs.pm/plug/Plug.Conn.html#send_resp/3)が用いられました。

接続はサーバーとのインタフェースです。たとえば、`send_resp/3`は得られたステータスと本文を、ただちにクライアントに返します。

# `Plug.Router`を使う
ルーターはPlugです。[`Plug.Router`](https://hexdocs.pm/plug/Plug.Router.html)により定められ、リクエストのパスとメソッドにもとづいてルーティングします。新たに`lib/router.ex`として、つぎのコード002のようなルーターモジュール(`MyPlug.Router`)をつくりまししょう(「[`Plug.Router`](https://hexdocs.pm/plug/readme.html#plug-router)」参照)。

#### コード002■`lib/router.ex`
```elixir:lib/router.ex
defmodule MyPlug.Router do
	use Plug.Router

	plug :match
	plug :dispatch

	get "/hello" do
		send_resp(conn, 200, "Japan\n")
	end

	match _ do
		send_resp(conn, 404, "not found\n")
	end
end
```

ルーターには独自のPlugパイプラインがあります。前掲コード002では、つぎのふたつのPlugを順に呼び出しました。

- `:match`: マッチングするルートを探します。
- `:dispatch`: マッチングしたコードを実行します。

ほかにもさまざまなPlugがあり、それぞれのPlugパイプラインをもちます。実行は記述した順です。たとえば、つぎのコードでは、ルートのマッチング前に[`Plug.Logger`](https://hexdocs.pm/plug/Plug.Logger.html)が加わります。

```elixir
plug Plug.Logger
plug :match
plug :dispatch
```

`Plug.Router`は、すべてのルートをひとつの関数にコンパイルします。そして、Erlang VMにより、ルートの検索は、ツリー状に最適化されます。ルートを逐次マッチングする線型状ではありません。Plugのルーティングは極めて高速になるのです。各ルートはPlugの仕様にしたがって、接続を返さなければなりません。

モジュールの最後の`match`ブロックは、いずれにもマッチングしなかったルートを拾います。これは関数のエラー(`FunctionClauseError`)を避けるためです。


# ルーターを監視ツリーに加える
`mix new`コマンドに`--sup`オプションを添えましたので、Plugパイプラインは監視ツリーのもとで動かせます。そのために呼び出すのが、[`child_spec`](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html#child_spec/1)関数です。`lib/my_plug/application.ex`に、つぎのように監視する子プロセスを加えてください。

```elixir:lib/my_plug/application.ex
defmodule MyPlug.Application do

  def start(_type, _args) do
    children = [
      Plug.Cowboy.child_spec(scheme: :http, plug: MyPlug.Router, options: [port: 4001])
    ]

  end
end
```

アプリケーションを起動するのは[`mix run`](https://hexdocs.pm/mix/Mix.Tasks.Run.html)コマンドです。そのままではすぐに終了してしまうので、`--no-halt`オプションで動作し続けるようにします。

```
$ mix run --no-halt
```

別に開いたコマンドラインツールから、リクエストを送りましょう。ここでは、[curl](https://curl.haxx.se/)を使います。

```
$ curl http://localhost:4001/hello
Japan
$ curl http://localhost:4001/oops
not found
```

これで、監視ツリーに加えたルーターにより、リクエストをルーティングすることができました(図002)。

#### 図002■ルーティングされたページ
![elixir_plug_010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262045/687375c9-3dd6-78fb-c56c-c7abb5d742dc.png)


# Plugをテストする
Plugに備わっているテスト用のモジュールが`Plug.Test`です。このモジュールを使えば、Plugが簡単にテストできます。ひな形につくられている`test/my_plug_test.exs`をつぎのコード003のように書き替えてください。

#### コード003■`test/my_plug_test.exs`
```elixir:test/my_plug_test.exs
defmodule MyPlugTest do
  use ExUnit.Case, async: true
  use Plug.Test

  @opts MyPlug.Router.init([])

  test "hello Japanを返す" do
    # テスト用の接続をつくる
    conn = conn(:get, "/hello")

    # Plugを起動する
    conn = MyPlug.Router.call(conn, @opts)

    # レスポンスとステータスを確認する
    assert conn.state == :sent
    assert conn.status == 200
    assert conn.resp_body == "Japan\n"
  end
end
```

`mix test`は正しく終了するはずです。

```
$ mix test
.

Finished in 0.03 seconds
1 test, 0 failures
```