ElixirのHTTPoisonライブラリの使い方

HTTPoisonはHTTPクライアントのことです．なぜHTTPoisonを使うかというと，バックエンドはErlangのHackneyライブラリが使われているからです．Hackneyはバイナリを使うので，それが気に入っています.今回[GithubのAPI](https://developer.github.com/guides/getting-started/)を通して，HTTPoisonライブラリの基本的な使い方をまとめたいと思います．

### Setup

```
$ mix new githubapi --module Github
```
mix.exsへ以下の記述を追加します．

```
def application do
  # ここに`:httpoison`を書いておくとモジュールに`HTTPoison.startを書かなくても済む
  [applications: [:logger, :httpoison]]                                                                                                end

def deps do
  [{:httpoison, "~> 0.7.2"},
   {:poison, "~> 1.5"}
  ]
end
```

```
$ mix deps.get
```

`lib/githubapi.ex`ファイルは以下のようになっています．

```
defmodule Github do

  def zen do # これから実装する関数
  ...
  end

  def list_repos do
  ...
  end   
  ...
  ...
end
```

### GET 禅

毎回違う`禅`を取得してみます．

```
$ curl https://api.github.com/zen
Anything added dilutes everything else.
```

HTTPoisonでのやり方

```
def zen do
  url = "https://api.github.com/zen"
  response = HTTPoison.get!(url)
  Logger.info "レスポンス: #{inspect response}"
end
```
実行結果

```
$ iex -S mix 

iex(1)> Github.zen

18:37:09.866 [info]  レスポンス: %HTTPoison.Response{body: "Non-blocking is better than blocking.", headers: [{"Server", "GitHub.com"}, {"Date", "Sat, 12 Sep 2015 09:37:08 GMT"}, {"Content-Type", "text/plain;charset=utf-8"}, {"Content-Length", "37"}, {"Status", "200 OK"}, {"X-RateLimit-Limit", "60"}, {"X-RateLimit-Remaining", "57"}, {"X-RateLimit-Reset", "1442051552"}, {"X-XSS-Protection", "1; mode=block"}, {"X-Frame-Options", "deny"}, {"Content-Security-Policy", "default-src 'none'"}, {"Access-Control-Allow-Credentials", "true"}, {"Access-Control-Expose-Headers", "ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"}, {"Access-Control-Allow-Origin", "*"}, {"X-GitHub-Request-Id", "6A89D658:C023:20C345:55F3F243"}, {"Strict-Transport-Security", "max-age=31536000; includeSubdomains; preload"}, {"X-Content-Type-Options", "nosniff"}, {"Vary", "Accept-Encoding"}, {"X-Served-By", "b0ef53392caa42315c6206737946d931"}], status_code: 200}
:ok
```

レスポンスを見てみると`%HTTPoison.Response{body: ..., headers: ..., status_code: ...}`のような形の[struct](http://elixir-lang.org/getting-started/structs.html)です．その中の`body`フィールドを抽出するのは簡単(response.body)です．

```
def zen do
  url = "https://api.github.com/zen"
  response = HTTPoison.get!(url)
  # Logger.info "レスポンス: #{inspect response}"
  
  # bodyを抽出
  body = response.body
  Logger.info "body: #{inspect body}"
end
```

結果

```
iex(1)> Github.zen

18:52:19.126 [info]  body: "Encourage flow."
```

`get!`関数の最後に`!`が付いているのは理由について[ソース](https://github.com/edgurgel/httpoison/blob/master/lib/httpoison/base.ex)を見てみましょう．要は失敗すると例外を起こすようにすることです．

> Issues a GET request to the given url, raising an exception in case of failure.


すでに気づいたと思いますが，レスポンスの中の`headers`は`tuple`の`list`になっています．Enumを使って，`headers`を出力してみます．

```
def zen do
  url = "https://api.github.com/zen"
  response = HTTPoison.get!(url)
  # Logger.info "レスポンス: #{inspect response}"
  
  # bodyを抽出
  # body = response.body
  # Logger.info "body: #{inspect body}"

  # headerを出力
  headers = response.headers
  Enum.map(headers, fn {header, value} -> IO.puts "#{header}: #{value}\n" end)
end
```
結果

```
iex(1)> Github.zen
Server: GitHub.com

Date: Sat, 12 Sep 2015 10:16:20 GMT

Content-Type: text/plain;charset=utf-8

Content-Length: 26

Status: 200 OK

X-RateLimit-Limit: 60

X-RateLimit-Remaining: 58
......
......
[:ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok,
 :ok, :ok, :ok]
```


### Githubのrepoをリストアップ

認証されたユーザーとしてレポジトリをリストアップしてみます．そのために`token`が必要で，[ここ](https://github.com/settings/tokens)で生成することができます．
```
$ curl -i -H 'Authorization: token ****************************************' https://api.github.com/user/repos
```

HTTPoisonでのやり方

```
def list_repos do
  url = "https://api.github.com/user/repos"
  token = "***************************************"
  headers = [{"Authorization", "token #{token}"}]
  response = HTTPoison.get!(url, headers)
  Logger.info "response: #{inspect response.body}"
```

結果
大量なデータが出力されるので，省いておきます．

```
iex(1)> Github.list_repos
:ok
19:50:59.235 [info]  response: "[{\"id\":9593030,\"name\":\"Addition\",
.....
.....
.....
]
```
これで`get!`関数にheaerの追加のやり方も分かりました．どんなフォーマットの引数を渡せばいいかを知るために[ソースコード](https://github.com/edgurgel/httpoison/blob/master/lib/httpoison/base.ex)を見るとわかります．


### レポジトリのIssueを作成 (POST)

このAPIの仕様は[ここから](https://developer.github.com/v3/issues/#create-an-issue)アクセスできます．

```
$ curl -i -H 'Authorization: token ***************************************' \
>   -d '{
>        "title": "New logo",
>        "body": "We should have one",
>        "labels": ["design", "feature"]
>      }' https://api.github.com/repos/ColdFreak/ppool/issues
```

ここでjson形式のPOSTデータを送ります．"labels"の部分2つの要素が入っているリストになっています．

HTTPoisonでのやり方

```
def create_issue do
  url = "https://api.github.com/repos/ColdFreak/ppool/issues"
  token = "***************************************"
  
  response = HTTPoison.post!(url,  "{\"title\": \"Add variable\", \"body\": \"Add max overflow\", \"labels\": [\"design\", \"feature\"\]}", %{"Authorization" => "token #{token}"})

  Logger.info "response: #{inspect response}"
