Proxy環境下でElixirのHTTPoison利用する

2019.11.20時点の**[HTTPoisonの最新版ｖ1.6.2](https://hexdocs.pm/httpoison/1.6.2/readme.html)**で動作を確認しています。

Proxy環境下でElixirのHTTPoisonを利用する場合、Proxyの値を適切に設定しないとリクエストがProxy環境下のネットワークから出ていかない場合があります。
古いバージョンと新しいバージョンで動作が違ったので、簡単に整理しておきます。

## 前提

 * 上述の通り、HTTPoisonの最新版ｖ1.6.2で動作確認をしています。
 * 本文中でのProxyサーバの設定値は「 **http\://proxyserver.jp:8080** 」です。
 * リクエストを発行する関数については、「**HTTPoison.get!/3**」を例にして記述しています。ただし、post/3やhead/3などでも同じ対応です。
 * Elixirはv.1.9.0を利用しました。

## 設定方法

v.1.6.2の時点では、2種類ありました。

1. 環境変数に*HTTP_PROXY*の値を設定します
2. *HTTPoison.get!/3* の関数の第3引数「**options**」にProxyを設定して渡します


### 1. 環境変数にHTTP_PROXYの値を設定します

CHANGELOG をみると、[v.1.1.0](https://github.com/edgurgel/httpoison/blob/master/CHANGELOG.md#110-2018-04-02) からの機能です。
それ以前のバージョンでは利用できません。

Elixir側ではなく、Elixirを動作させるサーバ側の設定です。
環境変数「**HTTP_PROXY**」「**http_proxy**」「**HTTPS_PROXY**」「**https_proxy**」にProxyサーバの設定値を入れておけば、自動でProxyを設定してリクエストを発行してくれます。

ただし、**HTTPoison.get!/3** の第3引数「**options**」に対して「**:proxy**」を設定していない無い場合にのみ動作します。

なお、環境変数の設定値には優先度があります。
ソースコードをみると以下のようになっていました。

 * **HTTP_PROXY**と**http_proxy**では、**HTTP_PROXY**を優先
 * **HTTPS_PROXY**と**https_proxy**では、**HTTPS_PROXY**を優先する。

また、**HTTPoison.get!/3**の第1引数**url**のスキーマが「http」か「https」かで、**HTTP_PROXY**と**HTTPS_PROXY**のどちらを利用するか判断されています。


### 2. HTTPoison.get!/3の関数の第3引数「options」にProxyを設定して渡します

明示的にProxyの値を渡したい場合に利用します。
v.1.1.0より以前のバージョンでは、こちらがメインです。
CHANGELOGをみると、v.0.6.1から利用できていたようです。

**HTTPoison.get!/3** の第3引数「**options**」に対して「**:proxy**」を設定します。
設定値は、「**{プロキシサーバ, ポート番号}**」のようにタプル形式で指定します。
なお、プロキシサーバは*String*、ポート番号は*Integer*で設定します。

設定例:

```elixir
[ proxy: {"proxyserver.jp", 8080} ] 
```

この方法は、古いバージョン（例：v0.7.5やv0.6.1）でも動作します。
https://hexdocs.pm/httpoison/0.7.5/HTTPoison.html#request!/5
https://hexdocs.pm/httpoison/0.6.1/HTTPoison.html#request!/5

なお、**HTTPプロキシ**ではなく「**SOCKSプロキシ**」も指定できます。
こちらは、**v1.2.0**からドキュメントに記載されていました。

```elixir
[ proxy: {:socks5, "proxyserver.jp", 8080} ])}
```

それと、詳しいバージョンまでは不明ですが、タプルの部分は「**"http(s)://プロキシサーバ:ポート番号"**」としても動作します。
こちらの方が直感的に分かりやすいかもしれません。

```elixir
[ proxy: "http://proxyserver.jp:8080" ]
```

## おまけ1：リクエストのログについて

`iex -S mix` で動作確認してた際に、リクエストの値がログに出力されていました。
optionsの値の違いが確認できたので、参考までに載せておきます。 

* Proxyを明示的に指定していないとき:

```elixir
  request: %HTTPoison.Request{
    body: "",
    headers: [],
    method: :get,
    options: [],
    params: %{},
    url: "https://qiita.com/api/v2/items?query=Elixir"
  },
  request_url: "https://qiita.com/api/v2/items?query=Elixir",
  status_code: 200
}
```

* Proxyを明示的に指定したとき（タプルの場合）:

```elixir
  request: %HTTPoison.Request{
    body: "",
    headers: [],
    method: :get,
    options: [proxy: {"proxyserver.jp", 8080}],
    params: %{},
    url: "https://qiita.com/api/v2/items?query=Elixir"
  },
  request_url: "https://qiita.com/api/v2/items?query=Elixir",
  status_code: 200
}
```

* Proxyを明示的に指定したとき（文字列の場合）:

```elixir
  request: %HTTPoison.Request{
    body: "",
    headers: [],
    method: :get,
    options: [proxy: "http://proxyserver.jp:8080"],
    params: %{},
    url: "https://qiita.com/api/v2/items?query=Elixir"
  },
  request_url: "https://qiita.com/api/v2/items?query=Elixir",
  status_code: 200
}
```

## おまけ2: 動作確認に利用したソースコード

ソースコードは、こちらのスライドの内容を参考にして作成しました。

https://www.slideshare.net/piacere_ex/elixir1json-75571537

```elixir:lib/crawl.ex
defmodule Crawl do

  def get() do
    url = "https://qiita.com/api/v2/items?query=Elixir"

### Proxyを明示しないで、環境変数から取得するパターン
#   HTTPoison.get!(url) 

### タプル設定の場合
#    HTTPoison.get!(url, [], [ proxy: {"proxyserver.jp", 8080} ])

### SOCKSプロキシの場合
#    HTTPoison.get!(url, [], [ proxy: {:socks5, "proxyserver.jp", 8080} ])

### 文字列を渡す場合（＋環境変数から取得する場合）
#    px = System.get_env("HTTP_PROXY")
#    HTTPoison.get!(url, [], [ proxy: px ])


    ## QiitaAPIを利用してデータを取得してtitleだけにします。
    HTTPoison.get!(url) |> body() |> Poison.decode!() |> Enum.map( fn( %{"title" => title} ) -> title end )
  end

  def body( %{status_code: 200, body: json_body}  ), do: json_body
end
```

こちらは、`mix.exs`に設定した`httpoison`のバージョン。

```elixir:mix.exs
defmodule Crawl.MixProject do
  use Mix.Project

  def project do
    [
      app: :crawl,
      version: "0.1.0",
      elixir: "~> 1.9",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
      { :httpoison, " ~> 1.6" },
      { :poison, " ~> 1.5 " }
    ]
  end
end

```

## まとめ

HTTPoisonのバージョンがv.1.1.0以上の場合ならば、環境変数(*HTTP_PROXY、http_proxy、HTTPS_PROXY、 https_proxy*)を利用した方法が良い考えます。
コード内に環境依存の情報を記述しませんので。