スポンサーリンク

2016年9月12日

alchemist report 001

Goal

  • Plug+Cowboyのサンプルを作成する
  • Phoenixフレームワーク v0.1.1のControllerを実装する

Dev-Environment

  • OS: Windows8.1
  • Erlang: OTP19
  • Elixir: v1.3.0
    • Plug: v1.1
    • Cowboy: v1.0

Prerequisite 1: Erlang、Elixirはインストール済みであること

Prerequisite 2: プロジェクトは各自適当に作ってください

Context

Caution: 知っている人にとってはなんてことない記事のため、有識者の方はブラウザバック推奨!

Phoenixフレームワークの動きを知るため、Plugについて動作検証しました。
また、Phoenixフレームワーク(v0.1.1)のControllerをすごく簡単に実装してみました。
(コントローラ単独で動かすために、Routerのコードも混じってしまいましたが…)

PlugとCowboyを使ってプレーンテキストで”Hello World!”を表示してみる

まずは、mixファイルにPlugとCowboyを追加しましょう!

File: mix.exs

defmodule PlugCowboyExample.Mixfile do
  ...

  def application do
    [applications: [:cowboy, :plug, :logger]]
  end

  defp deps do
    [{:cowboy, "~> 1.0", optional: true},
     {:plug, "~> 1.1"}]
  end
end

Example:

> mix deps.get
次にモジュールPlugを実装して、レスポンスを返すコードを作ってみましょう。
(モジュールPlugを実装するには、init/1とcall/2を実装する必要があります)

lib/example.ex

defmodule Example do
  import Plug.Conn

  def init(options), do: options  def call(conn, options) do
    word = Keyword.get(options, :word, "World")

    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, "Hello #{word}")
  end
end
実行してみましょう!

Example:

iex> Plug.Adapters.Cowboy.http Example, []
上記を実行後、”http://localhost:4000“にアクセスしてみてください。
プレーンテキストで”Hello World!!”が表示されていると思います。

PhoenixのControllerを(一部)再現してみる

Phoenixフレームワーク v1.2.0のソースコードだと間違いなく、色々と変わっていることでしょう。
構造を知るために公開されている中で一番古いPhoenixのControllerをちょっとだけ実装してみます。
マクロによるControllerの実装だが、v0.1.1だとimportやuseなどを展開しているだけです。
ここでは、プレーンテキストの部分のみ実装します。(他のコードも大差ないです)

File: lib/controller.ex

defmodule Controller do
  import Plug.Conn

  defmacro __using__(_options) do
    quote do
      import Plug.Conn
      import unquote(__MODULE__)
      use Plug.Builder
      @before_compile unquote(__MODULE__)
    end
  end

  defmacro __before_compile__(_env) do
    quote do
      def start do
        IO.puts ">> Running #{__MODULE__} with Cowboy"
        Plug.Adapters.Cowboy.http __MODULE__, []
      end
    end
  end

  def text(conn, text) do
    text(conn, 200, text)
  end
  def text(conn, status, text) do
    send_response(conn, status, "text/plain", text)
  end

  def not_found(conn, method, path) do
    text conn, 404, "No route matches #{method} to #{inspect path}"
  end

  def send_response(conn, status, content_type, data) do
   conn
   |> put_resp_content_type(content_type)
   |> send_resp(status, data)
  end
end
本来であれば、before_compile/1の部分はRouterが担っていたのですが、
Controller単体で動かすためにコードを持ってきています。
こうなると(ルーティングしていないですが)Controller+Routerのような状態ですかね。
(Controllerと言えるほどでもなく、Routerと言えるほどでもないですが…これはその内)
続いて、上記のマクロを展開しコントローラとして実装するモジュールを作成します。
defmodule ControllerExample do
  use Controller

  plug :show, [word: "World"]

  def show(conn, _options) do
    text conn, "Hello World!!"
  end
end
実行してみましょう!

Example:

iex> ControllerExample.start
上記を実行後、”http://localhost:4000“にアクセスしてみてください。
先ほどとおなじように、プレーンテキストで”Hello World!!”が表示されていると思います。
初期段階の実装だとほぼPlugですね。
これに色々なページを出したくなるとRouterが必要になりますね。
ほとんどの方は知っていると思うのですが、Phoenixフレームワークの実装としては、
Router側に定義した内容によってコントローラへのマッピングを作っていて、
そのマッピングしているコントローラのアクション用メソッドを呼び出しています。
ControllerExampleの関数一覧を表示してみると、下記のようになっています。
Plug.Builderを使うことにより、init/1とcall/2が生成されていますね。
ここら辺に関しては、この方記事が参考になると思います。
(参考: Elixir の DSL がどう実装されているか、マクロの展開順を追ってみた(Plug.Builder を例に))
iex> ControllerExample.__info__(:functions)
[call: 2, init: 1, show: 2, start: 0]
もっと色々な説明を書かないと足りないと思いますが、今日はもう時間がないのでここまで…

Bibliography

人気の投稿