プッシュ通知を送信するためのサーバサイド側を構築する


プッシュ通知、語り尽くされた話ですが、サーバ側の話はあまりされません。
特に、アプリ側の実装がメインで書かれている記事の大半は "[ApnsPHP](https://github.com/immobiliare/ApnsPHP)" というものを使用しています。
しかしこのプログラムを用いても、スケールしなかったり、androidへのプッシュ通知は送信できません。
この記事では、（おそらく）もっと手軽にかつ安定してプッシュ通知を送るサーバサイドの構築をGaurunを用いて行います。

#Gaurunとは

Gaurunとは、[メルカリ](https://github.com/mercari)さんがOSSとして公開している、Go言語で書かれた汎用的なPush通知配信サーバです。

> "A general push notification server in Go."
https://github.com/mercari/gaurun/blob/master/README.md

このサーバは、プッシュ通知をキューイングし、APNSやGCMにプッシュ通知リクエストを送信するWebAPIを提供しています。
ApnsPHPなどを用いた場合、1,000デバイス程度であれば問題無いのですが、5,000, 10,000と言ったデバイスにプッシュ通知を行いたい場合、かなり待たされたり、実装が悪いと、

```
PHP Fatal error:  Allowed memory size of yyy bytes exhausted (tried to allocate xxx bytes) in ....
```

とか言われます。Gaurunを用いると、プッシュ内容をAPNSやGCMに通知する処理をそちらに任せることができるので、負荷分散などを行うことが可能です。
また、Gaurunでは、iOSだけではなく、Androidのプッシュ通知を行うことが(設定すれば)可能です。

#前提

今回使用するものの環境は下記の通りです。

サーバ: [さくらVPS 2G](http://vps.sakura.ad.jp/)
OS: [Ubuntu 14.04 LTS](http://releases.ubuntu.com/14.04/)
Go: [go1.5.1 linux/amd64](https://golang.org/dl/)
Gaurun: [v0.4.0](https://github.com/mercari/gaurun/releases)

#プッシュ通知を送るための下準備

##iOSのAPNSの鍵を取得する
iOSアプリ開発者にやってもらってもいいのですが、ややこしいので。鍵系の問題は多々起こるので、サーバサイドに慣れている人のほうがもちろん慣れていることでしょう。もしできないと言われたら代わりにやってあげましょう…。
今回の説明では、cerファイルの発行の部分まではiOS Dev Centerを見れば方法が書いてあるため割愛します。
発行後、証明書と秘密鍵を取り出すところからの説明です。仮に、発行されたファイルを書類ディレクトリ(`~/Documents`)に保存したとします。

###p12形式で秘密鍵をエクスポートする

iOS Dev Centerから発行されたcerファイルをダブルクリックし、`キーチェーンアクセス` に追加します。
`キーチェーンアクセス`を開き、左タブの`証明書`を選択します。
その後、`Apple Development IOS Push Services: [bundle-id]`という名前の証明書の左の三角ボタンを押して展開し、その証明書に紐づく秘密鍵を右クリックし、書き出します。形式はp12形式で大丈夫です。書き出しを保護するために使用されるパスワードは空欄で構いません。
名前はややこしくなるので `[appname]_dev_key`のように指定するとわかりやすいです。ここでは書類ファイル(`~/Documents`)に保存したとします。

###秘密鍵と証明書をPEMファイルに変換

`ターミナル`を開き、

```
$ openssl x509 -in ~/Documents/cert.cer -inform DER -outform PEM -out cert_dev.pem
$ openssl pkcs12 -in ~/Documents/[appname]_dev_key.p12 -out key_dev.pem -nodes

```

これでそれぞれのPEMファイルが作成されます。
秘密鍵を作成する場合書き出しパスワードを聞かれます。前手順で設定した場合はそのパスワードを、空欄の場合はそのままエンターで出力されます。

###テストする

サンドボックス環境のものであれば、

```
$ openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert cert_dev.pem -key key_dev.pem 
```

プロダクション環境のものであれば、

```
$ openssl s_client -connect gateway.push.apple.com:2195 -cert cert.pem -key key.pem # production
```

でそれぞれ確認できます。

#サーバ側の下準備

##必要な物をインストール

```
$ sudo apt install git
```

##Goを使えるようにする

###アーカイブをダウンロードして展開

```
$ wget https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.5.1.linux-amd64.tar.gz
```

###環境変数を設定

```
export GOPATH=$HOME/go
export GOROOT=/usr/local/go
export PATH=$PATH:/usr/local/go/bin:$GOROOT/bin:$GOPATH/bin

$ mkdir $HOME/go
```

###Goの動作確認

```
$ go version
go version go1.5.1 linux/amd64
```

のようにでたらOK

##Gaurun環境を作成

###Gaurunを持ってくる

```
$ git clone https://github.com/mercari/gaurun.git
```

###README通りセットアップをすすめる

ディレクトリに入って、

```
$ make gom
$ make bundle
$ make
```

なお、Goが正常に動作していない場合、環境変数の設定を間違えている場合はここでエラーが表示されます。

###取り出した秘密鍵と証明書のPEMファイルをどこかに配置する

sshで送ってもいいし、コピペでもいいし。
`conf/`に置くと仮定します。

###CONFIGURATION.md通りに設定ファイルを作成する

```
[core]
port = "1056"
#port = "unix:/tmp/gaurun.sock"
workers = 8
queues = 8192
notification_max = 100

[api]
push_uri = "/push"
stat_go_uri = "/stat/go"
stat_app_uri = "/stat/app"
config_app_uri = "/config/app"

[android]
apikey = "apikey for GCM"
enabled = true
timeout = 5 # sec
retry_max = 0

[ios]
pem_cert_path = "conf/cert_dev.pem"
pem_key_path = "conf/key_dev.pem"
sandbox = true
enabled = true
keepalive_max = 0
keepalive_idle_timeout = 300
retry_max = 1
timeout_error = 500 # msec

[log]
access_log = "/var/log/gaurun/access.log"
error_log = "/var/log/gaurun/error.log"
level = "error"
```

logの配置場所、iOSの鍵ファイルの配置場所、androidのAPIキーの変種をすれば基本的には大丈夫かと思います。
プロダクション環境を使う場合は、iOSの`sandbox`のフラグの変更お忘れなく。

###実行してみる

```
$ bin/gaurun -c conf/gaurun.toml
```

#送ってみる

`curl`を用いて送ってみます。Gaurunが提供しているWebAPIはとてもシンプルです。
[ドキュメントはこちら](https://github.com/mercari/gaurun/blob/master/SPEC.md#post-push)

```
$ curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"notifications" : [{"token" : ["(token)"],"platform" : 1,"message" : "(文言)","badge" : 1,"sound" : "default","expiry" : 10,"extend" : [{ "key": "url", "val": "..." }, { "key": "intent", "val": "..." }]}]}' http://localhost:1056/push
* Hostname was NOT found in DNS cache
*   Trying ::1...
* Connected to localhost (::1) port 1056 (#0)
> POST /push HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:1056
> Accept: application/json
> Content-type: application/json
> Content-Length: 318
> 
* upload completely sent off: 318 out of 318 bytes
< HTTP/1.1 200 OK
< Content-Type: application/json
* Server Gaurun 0.4.0 is not blacklisted
< Server: Gaurun 0.4.0
< Date: Tue, 17 Nov 2015 09:08:20 GMT
< Content-Length: 17
< 
{"message":"ok"}
* Connection #0 to host localhost left intact
```

`{"message":"ok"}`が最終的に帰ってきていたらOKです。

#まとめ

Gaurunを用いることで、簡単に、素早くプッシュ配信を行うことができる。Gaurun最高！