LoginSignup
20
16

More than 5 years have passed since last update.

cowboy2とectoでRESTサーバ

Last updated at Posted at 2015-08-15

cowboyectoでシンプルなRESTサーバを実装してみます。
(素直にPhoenixを使えとの神の声が聞こえてきそうですが、勉学目的で地道に行ってみます)

以下の通り動作するサーバを実装します:

$ curl localhost:3000/hello-school-live/characters
["丈槍 由紀","佐倉 慈","若狭 悠里","恵飛須沢 胡桃","直樹 美紀"]

このサンプルでは最新のcowboy 2系を利用しています(@ColdFreak さん、ご指摘ありがとうございます)。cowboy 2系ではErlang 18以上が必要となります。また、1系のサンプルについてはソースのみですがこちらにあります。

事始め

まずmixでアプリケーションを作成します:

$ mix new --sup hello_school_live
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/hello_school_live.ex
* creating test
* creating test/test_helper.exs
* creating test/hello_school_live_test.exs

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

    cd hello_school_live
    mix test

Run `mix help` for more commands.

できたディレクトリに移動しておきます:

$ cd hello_school_live

Hello, cowboy!

cowboyは言わずと知れたErlang向けHTTPサーバです、Phoenixも内部ではcowboyを使っています。まず動作確認用に「Hello, School Live!」という文字列を返却するページを作ってみます。

mix.exsにcowboyへのdependencyを追記します。

mix.exs

def application do
  [applications: [:logger, :cowboy], # ← `:cowboy`を追記
   mod: {HelloSchoolLive, []}]
end

...省略...

defp deps do
  [
    {:cowboy, github: "ninenines/cowboy"}, # ← 追記
  ]
end

lib/hello_school_live.exにルーティングの設定とcowboyの起動処理を追記します。

lib/hello_school_live.ex

