おなじみgit探検隊
Elixirに限ったことではないが、定期的にgitでトレンドのレポジトリはチェックするようにしている
そうすると大体、何が流行っていて何に注目が集まっているかが何となく分かる
最近は中国語のREAD.MEが多くて翻訳ないと詰む
さておき、また今回も良さげなElixirのレポジトリを発見した
An Elixir web micro-framework
説明の通り、ウェブのマイクロフレームワーク
開発自体は3年程前からされているようでラストコミットは2018年の7月っぽい
ちょうどElixirでAPIをさくっと作りたかったのでありがたい
なぜPhoenixで作らないのか
信じてもらえるか分からないが実はPhoenixでjsonAPIを作ったことはある
普通に楽々作れるし、ドキュメントもある
じゃあ、なんでtrotを使うんだって話ですけど
単に作業量が圧倒的に少ないからに限る
cloud functionやらpythonのresponderやらトレンドはミニマムに高速で作ることだと思っている
Phoenixは1つエンドポイントを作るだけとかマイクロサービスを作る際にはオーバースペック感がある
そんな機能は別に使わへんがな... あとは学習コストの問題も
だったらさくっと覚えられて素早く使える物がいいよねってことでこうなった(事後
色々とエラーには遭遇してかなり時間は取られたことは内緒
プロジェクトの新規作成
いつも通りです
mix new simple_api
./simple_api/mix.exs に以下を追加
trotのREAD.MEにはtrotだけが記述されているが、僕の環境ではerrorになったので
plug_cowboyも追加している
fakerに関しては後に使うためインストールするようにしているが無くても問題ない
def application do [ extra_applications: [:logger], applications: [:trot, :faker] ] end defp deps do [ {:trot, github: "hexedpackets/trot"}, {:plug_cowboy, "~> 1.0"}, {:faker, "~> 0.12"}, ] end
plug_cowboyをインストールするようにしていなかった時のerror
#mix trot.serverでerrorになってしまう warning: please add the following dependency to your mix.exs: {:plug_cowboy, "~> 1.0"}
次に./sample_api/config/config.exs に以下を追加
公式ドキュメントだと割とひっそり書かれていて気づかない場合があるかもしれないが
少なくともrouterを設定するモジュールを記述しないとnot foundをただ返すだけの
ポンコツサーバーが出来上がる
config :trot, :port, 4000 #どちらでもok(デフォルトが4000番) config :trot, :router, SimpleApi #使用するモジュールを指定(必須)
これで準備は完了
APIをさくっと作る
今回は特にdbを使っていないので触れるのは「GETとPOST」メゾットのみ
データのやり取りが出来れば十分
公式では
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
をサポートしている
まずはGETから書いてみる
モジュールの頭に以下を追加
use Trot.Router
このRouterを記述したモジュールが先ほどconfigに記述したモジュール名と等しくなるようにする
./simple_api/lib/simple_api.ex
defmodule SimpleApi do use Trot.Router get "/easy-get" do "what's up!!" end end
たったこれだけ
マクロになっていてサクッと記述できる
第2引数でheader情報にマッチが取れるらしいが今の所使い道は思いつかない
さっそくターミナルからcurlしてみる
❯ curl http://localhost:4000/easy-get what's up!!%
いいですね。無事に文字列が帰ってきてる
最後に%が付くのはデフォルト? なぜだろう
次にPOST
値の受け取り方に関して公式ドキュメントに一切の記述が無くて不親切だなと思った
色々と調べた結果、Plugの関数を使うことで取得できることが分かったが
かなりここに時間をとられた(1日調べまくった
そもそもconnという謎の変数の存在に気付けへんわ
ここにリクエスト情報が詰まっているっぽい
#bodyの{"user": ___ }を取得する user = conn.body_params["user"]
#connの中身 %Plug.Conn{ adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{}, : : host: "localhost", method: "POST", owner: #PID<0.369.0>, params: %{"user" => "okb"}, path_info: ["easy-post"], path_params: %{}, port: 4000, : : scheme: :http, script_name: [], secret_key_base: nil, state: :unset, status: nil }
あとは簡単
普通に書くだけ(日本語難しい
post "/easy-post" do user = conn.body_params["user"] res = "hello, #{user}" IO.puts(res) res end
呼び出す
curl -H "Content-Type: application/json" -X POST -d '{"user": "okb"}' http://localhost:4000/easy-post hello, okb%
無事に値が返ってきた
ついでにサーバーサイドのログにも"hello, okb"が出力されている
基本的な部分に触れたところでもう少し便利なAPIを作ってみる
他のモジュール関数を呼び出す
せっかくなので過去に作成したモジュールを使用する
まずはGETメゾットが叩かれた時にmockデータを返すようなAPIを作る(ずっとほしかった
実装にFakerというライブラリを使っている
詳しくは以下をご覧ください
今回は試しがてらURLから値を受け取る(queryではない)をやってみる
URLに渡した指定数分だけデータを返すようにしている
defmodule CreateMock do def user_info(num) do 1..num |> Enum.map(fn _ -> _create_user_info() end) end defp _create_user_info do %{ email: Faker.Internet.email(), city: Faker.Address.En.city(), name: Faker.Name.En.first_name() <> " " <> Faker.Name.En.last_name(), phone: Faker.Phone.EnUs.phone(), food: Faker.Food.dish(), age: trunc(:rand.uniform() * 100) #Erlangモジュールを使って乱数を生成 } end end get "/mock/:create_num" do #受け取った時点では文字列なので注意 info = CreateMock.user_info(String.to_integer(create_num)) %{data: info} end
呼び出してみる
❯ curl http://localhost:4000/mock/2 {"data":[{"phone":"(988) 898-5220","name":"Hilario Dach","food":"Scotch eggs","email":"wallace.emmerich@spinka.info","city":"East Cierra","age":83},{"phone":"431-588-1937","name":"Faustino Senger","food":"Meatballs with sauce","email":"rey2086@kris.net","city":"South Friedrich","age":0}]}%
やったぜ
次にPOSTで送った2つの文字列が一致しているか、どれだけタイポしているかをチェックする
詳しくは以下をご覧ください
defmodule TypoChecker do def main(input_val, ans_val) when input_val == ans_val, do: :good def main(input_val, ans_val), do: _main(String.graphemes(input_val), ans_val, 0) defp _main([], _, counter), do: counter defp _main([head | tail], ans_val, counter) do str_lst = String.graphemes(ans_val) [_n_head | n_tail] = str_lst case type_judge(str_lst, head) do :hit -> _main(tail, List.to_string(n_tail), counter+1) :empty -> _main(tail, List.to_string(n_tail), counter) end end def type_judge([], _), do: :empty def type_judge([head | tail], compare_str) do if String.contains?(compare_str, head) do :hit else type_judge(tail, compare_str) end end end post "/typo-check" do input_val = conn.body_params["input_val"] ans_val = conn.body_params["ans_val"] case TypoChecker.main(input_val, ans_val) do :good -> "no typo" _ -> "is typo" end end
Elixirのatomをそのままreturnするとerrorになるため
case使って無理やり文字列を返すようにしている
では呼び出す
> curl -H "Content-Type: application/json" -X POST -d '{"input_val": "apple", "ans_val": "apple"}' http://localhost:4000/typo-check no typo%
> curl -H "Content-Type: application/json" -X POST -d '{"input_val": "apple", "ans_val": "appple"}' http://localhost:4000/typo-check is typo%
いいですね
使い方さえ分かってしまえばtrotでのAPI作成は爆速ですわ
あとはheorkuとかにデプロイする方法を調査してまた記事にでもします