この記事は Elixir Advent Calendar 2016 - Qiita の 19 日目の記事です。
Elixir歴3ヶ月ほどの新人です。まだまだ分かっていませんが、Elixir楽しいです。
今回はPhoenix以外のWEBフレームワークについて調べてみました。
Elixirで何らかのWEBアプリケーションを作る場合はPhoenixを選ぶのが普通だと思います。
Railsと似ていて入門しやすく、情報量からいっても妥当な選択だと思うのですが、
他の選択肢もあるだろうということで、今回はWebmachineを少し調べてみました。
Webmachineとは
WebmachineはRiakで有名なBashoが発祥のerlangで書かれたソフトウェアです。
実際にはWEBフレームワークではなくREST Toolkitと称している通り、
フレームワークとしてみると機能が足りない点が多いので、
足りないものは自分で他のライブラリを使うなりして補っていくスタイルです。
特徴的な点としては、Resourceを定義したのちに、その中でひたすら関数で設定などを書いていく点です。
Phoenixと比べるとより関数型らしいと言えるかもしれません。
アプリケーションを書いてみる
まずはともかく、サンプルアプリケーションを書いてみましょう。
create project
mixで新しいプロジェクトを作ります。
$ mix new elixir_webmachine_sample --sup
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/elixir_webmachine_sample.ex
* creating test
* creating test/test_helper.exs
* creating test/elixir_webmachine_sample_test.exs
$ cd elixir_webmachine_sample
mix.exsにwebmachineの依存を追加
Webmachineの依存を追加します。
mix.exs
defmodule ElixirWebmachineSample.Mixfile do
use Mix.Project
def project do
[app: :elixir_webmachine_sample,
version: "0.1.0",
elixir: "~> 1.3",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps()]
end
def application do
[applications: [:logger, :webmachine], # 追加
mod: {ElixirWebmachineSample, []}]
end
defp deps do
[{:webmachine,
git: "https://github.com/webmachine/webmachine.git",
branch: "master"}] # 追加
end
end
ライブラリの取得をしておきます。
$ mix do deps.get, compile
サーバを子プロセスとして起動させる
Webmachineを起動する設定を書いていきます。
lib/elixir_webmachine_sample.ex
defmodule ElixirWebmachineSample do
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
# Webmachine用設定
web_config = [ip: {127,0,0,1},
port: 8080,
dispatch: []]
# 子プロセスとしてサーバをスタートさせる
children = [
worker(:webmachine_mochiweb,
[web_config],
function: :start,
modules: [:mochiweb_socket_server])
]
opts = [strategy: :one_for_one, name: ElixirWebmachineSample.Supervisor]
Supervisor.start_link(children, opts)
end
end
これを書いたら、以下でサーバを起動させてみます。
$ mix run --no-halt
http://127.0.0.1:8080/
にアクセスしてみます。
NotFoundですがサーバ自体は動いていそうです。
サーバはCtrl-Cなりで一旦終了させておきましょう。
リソースの追加
WebmachineではRESTでいうところのリソース単位でファイルを作成し、処理を書いていきます。
まずはリソースファイルを保存するためのディレクトリを作成します。
$ mkdir -p lib/elixir_webmachine_sample/resources
リソースの作成
ひとつリソースを作ってみます。あまりリソースっぽくないですが、helloとします。
lib/elixir_webmachine_sample/resources/hello.ex
defmodule ElixirWebmachineSample.Hello do
# 初期化作業
def init(_) do
{:ok, nil}
end
# デフォルトではto_htmlという関数が呼ばれるようになっているので、実装しておく。
def to_html(req_data, state) do
{"<html><body>Hello, World!</body></html>", req_data, state}
end
end
サーバにリソースを追加する
lib/elixir_webmachine_sample.ex
# Webmachine用設定
web_config = [ip: {127,0,0,1},
port: 8080,
dispatch: [
{[], ElixirWebmachineSample.Hello, []}
]]
さきほどと同様にサーバを起動させ、アクセスしてみます。
表示されました!
JSON APIを作ってみる
これだけだとあまり面白みがないので、JSONを返すAPIっぽいものも作ってみましょう。
Userリソースを作成し、Acceptヘッダの値によってhtmlかjsonのどちらかを返すようにします。
config(dispatch)変更
dispatchに新しいリソースを追加しておきます。
先頭に['user', :user_id]
とありますがこれはルーティングの設定で、
この定義で「/user/1」でアクセスするとUserリソースが選ばれ、
のちに専用のメソッドで:user_id
を指定することでこの例だと1を取得することができます。
ここの指定方法はなかなか独特ですが、詳しくはこちらを参考にしてください。
さらに注意点なのですが、文字列の設定を行う場合は、
string(「"」で囲んだもの)ではなくChar List(「'」で囲んだもの)で設定してください。
古めのerlangのライブラリではstring(binary)が使えないらしく、Char Listを使うようです。
常識なのかもしれませんが自分は気付かず、エラーになるでもなく、
しかし意図した動作にならなくてハマりました。
lib/elixir_webmachine_sample.ex
# Webmachine用設定
web_config = [
ip: {127,0,0,1},
port: 8080,
dispatch: [
{[], ElixirWebmachineSample.Hello, []},
{['user', :user_id], ElixirWebmachineSample.User, []} # <-ここ!
]
]
リソース追加
Userリソースを新規作成します。
(ところどころデバッグ用の出力をいれてあります)
lib/elixir_webmachine_sample/resources/user.ex
defmodule ElixirWebmachineSample.User do
def init(_) do
{:ok, nil}
end
# 使用可能なHTTP Methodを指定する
def allowed_methods(req_data, state) do
# ここではGETのみ許可。atomで指定すること。
# 他のメソッドでアクセスされるとMethod Not Allowedが起きる。
{[:GET], req_data, state}
end
# Content TypeをWebmachineに伝えるためのcallback
def content_types_provided(req_data, state) do
# Acceptヘッダの内容により後続の処理を分離できる。
types = [
{'application/json', :to_json},
{'text/html', :to_html}
]
{types, req_data, state}
end
# jsonを返す場合のメソッド
def to_json(req_data, state) do
req_data |> inspect |> IO.puts
# HTTPメソッドはこれで取得できる。
# 今回はGETのみ許可なので意味はないが、複数使用可能な場合はこの値で処理を分岐させる。
method = :wrq.method(req_data)
IO.puts "HTTP Metod:#{method}"
# :wrq.path_infoを使うことでpathに埋め込まれた値を取得できる。
# 取得した値もstringではなくChar Listなので注意すること!
user_id = :wrq.path_info(:user_id, req_data)
case get_user(user_id) do
nil ->
# {:halt, レスポンスコード}を戻り値にするとエラーを返せる。
{{:halt, 404}, req_data, state}
user ->
{:ok, content} = Poison.encode(user)
{content, req_data, state}
end
end
# htmlを返す場合のメソッド。こちらはエラー処理などは手抜き。
def to_html(req_data, state) do
req_data |> inspect |> IO.puts
user_id = :wrq.path_info(:user_id, req_data)
user = get_user(user_id)
{"<html><body>Hello, #{user.name} </br>(text/html)</body></html>", req_data, state}
end
defp get_user(user_id) do
IO.puts user_id
users = %{
'1' => %{user_id: 1, name: "TARO TANAKA"},
'2' => %{user_id: 2, name: "JIRO SATO"}
}
Map.get(users, user_id)
end
end
ここでの肝はWebmachineで規定されたcallback用関数群(init, allowed_methods, content_types_provided)です。
決められた関数に必要な設定をしておくことで自動で読み込まれるわけです。
これらの関数は認証されているかどうかのチェック用など他にもいろいろあります。
Resource Functions · webmachine/webmachine Wiki
実行
サーバを起動し、さっそく試してみましょう。
ライブリロードなんて機能は無さそうなので、再起動してください。
json
$ curl -i -X GET -H 'Accept: application/json' http://localhost:8080/user/1
HTTP/1.1 200 OK
Vary: Accept
Server: MochiWeb/2.12.2 WebMachine/1.10.8-49-g6c42658 (cafe not found)
Date: Sat, 17 Dec 2016 19:29:33 GMT
Content-Type: application/json
Content-Length: 34
{"user_id":1,"name":"TARO TANAKA"}
html
$ curl -i -X GET -H 'Accept: text/html' http://localhost:8080/user/1
HTTP/1.1 200 OK
Vary: Accept
Server: MochiWeb/2.12.2 WebMachine/1.10.8-49-g6c42658 (cafe not found)
Date: Sat, 17 Dec 2016 19:29:58 GMT
Content-Type: text/html
Content-Length: 61
<html><body>Hello, TARO TANAKA </br>(text/html)</body></html>
意図通り動いているようです。
感想
少し触っただけでしかありませんが、Phoenixと比べるとかなりシンプルであり、
マクロもなく関数だけでなんでも行う感じが、こういうのでいいんだよ感と言いますか
個人的にはかなり好みです。
一方で複雑なアプリをつくる場合は機能不足感は否めないので、
採用するなら単純なつくりのものが向いているでしょう。
以前Goのバッチで統計を取得するWebAPIをつくってみたことがあり、
Elixirで同じようなことをしたいと思っていたのですが、
軽量なのでこういう用途にも使えそうな気がしています。
(今回そこまでやってみたかったけど力尽きたのでいずれ・・・)
参考
- webmachine/webmachine: A REST-based system for building web applications.
- Webmachine in Elixir Tutorial, Part 1 - Sean Cribbs
- Designing and implementing a REST API (with Webmachine)
- Web Development with Webmachine for Erlang - Wikiversity
- Webmachine コトハジメ
- webmachineとAJAX - 備忘録、はじめました。
- Resource Functions · webmachine/webmachine Wiki