Elixir + Phoenix + Firebase Cloud Messaging(FCM)でPush通知

Elixir + Phoenix + Firebase Cloud Messaging(FCM)でPush通知

こんにちは、渡部です。

アドウェイズには技術向上プロジェクトという、業務時間の10%をそのプロジェクトのために使っても良いというすごいルールがあります。(1週間1日8時間、計40時間、定時分働いたら4時間は使っても良いことになります)

私はPush通知API用のマイクロサービスを作りたいというプロジェクトを作っていまして、 今回はブログ作成のために進捗をなんとか作れましたので、それについて少し書きたいと思います。

背景

  • アドウェイズではPush通知用のサーバーを各サービスで実装している
  • これのためのコードの開発・保守運用のコスト(工数=お金)がかかっている
  • サービスごとに集計したいデータやレポートは別々だとしても、誰かに何かを送って通知データを保存しているというのは共通のはず
  • Push通知を行うとユーザーの方が、サービスに関心を持ってくれます。実際に予約TOP10などのサービスではPush通知を行うとアクセス数がとても増えます。このことから、サービスへの呼び戻し(reengagement)と継続率(retention)アップの効果があるため、マイクロサービス化して簡単に使えるようにするのは価値があるはず

Elixir + Phoenixを使おうとなったのは最近身の回りで耳にすることが多いのと、 単純に複数のサービスに使われてみたいと思ったため、負荷も高いのではないかという浅慮のためでした。

方法

私の配属先で運用しているサービスでは、Google Cloud Messaging(GCM)でPush通知を送るようにしています。

Cloud Messaging | Google Developersの上部に

Firebase Cloud Messaging (FCM) is the new version of GCM. It inherits the reliable and scalable GCM infrastructure, plus new features! See the FAQ to learn more. If you are integrating messaging in a new app, start with FCM. GCM users are strongly recommended to upgrade to FCM, in order to benefit from new FCM features today and in the future.

と赤色のメッセージが・・・。

何事かと思ってみてみれば、Firebase Cloud Messaing(FCM)というGCMの新バージョンが出たというお知らせのようで、 FCMにアップグレードすることを強く勧めるという内容のようです。

今回は、せっかくメッセージでお勧めてしてもらったのでFCMに挑戦してみます。 自分の開発環境ではGCMを使っていますが、いずれバージョンアップするかもしれないと考えると調査目的もあります。

また、Push通知のテストにAndroidかiOS、WEBクライアントが必要になるみたいなので、一番楽そうな()Android Studioでシミュレーションを行ってテストします。 基本的にサーバー開発・保守運用がメインなので、Android・iOS開発はほとんど触れたことがないのですが、Push通知のテストまでの道のりは以外と長いみたいですね。

夜中にAndroid Studioをダウンロードしたら12GBもありました。 流石IDE、、昨今のソフトウェアは容量が大きいもの多い印象がありましたが、吃驚しました(*_*)

過程

今回の流れ

  1. Android Studioにサンプルコードをインポート
  2. Firebaseプロジェクトの作成とNotificationアプリの登録
  3. サーバー用のAPIキーの取得
  4. デバイストークンの取得
  5. Elixirコーディング

やってみた

Android Studioにサンプルコードをインポート

FirebaseのNotificationテスト用に公式のクイックスタートプロジェクトがあるので、これを利用しました。

ダウンロード → Android StudioにプロジェクトをインポートでOKです。 コードは変更の必要がありませんでした。

Firebaseプロジェクトの作成とNotificationアプリの登録

f:id:AdwaysEngineerBlog:20160903013042p:plain

AndroidStudioで<project>/app/src/AndroidManifest.xmlpackage="com.google.firebase.quickstart.fcm"とパッケージ名があったのでそれを登録。

サーバー用のAPIキーの取得

作成したプロジェクトの歯車マーク > プロジェクトの設定 > クラウドメッセージングと辿るとサーバー用のAPIキーを取得できます。 f:id:AdwaysEngineerBlog:20160903013159p:plain

