nginx+Laravel+WebSocketでpush通知を構築

# Nginx+php7.2+laravel-echo-server環境を作成するまでの道のり

前の会社でとあるシステムを作成中、色々共有し合った部分をまとめる。

##この作業で出来る事
GCMやpusherみたいな有料サービスを使用しないで自サーバー上でpush通知が使用可能になる。

##おおまかにやる事
####①laravel-echo-serverをインストールする。
　こいつはnodejsで動くサーバーなのだが、supervisorでデーモン化＋落ちたときに自動的に再起動するようにしておく。
####②Nginxのリバースプロキシ機能を使ってポート80に対してWebSocketが来た場合はlaravel-echo-serverに処理を行わせる。
　クライアント側から見るとhttpとwsをポート80で接続する事になる。
<hr/>

| 必要なもの | バージョン情報 |
|:---------|:---------|
|Ubuntu| 16.04LTS
|nginx|任意のバージョン|
|php-fpm|php7.2|
|Laravel|5.5 LTS|
|nodejs|v10.16.3|
|npm|6.4.1|
※恐らくバージョンは古すぎなければあまり気にしなくても良いと思われます。
<hr/>
####通知機能が動く所までを書いていく。

とりあえず、OSはubuntuを使った。
今回は備忘録用なのでvagrantでさくっと作った場合という前提で記載
下記コマンドでvagrant環境を作成し、接続

```
> vagrant init bento/ubuntu-16.04
> vagrant up
> vagrant ssh
```

ざっと実行したコマンドを書いておく
やっている事は、nginxのインストールやDBのインストール、PHPインストール、composerのインストール、その他社内案件で必要となるモジュールのインストールである。

```
vagrant@vagrant:~$ sudo apt install nginx unzip zip acl
vagrant@vagrant:~$ sudo apt install xpdf curl
vagrant@vagrant:~$ sudo systemctl enable nginx
vagrant@vagrant:~$ sudo apt install software-properties-common
vagrant@vagrant:~$ sudo add-apt-repository ppa:ondrej/php
vagrant@vagrant:~$ sudo apt update
vagrant@vagrant:~$ sudo apt install -y php7.2 php7.2-fpm php7.2-mysql php7.2-mbstring php7.2-zip php7.2-xml php7.2-dom php7.2-pgsql php7.2-curl
vagrant@vagrant:~$ sudo apt install -y postgresql postgresql-contrib
vagrant@vagrant:~$ curl -sS https://getcomposer.org/installer | php
vagrant@vagrant:~$ sudo mv composer.phar /usr/local/bin/composer
vagrant@vagrant:~$ sudo chmod +x /usr/local/bin/composer

※その他、php-fpm設定やらpostgresqlをLaravelから使用するための設定などは人それぞれ違うと思うので省略
nginxの設定はこんな感じ
User:www-data
group:www-data
```

で、ここから結構ハマったというか自分の知識が不十分で色々苦労したので今後の為に書いておく。

Laravelのプロジェクト名：sample
nginx実行ユーザー：www-data
nginx実行グループ：www-data
sftpログインユーザー：vagrant

```
nginxの実行ユーザーと実行グループに合わせてここは変わる。
僕は、vagrantユーザーでアップロードしたり色々やるので、nginxのユーザもvagrantにしたので下記のように設定

$ sudo usermod -aG www-data vagrant
$ cd /var/www
$ sudo chown -R vagrant:www-data /var/www
$ composer create-project --prefer-dist laravel/laravel sample "5.5.*"
$ cd sample
$ sudo chown -R vagrant:www-data .
$ sudo find . -type d -exec chmod 750 {} \;
$ sudo find . -type f -exec chmod 640 {} \;
$ sudo find storage -type d -exec chmod 775 {} \;
$ sudo find bootstrap/cache -type f -exec chmod 664 {} \;
$ sudo setfacl -R -d -m u:www-data:rw storage
$ sudo setfacl -R -d -m u:www-data:rw bootstrap/cache
$ sudo systemctl enable postgresql@
$ sudo systemctl enable php7.2-fpm
$ sudo reboot
```

nginxの設定ファイルの中身、結構忘れるので書いておく。

