Elixir+PhoenixでAPNsを利用したPush通知を送る簡単なAPIをつくってみる

# はじめに
Advent Calendar童貞を捨てに来ました。

# 前提
以前["Elixir+PhoenixでGCMのAPIを叩いてPush通知を送る簡単なAPIをつくってみた"](http://qiita.com/itosho/items/bf1b1ca1d28cc2c7f2a4)という記事で、ElixirでAndroidアプリにPush通知するWebAPIをつくったので、その続きとしてiOSアプリにPush通知するWebAPIをつくってみます。
ですので、環境構築などは前回の記事をご覧いただければと思います。

# Apns4exインストール
最初張り切って自前で実装しようとした（無謀）のですが、[Apns4ex](https://github.com/chvanikoff/apns4ex)というよさげなライブラリがあったので、そちらを利用させていただきました。

~mix.exsのdepsとapplicationの箇所にApns4exの設定を追加します。

```ex:mix.exs
defp deps do
    [{:phoenix, "~> 1.0.3"},
     {:phoenix_ecto, "~> 1.1"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.1"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:cowboy, "~> 1.0"},
     {:httpoison, "~> 0.7.2"},
     {:apns, "== 0.0.10"}]
end
```

```ex:mix.exs
def application do
    [mod: {PushApp, []},
     applications: [:phoenix, :phoenix_html, :cowboy, :logger,
                    :phoenix_ecto, :postgrex, :httpoison, :apns]]
end
```

そして、mixコマンドでダウンロード！

```
$ mix deps.get
```

# ルーター設定

`GET /push_apns/:message`というエンドポイントを追加します。

```ex:router.ex
scope "/api", PushApp do
    pipe_through :api

    get "/push_gcm/:message/", PageController, :push_gcm
    get "/push_apns/:message/", PageController, :push_apns
end
```

# APNs設定

次に証明書やPoolingの設定をします。

```ex:config.exs
# Configure APNS
config :apns,
  # Here goes "global" config applied as default to all pools started if not overwritten by pool-specific value
  callback_module:    PushApp.PageController,
  timeout:            30,
  feedback_interval:  1200,
  reconnect_after:    1000,
  support_old_ios:    true,
  # Here are pools configs. Any value from "global" config can be overwritten in any single pool config
  pools: [
    # app1_dev_pool is the pool_name
    app_prod_pool: [
      env: :prod,
      certfile: "/PATH/TO/DIR/xxxx.pem",
      pool_size: 100,
      pool_max_overflow: 50
    ],
  ]
```

今回はプロダクション環境向けの証明書を作成したので、`env: :prod`を設定しましたが、サンドボックス用の証明書は、`:dev`を設定してください。
**（話逸れますが、将来的には証明書が1つになるようです！）**
また、APNsのエラーやフィードバックが発生した時用ののコールバック関数の定義場所を`callback_module:    PushApp.PageController`と設定しました。

他の設定はデフォルトのままにしています。（Pooling周りは今勉強中）

# メイン処理

~web/controllers/page_controller.exにpush_apnsメソッドを以下のように追加します。

```ex:page_controller.ex
def push_apns(conn, %{"message" => alert}) do

    token = "hogehoge"

    message = APNS.Message.new
    message = message
    |> Map.put(:token, token)
    |> Map.put(:alert, alert)
    |> Map.put(:badge, 1)

    APNS.push :app_prod_pool, message

    json conn, %{status: "done"}

end
```

# コールバック関数

先程設定ファイルで定義したコールバック用の関数を追加します。

```ex:page_controller.ex
def error(%APNS.Error{error: error, message_id: message_id}) do
    # エラー発生時の処理を書く
    Logger.error "[APNS] Error \"#{error}\" for message #{inspect message_id}"
end

def feedback(%APNS.Feedback{token: token}) do
    # フィードバック発生時の処理を書く
    Logger.info "[APNS] Feedback received for token #{token}"
end
```

ご存知の方も多いと思いますが、APNsはエラーハンドリングが結構厄介で、Apns4exは非同期でエラーやフィードバック時の処理できパフォーマンス的にとても優れているのですが、僕の頭では非同期で素敵なエラーハンドリングが書けず、悪戦苦闘しつつ、最終的には来年Apple様が用意してくれると言われているHTTP2ベースのAPNs APIを待つことになりました←

と言いつつたまにゴニョゴニョ頑張っているので、よい書き方があれば教えてくださいm(__)m

# API実行
開発が完了したら、サーバーを起動してAPIを実行！

```
$ curl http://localhost:4000/api/push_apns/finalfantasy
{"status":"done"}
```

上手く行けば、iPhoneアプリに"finalfantasy"というPushメッセージが通知されます。
エラーの場合（デバイストークンが不正など）は、ログが出力されます。

# まとめ
Elixir版[gaurun](https://github.com/mercari/gaurun)つくりたい。
APNS4exのソースはElixir/Erlangを理解するのにいい勉強になりそう。
**早くHTTP2ベースのAPNs APIリリースしてください！**
【追記】
新しいAPIでましたｷﾀ━━━━(ﾟ∀ﾟ)━━━━!!
[APNs Provider API](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/APNsProviderAPI.html#//apple_ref/doc/uid/TP40008194-CH101-SW1)

# 参考サイト
iOSのPUSH通知(APNS)の特徴・ノウハウまとめ(iOS 9まで対応)：
http://qiita.com/mono0926/items/df03c61adc56934e2e7a
Apple Push Notification Serviceのエラー処理について：
http://hagino3000.blogspot.jp/2014/05/apple-push-notification-service.html