背景
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ツリーです。
クライアント=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_sup
に gun_sup
を入れています。これは、applicationが再起動したときに gun
のクライアントも再接続して欲しいためです。
poolの役割はfirebaseのclientを返すことと、その接続が切断されてしまったclientの再接続(復旧)です。workerは初期化のときにpoolからclientをfetchして、自身のstateにそのclientをキャッシュしておきます。
送信時にキャッシしたそのclientを使ってpush通知を送信します。
キャッシュしているのは、毎回fetchしてしまってメッセージパッシングが頻発しないようにしたいからです。push通知は全ユーザー、あるいはセグメントで対象を絞って送信することが想定できるので、それなりに多くの数への送信を考慮して、可能な限り負荷を減らそうという考えです。
先の送信方法でcurlでの送信をしていました。 pnsvc
は POST /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”
}
端末に届いているのを確認。
感想
とりあえず今後必要そうだなと思ったので、時間の余裕があるときに作り置きみたいな感じになりました。まだ実験的に実装、動作確認しただけなので、本番環境で使うことがあったらまた色々と課題がありそうだろうなと思っています。
あとは、webpushの確認や、ベンチマーク、ログ周り、statsをとったりする必要がありそうです。
erlangで書く分は苦労はなかったけれど、初めて触るflutterアプリを作るのに手間取りました。
色々終わったらgithubに公開しようと思ってます。
もっとこうすると良いんじゃないかとかの意見ありましたらコメントいただければと。