はじめてのElixirです。
最近、はじめての言語を覚えるには普段使いのツールを作るとよさそうだという気付きを得たので、チーム開発向けのめっちゃグレートなグループチャットであるidobataにpostするスクリプトをElixirで書いてみました。
このスクリプトの名称は「 idobatir 」としました。ファイル名はidobatir.exsとします。
なお、出来上がりの品は、https://gist.github.com/bash0C7/7837806 にあります。
このエントリーでは、インストール、Elixirの動かし方の理解から、httpのリクエストの送り方を知り、具体的にidobataにメッセージを送るところまでの過程を記します。
インストールと動かし方の理解
macなのでhomebrew頼り。
brew install elixir
動かし方を知るために、http://elixir-lang.org/getting_started/3.html の3.2 Scripted modeのmath.exsをやってみましょう。
defmodule Math do
def sum(a, b) do
a + b
end
end
# IO.puts expects a list or a binary, so we use the string representation of the sum
IO.puts to_string(Math.sum(1, 2))
elixir math.exs
3
把握。
httpのリクエストの送り方を知る
まずは、インタラクティブシェルのiexを使って、httpリクエストの送り方を試していきます。
どうやら http://erlang.org/doc/apps/inets/http_client.html#id60622 を使うとのこと。
:application.start(:inets)
:httpc.request(:get, {'http://127.0.0.1', []}, [], [])
このように、http://127.0.0.1/ にgetリクエストが送られました。ならば、postするにはこうすればいいのかな。
application:start(inets)
:httpc.request(:post, {'http://127.0.0.1/', []}, [], [])
** (FunctionClauseError) no function clause matching in :httpc.request/5
httpc.erl:147: :httpc.request(:post, {'http://127.0.0.1/', []}, [], [], :default)
動かぬ。
http://stackoverflow.com/questions/19103694/simple-example-using-erlang-for-https-post をみるに、下記のようなrequestを指定すればすれば動くとのこと。
:httpc.request(:post, {'http://127.0.0.1/', [], [], []}, [], [])
これは、 http://erlang.org/doc/man/httpc.html#request-5 にrequestの書き方が示されています。
request() = {url(), headers()} |
{url(), headers(), content_type(), body()}
idobataにメッセージをpostする
では、具体的にidobataにメッセージを送ります。引き続きiexで試していきます。
普段idobataへpostするときは、urlにidobataのGeneric WebHookのURLを指定し、content-typeにapplication/x-www-form-urlencodedを、bodyにsource=<メッセージ>を送っています。
まずは固定値でそれらを指定してみた。
application:start(inets)
:httpc.request(:post, {'https://idobata.io/hook/XXXXXXXXX', [], 'application/x-www-form-urlencoded', 'source=HelloElixir'}, [{:ssl,[{:verify,0}]}], [])
動かない。http://stackoverflow.com/questions/2886521/how-do-i-do-an-https-request-with-erlang に同様の質問があったが、httpsの場合はsslをapplication.startする必要があるとのこと。
ただ、これだけstartしてもあれが無いこれが無いと言われ、最終的には下記をapplication start
ってやったら、色々ないと言われたので、結局下記をずらっと実行。
:application.start(:asn1)
:application.start(:crypto)
:application.start(:public_key)
:application.start(:ssl)
:application.start(:inets)
:httpc.request(:post, {'https://idobata.io/hook/XXXXXXXXX', [], 'application/x-www-form-urlencoded', 'source=HelloElixir'}, [{:ssl,[{:verify,0}]}], [])
これで無事idobataにHelloElixirというメッセージを送ることができた。
コマンドライン引数でURLやメッセージを渡せるスクリプトとして仕上げる
最後に、URLもメッセージも変動要素なので、外から渡せるようにして、実用的なスクリプトに仕上げようと思います。
URLとメッセージは、コマンドライン引数として渡すようにします。
http://elixir-lang.org/docs/master/System.html#argv/0 というのが準備されており、System.argvを呼び出すことで取得ができます。
メッセージはsource=<メッセージ>という形式にしたいので、スクリプトにsource=まで書いておいて、それに外から渡したメッセージを文字列埋め込みを行います。
http://d.hatena.ne.jp/k-1/20120316/p1 をみるに、Rubyと同じような書き方ができるとのことです。
そこで、下記を記述して動かしてみたのですが、リクエストを送るところでargument errorで動きません。
:application.start(:asn1)
:application.start(:crypto)
:application.start(:public_key)
:application.start(:ssl)
:application.start(:inets)
[url, message] = System.argv()
:httpc.request(:post, {url, [], 'application/x-www-form-urlencoded', 'source=#{message}'}, [{:ssl,[{:verify,0}]}], [])
** (ArgumentError) argument error
:erlang.tl("https://idobata.io/hook/XXXXXXXXX")
inets_regexp.erl:80: :inets_regexp.first_match/3
inets_regexp.erl:68: :inets_regexp.first_match/2
http_uri.erl:186: :http_uri.split_uri/5
http_uri.erl:136: :http_uri.parse_scheme/2
http_uri.erl:88: :http_uri.parse/2
httpc.erl:188: :httpc.request/5
ただ、コマンドライン引数さえとらなければ、iexで動作できたので、文法上の誤りではなさそうでした。
となると、コマンドライン引数からとったものは文字列扱いされてないんじゃないかと疑い、文字列埋め込みすによって冗長な書き方ではありますが、文字列化してみたところ、下記で無事完動しました。
:application.start(:asn1)
:application.start(:crypto)
:application.start(:public_key)
:application.start(:ssl)
:application.start(:inets)
[url, message] = System.argv()
:httpc.request(:post, {'#{url}', [], 'application/x-www-form-urlencoded', 'source=#{message}'}, [{:ssl,[{:verify,0}]}], [])
この後は、メッセージを標準入力で渡せるようにしたり、URLを環境変数で渡せるようにしたり、パラレルで動くようにしたりといった手を加えながら、Elixirと戯れ、そのパワーを味わっていきたいと思います。