ヽ(´・肉・`)ノログ

How do we fighting without fighting?

Sapporo.beamした

今日は2人もきた.

一人はGoの続きを調べていて,Kochaというwebフレームワークを触ってみていた.「チュートリアルがトラブルなく終わったので印象は良い」と言っていた.

最後にちょこっと見せてもらったけど,Railsのscaffoldに比べると動作が軽快でサクサク感があった.

もう一人(自分)はHeroku上でElixirを動かすことを試した.ここではその記録をまとめる.

HerokuにはBuildpackというHeroku上で言語やフレームワークを動かす仕組みがあり, Rubyもその仕組みを使って,Herokuがデフォルトで用意しているRuby用Buildpackを使って動作している.

このBuildpackではサードパーティ製のものも動かすことができる.

今回はこの仕組みを使って,ElixirのBuildpackを試す.

これとは別のElixirのBuildpackもあるようなんだけど,最後のコミットが3ヶ月前だったので今回は採用しなかった.

それではやってみよう.

プロジェクト作成

プロジェクトの名前はheroku_buildpack_with_plugという名前にした.

$ mix new heroku_buildpack_with_plug --bare
* creating README.md
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/heroku_buildpack_with_plug.ex
* creating test
* creating test/test_helper.exs
* creating test/heroku_buildpack_with_plug_test.exs

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

    cd heroku_buildpack_with_plug
    mix compile
    mix test

Run `mix help` for more information.
$ cd heroku_buildpack_with_plug

Plugを使う

RubyでのRackのようなHTTP用ミドルウェアがElixirにもありPlugと呼ばれている.

名前がもうちょっとオリジナリティがあるものだと検索しやすくてさらによかった.まあ仕方ない.

ともかくこれを利用してHTTPサーバーを動かしてみる.

Plugのinstallationを参考にmix.exsへapplicationとdepsを追加する.

defmodule HerokuBuildpackWithPlug.Mixfile do
  use Mix.Project

  def project do
    [ app: :heroku_buildpack_with_plug,
      version: "0.0.1",
      elixir: "~> 0.12.5",
      deps: deps ]
  end

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

  def deps do
    [ { :cowboy, github: "extend/cowboy" },
      { :plug, "0.3.0", github: "elixir-lang/plug" } ]
  end
end

書いたら依存関係に書いたライブラリを取得する.

$ mix deps.get

PlugのサンプルにあるHello Worldを適用して,起動してみる.

lib/heroku_buildpack_with_plug.ex へ

defmodule HerokuBuildpackWithPlug do
  import Plug.Connection

  def init(options) do
    # initialize options

    options
  end

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

IO.puts "Running HerokuBuildpackWithPlug with Cowboy on http://localhost:4000"
Plug.Adapters.Cowboy.http HerokuBuildpackWithPlug, []

と書き

$ mix run --no-halt lib/heroku_buildpack_with_plug.ex

を実行,ブラウザでlocalhost:4000にアクセスしてみると,Hello worldと表示されるはずだ.

Buildpack

次にBuilpackを適用してみる.

$ heroku create --buildpack "https://github.com/HashNuke/heroku-buildpack-elixir.git"Creating polar-earth-4093... done, stack is cedar
BUILDPACK_URL=https://github.com/HashNuke/heroku-buildpack-elixir.git
http://polar-earth-4093.herokuapp.com/ | git@heroku.com:polar-earth-4093.git
Git remote heroku added

……これでいいのだろうか?まあ進めてみる.

次にBuildpackの設定elixir_buildpack.configが必要だ.

サンプルが用意されているのでダウンロードしてそのまま利用させてもらう.

$ curl -O https://raw.github.com/HashNuke/heroku-buildpack-elixir/master/elixir_buildpack.config
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    98  100    98    0     0     97      0  0:00:01  0:00:01 --:--:--    97

ここまでの内容をgitへコミットして

$ git push -u heroku master

する.

webサーバーを起動するコマンドをProcfileへ指定する

$ heroku open

してみると,”Application Error”になっている.うまくいっていない.

$ heroku logs

すると

2014-03-19T15:49:38.388999+00:00 heroku[web.1]: Starting process with command `mix server -p 45597`
2014-03-19T15:49:40.395282+00:00 app[web.1]: ** (Mix) The task server could not be found
2014-03-19T15:49:41.647045+00:00 heroku[web.1]: Process exited with status 1
2014-03-19T15:49:41.663901+00:00 heroku[web.1]: State changed from starting to crashed

となっている.

mix server -p 45597

というコマンドが存在しないためのようだ.

heroku-buildpack-elixirのother-notes

Add your own Procfile to your application, else the default web task mix server -p $PORT will be used.

これだ.Procfileを作成してコマンドを書けばいいようだ.

ProcfileについてはHerokuにProcess Types and the Procfileというドキュメントがあった. web: に続いて実行したいコマンドを記述すればよいようだ.

web: mix run --no-halt lib/heroku_buildpack_with_plug.ex

Plugでポート番号を指定して起動する

$ git push
$ heroku open

すると”Application Error”になっている.まだうまくいっていない.

heroku logs してみると

2014-03-19T16:09:36.628910+00:00 heroku[web.1]: Starting process with command `mix run --no-halt lib/heroku_buildpack_with_plug.ex`
2014-03-19T16:09:39.254666+00:00 app[web.1]: lib/heroku_buildpack_with_plug.ex:1: warning: redefining module HerokuBuildpackWithPlug
2014-03-19T16:09:39.272263+00:00 app[web.1]: Running HerokuBuildpackWithPlug with Cowboy on http://localhost:4000
2014-03-19T16:10:36.824484+00:00 heroku[web.1]: Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch
2014-03-19T16:10:36.824740+00:00 heroku[web.1]: Stopping process with SIGKILL
2014-03-19T16:10:38.301630+00:00 heroku[web.1]: Process exited with status 137
2014-03-19T16:10:38.317056+00:00 heroku[web.1]: State changed from starting to crashed

ふむふむ.herokuの指定したPORT番号でwebサーバーを立ち上げなければならないようだ.

Plugでポート番号を指定するにはどうすればよいのか?

Plugのドキュメントを眺めたら例が書いてあった

Plug.Adapters.Cowboy.http HerokuBuildpackWithPlug, []

Plug.Adapters.Cowboy.http HerokuBuildpackWithPlug, [], port: 8080

とすると指定できる.

この状態でローカル環境で起動してlocalhost:8080へアクセスしてみるとつながる.なるほど.

あとはこのport値を PORT という環境変数から取得できればよい.

ドキュメントを探してみると,名前を指定した環境変数の取得はSystem.get_env/1でできるようだ. このときの取得できた値はバイナリ(文字列)なので,それを整数へ変換するのにKernel.binary_to_integer/1を使う.

結果的にこうなる.

port = System.get_env("PORT")
Plug.Adapters.Cowboy.http HerokuBuildpackWithPlug, [], port: binary_to_integer(port)

この状態で

PORT=8080 mix run --no-halt lib/heroku_buildpack_with_plug.ex

として起動するとブラウザからlocalhost:8080へアクセスでき

PORT=18888 mix run --no-halt lib/heroku_buildpack_with_plug.ex

として起動するとブラウザからlocalhost:18888へアクセスできることを確認する.

ポート番号がない場合はデフォルト状態で起動させる

それでは push してみよう.

$ git push

(snip)

== Compilation error on file lib/heroku_buildpack_with_plug.ex ==
** (ArgumentError) argument error
    :erlang.binary_to_integer(nil)
    lib/heroku_buildpack_with_plug.ex:19: (file)
    (elixir) src/elixir_lexical.erl:17: :elixir_lexical.run/2
    (elixir) lib/kernel/parallel_compiler.ex:91: anonymous fn/3 in Kernel.ParallelCompiler.spawn_compilers/8


 !     Push rejected, failed to compile elixir app

あら,pushができなかった.

binary_to_integer(nil) がエラーになっているようだ.環境変数が設定されない場合があって,そのときに nil が返ってきているのか?

この状況を回避するため,環境変数”PORT”に何も設定されていないときはデフォルトで起動することにする.

lib/heroku_buildpack_with_plug.ex はこんな感じだ

port = System.get_env("PORT")
if port do
IO.puts "Running HerokuBuildpackWithPlug with Cowboy on http://localhost:#{port}"
  Plug.Adapters.Cowboy.http HerokuBuildpackWithPlug, [], port: binary_to_integer(port)
else
  IO.puts "Running HerokuBuildpackWithPlug with Cowboy on http://localhost:4000"
  Plug.Adapters.Cowboy.http HerokuBuildpackWithPlug, []
end
$ git push
$ heroku open

ブラウザにHello Worldと表示される.うまくいったようだ.うひょー

まとめ

できたこと

heroku上にelixir製ミドルウェアplugを置いてHTTPリクエストを正常に処理できた.

サンプルはgithubに置いた

わからなかったこと

なぜ環境変数ありと環境変数なしの2回heroku_buildpack_with_plug.exが呼ばれたのか.

このエントリーをはてなブックマークに追加