ErlangでFirebaseを使ったPush通知送信アプリ実装

Tky
6 min readJan 21, 2019

--

背景

webpush/ios/androidのpush通知を送る仕組みが今後必要そう。
ある程度、こちら側で送信をコントロールしたいのでサーバーを作っておきたい(色々外部のサービスはあるけど今回はスルー)。
時間の余裕があったので先に作って置く。

push通知を送る

APIでどう送るのかの例:

% push通知の中身作成
> Msg = #{
message => #{
to => <<"registration_id">>,
notification => #{
title => <<"push title">>,
body => <<"">>
}
}
}.
% gcpのproject id
> ProjectID = “sample-project-id”.
% sendでpush通知を送る
> pnsvc:send(Msg, ProjectID).

curlで送信.

$ curl -XPOST -H 'Content-Type: application/json' -d {"message": {"token": "registration_id", "notification": {"body": "push body", "title": "push title"}}}' http://localhost:5000/v1/projects/test-project-id/send

push通知の中身の構造は公式ドキュメントのjsonをerlangのmapsに対応させています。また、動的なkeyは存在しなかったのですべてatomにしています。

最初はrecordで定義を考えようとしたのですが、複数recordがネストする構造になってしまい、jsonのエンコード/デコードまでの処理が面倒なことになりそうだったので、mapにしました。mapの中身もtypeはかけて、何をリクエストしなければならないか明示できるので、そこまで問題にはならないだろうという判断です。

% こんな感じでtypeを定義してる
-type message() :: #{
token => binary(),
notification => notification(),
android => android(),
apns => apns(),
webpush => webpush()
}.

ProjectIDはgcpのproject idを指定します。

sendでpush通知を送信します。 pnsvc は今回実装したアプリケーション名です。

実装について

http/2のやり取りなので、1つのhttp/2クライアントに対して複数のworkerからリクエストできるように作りました。以下、 pnsvcのsupervisorツリーです。

pnsvcのsupervisor tree

クライアント=5、worker=10の設定で起動しています(なので、1クライアント当たりの多重度はおよそ2)。 pnsvc_supの再起動戦略は rest_for_oneで、 gun_sup -> pnsvc_pool_sup-> pnsvc_worker_supの順番です。
この順番にしたのは、「クライアントが起動していないとpoolは起動できない」「workerはpoolからclientをfetchするのでpoolが事前に起動している必要がある」ためです。

また、 pnsvcでは gunのアプリケーションを起動せずに pnsvc_supgun_supを入れています。これは、applicationが再起動したときに gunのクライアントも再接続して欲しいためです。

poolの役割はfirebaseのclientを返すことと、その接続が切断されてしまったclientの再接続(復旧)です。workerは初期化のときにpoolからclientをfetchして、自身のstateにそのclientをキャッシュしておきます。

送信時にキャッシしたそのclientを使ってpush通知を送信します。

キャッシュしているのは、毎回fetchしてしまってメッセージパッシングが頻発しないようにしたいからです。push通知は全ユーザー、あるいはセグメントで対象を絞って送信することが想定できるので、それなりに多くの数への送信を考慮して、可能な限り負荷を減らそうという考えです。

先の送信方法でcurlでの送信をしていました。 pnsvcPOST /v1/projects/:project_id/send のapi持っていて、これはcowboyを使って実装しています。

動作確認

今回は確認手段として、flutterでandroidアプリを作ることにしました。
理由は自分の環境がMacじゃなく、Androidユーザだったためです。
webpushはservice workerとかの話だった気がしたので後回しにしちゃいました。

また、 pnsvc を起動するために、firebaseのサービスアカウントのキーが必要なので事前にfirebase consoleで作成します。詳細はここでは割愛します。

curlで送信。

$ curl -s -XPOST -H 'Content-Type: application/json' -d '{"message": {"token": "***", "notification": {"body": "テストです", "title": "これはテストです"}}}' http://localhost:5000/v1/projects/days-platform/send | jq .
{
“name”: “projects/test-project-id/messages/0:1548043747113627%1900d0b61900d0b6”
}

端末に届いているのを確認。

androidにpush通知が届いたことを確認

感想

とりあえず今後必要そうだなと思ったので、時間の余裕があるときに作り置きみたいな感じになりました。まだ実験的に実装、動作確認しただけなので、本番環境で使うことがあったらまた色々と課題がありそうだろうなと思っています。

あとは、webpushの確認や、ベンチマーク、ログ周り、statsをとったりする必要がありそうです。

erlangで書く分は苦労はなかったけれど、初めて触るflutterアプリを作るのに手間取りました。

色々終わったらgithubに公開しようと思ってます。

もっとこうすると良いんじゃないかとかの意見ありましたらコメントいただければと。

--

--