def start(_type, _args) do
  import Supervisor.Spec, warn: false

  dispatch = :cowboy_router.compile routes # ← 追記
  {:ok, _} = :cowboy.start_http :http, 100, [{:port, 3000}], [{:env, [{:dispatch, dispatch}]}] # ← 追記

  children = [
  ...省略...
end

# ↓の関数を追記
defp routes do
  [
    {:_, [
          {"/hello-school-live", HelloSchoolLive.Handlers.HelloSchoolLive, []}
        ]
    }
  ]
end

ハンドラを格納するディレクトリを作って:

$ mkdir -p lib/hello_school_live/handlers

lib/hello_school_live/handlers/hello_school_live.exを以下内容で作成します:

lib/hello_school_live/handlers/hello_school_live.ex

defmodule HelloSchoolLive.Handlers.HelloSchoolLive do
  def init(req, opts) do
    req2 = :cowboy_req.reply 200, [{"content-type", "text/plain"}], "Hello, School Live!", req
    {:ok, req2, opts}
  end
end

dependencyを取得した後、起動してみます、

$ mix deps.get
...省略...
$ iex -S mix
...省略...

ブラウザで http://localhost:3000/hello-school-live にアクセスして「Hello, School Live!」が表示されることを確認します。

Hello, ecto!

ectoは「Elixir向けのデータベースラッパーと統合クエリ言語」と本家では紹介されています、個人的には綺麗にRailsから分離されたActiveRecordのElixir板と理解するのがわかり易いかと。ここではデータベースとしてPostgreSQLを利用します、その他のデータベースを利用する場合、アダプターを適宜置き換えることで動くはず…。
まず動作確認用にiexでcharactersテーブルから一覧取得できるようにしてみます。

mix.exsにectoへのdependencyを追記します:

def application do
  [applications: [:logger, :cowboy, :postgrex, :ecto], # ← 追記
   mod: {HelloSchoolLive, []}]
end

...省略...

defp deps do
  [
    {:cowboy, github: "ninenines/cowboy"},
    {:postgrex, ">= 0.0.0"}, # ← 追記
    {:ecto, "~> 0.14.3"} # ← 追記
  ]
end

依存関係を解決してコンパイルしておきます:

$ mix do deps.get, compile
...省略...

mix -hを実行するとecto関連のタスクが追加されているのがわかります

$ mix -h
mix                    # Run the default task (current: mix run)
...
mix do                 # Executes the tasks separated by comma
mix ecto.create        # Create the storage for the repo
mix ecto.drop          # Drop the storage for the repo
mix ecto.gen.migration # Generate a new migration for the repo
mix ecto.gen.repo      # Generate a new repository
mix ecto.migrate       # Run migrations up on a repo
mix ecto.rollback      # Rollback migrations from a repo
mix escript.build      # Builds an escript for the project
...

以下コマンドでリポジトリを作成します:

$ mix ecto.gen.repo
* creating lib/hello_school_live
* creating lib/hello_school_live/repo.ex
* updating config/config.exs
Don't forget to add your new repo to your supervision tree
(typically in lib/hello_school_live.ex):

    worker(HelloSchoolLive.Repo, [])

上記出力にあるとおり、supervision treeにリポジトリを追加する必要があります。lib/hello_school_live.exに以下を追記します:

lib/hello_school_live.ex

def start(_type, _args) do
  ...省略...

  children = [
    worker(HelloSchoolLive.Repo, [])
  ]

  ...省略...
end

また作成されたconfig/config.exsのリポジトリ設定を環境に合わせて設定します、以下は私の環境の例です

config/config.exs

config :hello_school_live, HelloSchoolLive.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "hello_school_live_repo",
  username: "postgres",
  password: "postgres",
  hostname: "localhost"

以下コマンドでデータベースを作成します:

$ mix ecto.create
Compiled lib/hello_school_live/handlers/hello_school_live.ex
Compiled lib/hello_school_live.ex
Compiled lib/hello_school_live/repo.ex
Generated hello_school_live app
The database for HelloSchoolLive.Repo has been created.

以下コマンドでマイグレーションファイルを作成します:

$ mix ecto.gen.migration characters
* creating priv/repo/migrations
* creating priv/repo/migrations/20150815055420_characters.exs

priv/repo/migrations/<TIMESTAMP>_characters.exsを以下の通り編集します:

priv/repo/migrations/TIMESTAMP_characters.exs

def change do
  create table(:characters) do # ← 追記
    add :name, :string
  end
end

以下コマンドでマイグレーションを適用します:

$ mix ecto.migrate

14:56:18.082 [info]  == Running HelloSchoolLive.Repo.Migrations.Characters.change/0 forward

14:56:18.082 [info]  create table characters

14:56:18.087 [info]  == Migrated in 0.0s

次にモデルを作成します。まずモデルを格納するディレクトリを作成して、

$ mkdir lib/hello_school_live/models

lib/hello_school_live/models/character.exを以下内容で作成します。

lib/hello_school_live/models/character.ex

defmodule HelloSchoolLive.Models.Character do
  use Ecto.Model
  schema "characters" do
    field :name, :string
  end
end

iexで動作確認してみます:

$ iex -S mix
iex(1)> character = %HelloSchoolLive.Models.Character{name: "丈槍 由紀"}
iex(2)> HelloSchoolLive.Repo.insert! character
iex(3)> HelloSchoolLive.Repo.insert! %HelloSchoolLive.Models.Character{name: "佐倉 慈"}
iex(4)> HelloSchoolLive.Repo.insert! %HelloSchoolLive.Models.Character{name: "若狭 悠里"}
iex(5)> HelloSchoolLive.Repo.insert! %HelloSchoolLive.Models.Character{name: "恵飛須沢 胡桃"}
iex(6)> HelloSchoolLive.Repo.insert! %HelloSchoolLive.Models.Character{name: "直樹 美紀"}

iex(7)> import Ecto.Query
iex(8)> query = from c in HelloSchoolLive.Models.Character, select: c
iex(9)> HelloSchoolLive.Repo.all(query) |> Enum.each(&(IO.puts &1.name))

15:05:41.468 [debug] SELECT c0."id", c0."name" FROM "characters" AS c0 [] OK query=2.7ms
丈槍 由紀
佐倉 慈
若狭 悠里
恵飛須沢 胡桃
直樹 美紀
:ok

Hello, School Live!

まとめです、冒頭で提示したAPIを実装します。

まずlib/hello_school_live.exにルーティング/hello-school-live/charactersを追記します。

lib/hello_school_live.ex

defp routes do
  [
    {:_, [
          {"/hello-school-live", HelloSchoolLive.Handlers.HelloSchoolLive, []},
          {"/hello-school-live/characters", HelloSchoolLive.Handlers.Characters, []} # ← 追記
        ]
    }
  ]
end

このAPIではJSONで応答したいので、応答時にはElxiirのtermをJSONエンコードした文字列を返却する必要があります。ここではElixir向けJSONライブラリのpoisonを利用します。mix.exsにpoisonのdependencyを追記します

mix.exs

def application do
  [applications: [:logger, :cowboy, :postgrex, :ecto, :poison], # ← 追記
   mod: {HelloSchoolLive, []}]
end

...省略...

defp deps do
  [
    {:cowboy, github: "ninenines/cowboy"},
    {:postgrex, ">= 0.0.0"},
    {:ecto, "~> 0.14.3"},
    {:poison, "~> 1.4.0"} # ← 追記
  ]
end

依存関係を解決しておきます:

$ mix deps.get

次にCharactersハンドラを実装します、lib/hello_school_live/handlers/characters.exを以下内容で作成します:

lib/hello_school_live/handlers/characters.ex

defmodule HelloSchoolLive.Handlers.Characters do
  import Ecto.Query

  def init(req, opts) do
    query = from c in HelloSchoolLive.Models.Character, select: c
    characters = HelloSchoolLive.Repo.all(query) |> Enum.map(&(&1.name))
    req2 = :cowboy_req.reply 200, [{"content-type", "application/json"}],
      Poison.Encoder.encode(characters, []), req
    {:ok, req2, opts}
  end
end

最後に確認です:

$ iex -S mix

別のshellを開いて以下コマンドを実行します

$ curl localhost:3000/hello-school-live/characters
["丈槍 由紀","佐倉 慈","若狭 悠里","恵飛須沢 胡桃","直樹 美紀"]

上記の通り出力されることを確認します。

以上のソースはここにおいてあります。

20
16
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
16