```
server {
        listen 80 default_server;
        listen [::]:80 default_server;
#       root /var/www/html;
        root /var/www/sample/public;

#       index index.html index.htm index.nginx-debian.html;
        index index.html index.htm index.nginx-debian.html index.php;

        server_name _;

        location / {
#               try_files $uri $uri/ =404;
                try_files $uri $uri/ /index.php?$query_string;
#                auth_basic "Restricted";                        # Basic認証をかけるとき
#                auth_basic_user_file /etc/nginx/.htpasswd;      # Basic認証をかけるとき
        }

        location ~ \.php$ {
                fastcgi_pass    unix:/run/php/php7.2-fpm.sock;
                fastcgi_index   index.php;
                fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include         fastcgi_params;
        }
}
server {
        listen 443;
        ssl on;
        server_name **********;
        root /var/www/sample/public;
        index index.php;

        location / {
                try_files $uri $uri/ /index.php?$query_string;
                auth_basic "Restricted";                        # Basic認証をかけるとき
                auth_basic_user_file /etc/nginx/.htpasswd;      # Basic認証をかけるとき
        }

        location ~ \.php$ {
                fastcgi_pass    unix:/run/php/php7.2-fpm.sock;
                fastcgi_index   index.php;
                fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include         fastcgi_params;
        }

        ssl_certificate /etc/nginx/ssl/hogehoge.crt;
        ssl_certificate_key /etc/nginx/ssl/(省略;

}
```

ポスグレのパスワード変更の仕方を忘れるのでメモ

```
postgres=# alter user postgres with encrypted password 'password';
```
### node.jsとnpmのインストール

<font color="red">nodejsはバージョン管理出来るようにしておいた方が良いと思ったので、手順を書いておく。
必要がなければ読み飛ばしてOK</font>

1.まずは通常通りnodejsとnpmを入れる

```
$ sudo apt install -y nodejs npm
```
2.nというパッケージでバージョン管理が出来る模様なのでインストール

```
$ sudo npm -g install n
```
3.バージョン指定をしてインストール（とりあえずこのバージョンで動作確認出来たので）

```
$ sudo n 10.12.0
```
4.nパッケージでnodejsをインストールしてしまった後は、手順１で入れたnodejsとnpmは用済みなので、消す

```
$ sudo apt -y purge nodejs npm
```
※ この後一時的にnode コマンドが使えなくなるが、再ログインすれば使えるようになる。