サーバーキーが正しいかは以下のコマンドで確認できるようです。

$ api_key=YOUR_SERVER_KEY
$ curl --header "Authorization: key=$api_key" \
       --header Content-Type:"application/json" \
       https://fcm.googleapis.com/fcm/send \
       -d "{\"registration_ids\":[\"ABC\"]}"

を行って、404コードが返ってくれば失敗、

{"multicast_id":6782339717028231855,"success":0,"failure":1,
"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}

といったJSONで返ってくれば成功のようです。 JSONの"failure":1となっている理由は、curl中の{\"registration_ids\":[\"ABC\"]となっているためです。 故意に正しくない送り先IDを入れているので失敗しているようです。 もし、デバイストークンregistration_idsが正しいかテストしたかったら、ABCを正しい値にすればPush通知を送れます。

デバイストークンの取得

f:id:AdwaysEngineerBlog:20160903013109p:plain

インポートしたプロジェクトをエミュレータで起動すると上のようなアプリを見つけられると思います。

アプリの「SUBSCRIBE TO NEWS」をクリックすると、FirebaseのNotificationが送れるようになります。 「LOG TOKEN」ボタンをクリックすると、Android Studioのログメッセージに

InstanceID token: 「トークン」

と、送信に必要なデバイストークンが表示されます。 (「トークン」には実際には本物のトークンが入ります。)

Elixirコーディング

web/router.ex

  ...
  # Other scopes may use custom stacks.
  scope "/api", GcmApp do
    pipe_through :api

    get "/push/:message", PageController, :push
  end
  ...

web/controllers/page_controller.ex

defmodule GcmApp.PageController do
  use GcmApp.Web, :controller

  def index(conn, _params) do
    render conn, "index.html"
  end

  def push(conn, %{"message" => message}) do
    url = "https://fcm.googleapis.com/fcm/send"
    api_key = "サーバーキー"
    registration_ids = ["デバイストークン"]

    headers = [{"Content-Type", "application/json"},
               {"Authorization", "key=#{api_key}"}]

    json_data = %{collapse_key: "ff1",
                  delay_while_idle: true,
                  time_to_live: 864000,
                  notification: %{title: "gcm_app", body: message},
                  registration_ids: registration_ids} |> Poison.encode!

    response = HTTPoison.post!(url,  json_data, headers)

    # IO.puts response.body

    case response do
      %{status_code: 200, body: body} -> json conn, Poison.decode!(body)
      %{status_code: code} -> json conn, %{error: code}
    end
  end
end

コード自体はJSONデータを作って、POSTするだけというシンプルな作りになりました。

ElixirにはFCM用のライブラリがないので、直接HTTPリクエストを行います。 今回はHTTPoisonというライブラリを使っていますので、mix.exに依存パッケージを追記します。

mix.ex

...
...
def application do
  [mod: {GcmApp, []},
   applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
                  :phoenix_ecto, :mariaex, :httpoison]]    # 追加
end
...
...
defp deps do
   ...
   ...
   {:httpoison, "~> 0.9.0"}]    # 追加
end
...
...
$ mix deps.get

で依存パッケージのインストールは完了です。

(モジュール名がGcmAppになっていますが、最初にGCMを使ってPush通知をしようとしていたころの名残です(^^;)

結果

$ mix phoenix.server
$ curl -XGET http://localhost:4000/api/push/進捗どうですか?

f:id:AdwaysEngineerBlog:20160903013136p:plain

ヒエッ、無事できてますね(白目)

注意点としてはアプリがバックグラウンドにいないと通知が届かないようになっているようです。 フォアグラウンドでも届くようにするにはサンプルコードに少し手を加える必要がありそうです。

まとめ

やったことが意外に多かったので、説明書みたいになってしまいましたが無事Push通知を送ることができました! Elixirで書いたコードもとても少なく、Firebaseプロジェクトをいじりまわしている時間のほうが長く感じました。

ここから拡張していって、PushAPIサーバーを作っていきたいと思います。

参考にさせていただいたもの