end
```

`post!`関数の使い方は同じくソースコードの中で調べるとわかります．
`def post!(url, body, headers \\ [], options \\ []),  do: request!(:post, url, body, headers, options)`
二番目の引数に直接json形式のデータを作って渡しています．`labels`の部分は配列になっています．三番目の引数は`header`で，上のような[Map](http://elixir-lang.org/getting-started/maps-and-dicts.html#maps)の形で渡しても大丈夫です．

作成されていることは確認できました．

![スクリーンショット 2015-09-12 22.21.43.png](https://qiita-image-store.s3.amazonaws.com/0/54698/ff3c1f68-f557-5b4c-1948-25fedb56b41c.png)


もう一つのやり方は`Poison`というJSONライブラリを使う方法です．

```
def create_issue_2 do
  url = "https://api.github.com/repos/ColdFreak/ppool/issues"
  token = "***************************************"

  headers = [{"Authorization", "token #{token}"}]
  json_data = %{title: "test issue", body: "test body", labels: ["design", "feature"]} |> Poison.encode!

  response = HTTPoison.post!(url,  json_data, headers)
  Logger.info "response: #{inspect response}"
```

Mapでデータを準備して，`Poison.encode!`渡して，JSONを生成してくれます．
![スクリーンショット 2015-09-12 22.36.52.png](https://qiita-image-store.s3.amazonaws.com/0/54698/eec9d70f-dfd3-6200-c9d7-781f38fbc3ef.png)