### Redis-Serverのインストール
[この方](https://weblabo.oscasierra.net/redis-ubuntu1604-install-apt/)の記事を参考にした。
とても丁寧で分かりやすかった。かいつまんで書いておく。

```
$ sudo add-apt-repository ppa:chris-lea/redis-server
$ sudo apt update
$ sudo apt install redis-server
$ sudo systemctl enable redis-server
$ sudo systemctl start redis-server
```
※執筆時は3.0.6をインストールした。

### Laravel-Echo-Serverのインストール
```
$ sudo npm install -g laravel-echo-server
```


```
Laravelをインストールしたディレクトリで下記コマンドを実行する。
対話方式で進むので、空Enter対応してるとうまく繋がらないので注意

vagrant@vagrant:/var/www/sample$ laravel-echo-server init

? Do you want to run this server in development mode? No
? Which port would you like to serve from? 6001
? Which database would you like to use to store presence channel members? redis
? Enter the host of your Laravel authentication server. http://localhost
? Will you be serving on http or https? http
? Do you want to generate a client ID/Key for HTTP API? Yes
? Do you want to setup cross domain access to the API? Yes
? Specify the URI that may access the API: 127.0.0.1:80
? Enter the HTTP methods that are allowed for CORS: GET, POST
? Enter the HTTP headers that are allowed for CORS: Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization, X-CSRF-TOKEN, X-Socket-Id
? What do you want this config to be saved as? laravel-echo-server.json
appId: c05b50863b4d4exx
key: 083cac33e24033ddd495efffffffe1d
Configuration file saved. Run laravel-echo-server start to run server.
```

```
下記コマンドでlaravel-echo-serverが立ち上がる

vagrant@vagrant:/var/www/sample$ laravel-echo-server start
```
<font color="red">※こんなエラーを吐き続けた場合はRedis-serverが落ちているか、設定がなんかおかしいので見直す事</font>

```
[ioredis] Unhandled error event: Error: connect ECONNREFUSED 127.0.0.1:6379
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1113:14)
```

### Supervisorのインストール
```
$ sudo apt install -y supervisor
```


```
デーモン化とプロセス監視させるための設定
$ sudo vi /etc/supervisor/conf.d/laravel-echo-server.conf

中身はこんな
---------------------------------------------------
 1 [program:laravel-echo-server]
 2 directory=/var/www/sample/
 3 process_name=%(program_name)s_%(process_num)02d
 4 command=laravel-echo-server start
 5 autostart=true
 6 autorestart=true
 7 user=vagrant
 8 numproces=1
 9 redirect_stderr=true
10 stdout_logfile=/var/log/laravel-echo-server.log
---------------------------------------------------
```

```

設定ファイルを作成したら下記コマンドを実行しないと読み込まない模様

vagrant@vagrant:~$ sudo supervisorctl reread
```

```
設定ファイルを読み込ませたら下記コマンド実行

vagrant@vagrant:~$ sudo supervisorctl start all
laravel-echo-server:laravel-echo-server_00: started
```
ここまでがLaravel-Echo-Serverの設定

### Redisライブラリインストール
```
再び、ディレクトリはLaravelのプロジェクトディレクトリ内のお話になる。
下記コマンドを実行

vagrant@vagrant:/var/www/sample$ composer require predis/predis
```
### Laravel-Echoとsocket.ioインストール
```
vagrant@vagrant:/var/www/sample$ npm install laravel-echo
npm notice created a lockfile as package-lock.json. You should commit this file.
+ laravel-echo@1.5.2
added 1 package from 1 contributor and audited 1 package in 5.562s
found 0 vulnerabilities


┌───────────────────────────────────────────────────────────┐
│                  npm update check failed                  │
│            Try running with sudo or get access            │
│           to the local update config store via            │
│ sudo chown -R $USER:$(id -gn $USER) /home/vagrant/.config │
└───────────────────────────────────────────────────────────┘
```
<font color="red">↑インストールしたらこんなメッセージが出たので、内容に沿ってコマンドを実行した。</font>

```
vagrant@vagrant:/var/www/sample$ sudo chown -R $USER:$(id -gn $USER) /home/vagrant/.config
vagrant@vagrant:/var/www/sample$
vagrant@vagrant:/var/www/sample$
vagrant@vagrant:/var/www/sample$ npm install laravel-echo
+ laravel-echo@1.5.2
updated 1 package and audited 1 package in 0.518s
found 0 vulnerabilities

vagrant@vagrant:/var/www/sample$
vagrant@vagrant:/var/www/sample$
vagrant@vagrant:/var/www/sample$ npm install socket.io-client
+ socket.io-client@2.2.0
added 28 packages from 22 contributors and audited 49 packages in 4.138s
found 0 vulnerabilities

vagrant@vagrant:/var/www/sample$
```

#### config/app.phpの設定を変更
```
176行目にこんな内容がコメントアウトされてるのでコメントアウトを外す

// App\Providers\BroadcastServiceProvider::class, # コメントアウトを外す
```

#### .envの設定を変更
````
BROADCAST_DRIVER=log
↓
BROADCAST_DRIVER=redis

QUEUE_DRIVER=sync
↓
QUEUE_DRIVER=redis
````
<font color="red">ちなみにQUEUE_DRIVERはsyncでも動く。
その代わり、php artisan queue:work(後程記載する)が落ちてる間にpushされた内容は消えるので、redisにしとくのが無難かと。</font>

#### イベントの作成
```
vagrant@vagrant:/var/www/sample$ php artisan make:event MessagePush
```

ソースを作成されたファイルを開き、少々変更

```php5
 1 <?php
 2
 3 namespace App\Events;
 4
 5 use Illuminate\Broadcasting\Channel;
 6 use Illuminate\Queue\SerializesModels;
 7 use Illuminate\Broadcasting\PrivateChannel;
 8 use Illuminate\Broadcasting\PresenceChannel;
 9 use Illuminate\Foundation\Events\Dispatchable;
10 use Illuminate\Broadcasting\InteractsWithSockets;
11 use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
12
13 class MessagePush implements ShouldBroadcast  # 変更
14 {
15     use Dispatchable, InteractsWithSockets, SerializesModels;
16
17     public $messages;   # 追加
18
19     public function __construct( $messages )
20     {
21         $this->messages = $messages;   # 追加
22     }
23
24     public function broadcastOn()
25     {
26         // PrivateChannelの場合、routes/channels.phpで認証ルールを定義
27         // PrivateChannelで使ったチャンネルはbootstrap.jsでもEcho.private('channel-name').listen...と書かないと取得できない
28         return new Channel('channel-name');   # 追加
29 #        return new PrivateChannel('channel-name');
30     }
31
32     public function broadcastAs() # 追加
33     {
34         return 'push-test';
35     }
36
37 #    public function broadcastWith() {
38 #        return [ 'message' => $this->messages ];
39 #    }
40 }
```
<font color="red">※ 13行目のインターフェースは大事</font>
これがないと通知が飛んでいかない。
しばらくハマった。
このクラス内でpublic定義されている変数は全て通知のキー名として贈られる。
自分で定義したい場合はbroadcastWithを使えとの事。
参考：https://readouble.com/laravel/5.3/ja/broadcasting.html

通知イベントを発行する為、routes.phpに下記を追加

```php5
route::get('/push', function() {
    event( new App\Events\MessagePush('Test Message : ' . date('Y-m-d H:i:s')) );
});
```

#### resources/assets/js/bootstrap.jsの変更
```javascript
// 下記内容を追加
import Echo from 'laravel-echo'
window.io = require('socket.io-client');

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname,
});
$(function(){
    if (window.Echo) {
        window.Echo.channel('channel-name')
            .listen('.push-test', function(e) {
                console.log(e);
            });
    }
    console.log('onload');
});
```

コンパイルを行う

```
vagrant@vagrant:/var/www/sample$ npm run dev
```
※もし、エラーが出た場合は下記コマンドを実行してから再度上記コマンドを実行
　自分の場合はcross-envがないどうのこうのという内容だった（エビデンス取り忘れた）

```
vagrant@vagrant:/var/www/sample$ npm install
```

welcome.blade.phpに追記

```html
<script type="text/javascript" src="{{ asset('js/app.js') }}?{{ date('YmdHis') }}"></script>
```

この状態でページにアクセスするとこんな感じで数秒ごとにエラーを吐いているのが確認出来る。
![SnapCrab_NoName_2019-10-1_18-10-26_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39934/cf5b3244-ec2c-06fd-ac9d-59695f79165b.png)

nginxの設定ファイルに追記

```
vagrant@vagrant:/var/www/sample$ sudo vim /etc/nginx/sites-enabled/default
```

下記内容をserver{}の中に追記する

```
        location /socket.io/ {
                proxy_pass              http://127.0.0.1:6001;
                proxy_http_version      1.1;
                proxy_set_header        Upgrade $http_upgrade;
                proxy_set_header        Connection 'upgrade';
        }
```
nginx再起動

```
sudo systemctl restart nginx
```

nginxを再起動すると同時にブラウザのNetworkログが変化するのを確認
![SnapCrab_NoName_2019-10-1_18-14-59_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39934/0336bb43-ea18-545b-7ae2-7d324b1d2ba5.png)

送信した通知メッセージをWebSocketでレスポンスとして返すには下記コマンドを実行する

```
vagrant@vagrant:/var/www/sample$ php artisan queue:work
```

すると、ブラウザ画面でF5押しまくった分が一気に通知されてくる。
![SnapCrab_NoName_2019-10-1_18-17-53_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39934/189453ee-2f0b-a9db-5389-bc2a1864c653.png)

しかし、このままだとphp artisan queue:workが実行中の時しか動かないので、こいつもsupervisorを使ってデーモン化する

/etc/supervisor/conf.d/artisan-queue.conf

```
[program:artisan-queue]
directory=/var/www/sample/
process_name=%(program_name)s_%(process_num)02d
command=php artisan queue:work --tries=3
autostart=true
autorestart=true
user=vagrant
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/artisan-queue.log
```

作成したファイルをsupervisorに読むように設定する

```
vagrant@vagrant:~$ sudo supervisorctl reread
artisan-queue: available
```

supervisor再起動

```
vagrant@vagrant:~$ sudo supervisorctl stop all
laravel-echo-server:laravel-echo-server_00: stopped
vagrant@vagrant:~$
vagrant@vagrant:~$
vagrant@vagrant:~$ sudo supervisorctl start all
artisan-queue:artisan-queue_00: started
laravel-echo-server:laravel-echo-server_00: started
vagrant@vagrant:~$
```

#### Push通知の結果を保存させる
プッシュ通知に関するテーブルを作成する。

```
vagrant@vagrant:/var/www/sample$ php artisan queue:table
vagrant@vagrant:/var/www/sample$ php artisan queue:failed-table
vagrant@vagrant:/var/www/sample$ php artisan migrate
```

長文疲れた。
手順の漏れはないと思うけど、あったらご指摘頂けると。
誰かの役に立ちますように。