Laravel5.3+EchoでWebSocketsを簡単に実装する

本記事は Qiita Advent Calendar 2016 – Laravel の7日目の記事です。今年春にLaravelに入門して秋にクライアントのサービスを1つリリースしました。Laravel良いですね。使いやすくて今年はとてもお世話になりました。今回はそんなLaravelの中でも好きな機能のひとつであり開発でも実際に利用したイベントブロードキャストによるWebSockets連携について書きたいと思います。今回の記事用に作ったサンプルプログラムなど記事末尾にリンクを記載してます。

LaravelはWebSocketsサポートが明確になっている

元々PHPはWebSocketsのサポートが弱くて、たとえばCakePHPで調べてみるとマニュアルに記載がなくStackOverflowでもratchetやreactPHPが紹介されてるのみです(もし公式手順あるようだったら教えてください)。Rails5であればActionCable、DjangoであればChannelsなど、Node.jsに限らずRubyやPython等でもWebSocketsが公式サポートになってきています。

そんなWebSocketsにちょっと後ろめたいPHPフレームワークの中でもLaravelはWebSocketsのサポートが明確になっています。イベントブロードキャストという機能を利用します。Laravel5.1から利用できるようになっています。公式なのが素晴らしい。

イベントブロードキャストとは?

とは言えLaravelにreactPHPのようなノンブロッキング実装があるわけではないので、そこはクラウド(Pusher)であったり、Node.js(+ Socket.io + Redis)の力を借りて実現しています。つまり餅は餅屋的なソリューションなんですが、個人的には変にPHP実装にこだわるより性能・実績面で枯れているソリューションとうまく統合するというアプローチに好感を持っています。

どのような統合がされているかというと、Laravelのイベントの仕組みを利用するようになっていて、たとえば下記のようにイベントを定義しアプリケーションの任意のポイントでイベントを発行します。

ポイントとしては、ShouldBroadcastインタフェースを実装しpublicで情報を保持する、broadcastOnメソッドで配信先のチャネル名を指定する、というくらいです。publicで保持した情報がシリアライズされてブラウザ側に通知される事になります。

class SomeEvent implements ShouldBroadcast
{
    use InteractsWithSockets, SerializesModels;

    public $someInfo;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(SomeInfo $someInfo)
    {
        $this->someInfo = $someInfo;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('channel-name');
    }
}

イベント発行はどこでも大丈夫です。コントローラやモデルでも大丈夫ですし、コマンドの中からでもうまく動きます。

event(new SomeEvent($someInfo));

“PHP”で考える事はこれだけです。PusherやRedisに連携する部分はLaravelが隠蔽してくれているので業務価値の高い開発に集中する事ができます。ちなみにこのShould*系のインタフェース良いですよね(使い場所違いますがShouldQueueインターフェースもimplementsするだけで振る舞いが変わってくれて素敵です)。Laravel使っていて好きなところのひとつです。

Laravel Echoでさらに簡単に

ぼくはLaravel5.2から入門して、開発したサービスでも5.2時点の情報や利用できる技術をベースに開発してました。その時にイベントブロードキャストを検証した記事が下記です。

Laravel5とWebSockets: Broadcasting Eventsを試してみる

この時のイベントブロードキャストも上記のShouldBroadcastインタフェース等は使えましたしそこは変わってないんですが、Laravel5.3ではさらに進化しています。今からLaravelでWebSocketsを利用する人は5.3 + Echoの構成が良いと思います。

今回簡単に検証してみたんですが個人的に大きく変わったなと思うのは下記の点です。

  • 認証・認可チェックを行うPrivateChannelの導入
  • ブラウザ側での開発を容易にするLaravel Echo
  • Node.jsプログラミングが不要になるLaravel Echo Server(非公式)

簡単にざっくり言うと「プライベートなチャットや通知もラクラク実装できる(PrivateChannel)」「Socket.io知らなくてもOK(Laravel Echo)」「Node.jsでプログラミングできなくてもOK(Laravel Echo Server)」という事です。

PrivateChannel

PrivateChannelは認証・認可を通過したユーザーでないと購読できないチャネルを作る事ができる機能です。イベント定義のbroadcastOnメソッドでPrivateChannel(またはその配列)を返せばOKです。

    public function broadcastOn()
    {
        return new PrivateChannel('channel-name.' . + $this->user->id);
    }

そしてこのチャネルを購読するための認可条件ですが、下記のように認証ユーザーが購読可能かの認可条件をBroadcastServiceProviderでクロージャで設定します。

    public function boot()
    {
        Broadcast::routes();

        Broadcast::channel('channel-name.*', function ($user, $userId) {
            return (int) $user->id === (int) $userId;
        });
    }

Broadcast::routes()メソッドで/broadcasting/authというパスが有効になっており、ブラウザがLaravel Echo Server等のWebSocketsサーバーに接続した際にWebSocketsサーバー側でこのパスにアクセスし認可の可否を判定します。認可OKであればブラウザがこのチャネルを購読します。

前はこれをNode.js側のプログラミングでなんとかしてたんですよねぇ…。だいぶラクになってると思います。ちなみにNode.js側でやる場合は以前作ったサンプルが参考になるかもしれません。やっている事はブラウザがNode.jsと接続した際にLaravelのセッションIDを確認してブラウザの認証が通っているか確認してるのと、認証しているユーザー情報を保持しておいてイベントが発行された際にイベントで用意した宛先フィールドを見て個別のユーザーへemitするという事をしています。ちなみに後述のLaravel Echo Serverを使えばこんな事はもう不要です。

Laravel Echo

laravel/echo

NPMパッケージになっています。今のところPushserを使わなくてもpusher-jsも同時にインストールする必要があります。

$ npm install --save laravel-echo pusher-js

Socket.io Clientを隠蔽して、イベントブロードキャストに適した形でJavaScriptの実装を進める事ができます。

import Echo from "laravel-echo"
window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: '192.168.33.40:6001', // Laravel Echo Serverのアドレスを指定
    // namespace: ''
});

// PrivateChannelの購読
Echo.private('channel-name.' + someId)
    .listen('EventName', (e) => {
      // サーバーからメッセージを受け取る処理
      console.log(e);
      this.messages.unshift(e.message);
    });

Socket.io Clientも別に難しくなかったのであまり有り難みはないのですが、EchoはLaravelのイベントモデルに対応してるので特別な理由がなければ使ったほうがプログラミングは自然になると思いました。

Laravel Echo Server

tlaverdure/laravel-echo-server

公式のものではないようですがコミュニティの有志が作ったLaravel Echoに対応したNode.js + Socket.io実装です。Laravelのマニュアルにも記載があるのでいちおうお墨付きですね。これのおかげでNode.jsのプログラミングが要らなくなりました。ブロードキャストメッセージだけだったらNode.jsのプログラミングも数行で終わるんですが、ユーザー個別に認証まで確認して…というような事をしようとするとNode.jsあまり使ってない人は面倒に感じるかもしれません。Laravel Echo ServerはLaravelの通常のChannelはもちろんPrivateChannelも合わせてうまいことやってくれます。

$ npm install -g laravel-echo-server

各種接続情報をlaravel-echo-server.jsonに定義して(laravel-echo-server initで対話定義可能)、あとは下記のコマンドでサーバーを起動するだけです。

$ laravel-echo-server start

Laravel Echo + Laravel Echo Serverを使ったサンプルプログラム

検証用に作ったサンプルをGitHubに置いてます。

takayukii/example-Laravelchat-Echo-Socket.io

動作イメージとしては下記の動画のような感じです。未編集で見苦しいですが、下記を検証してます。

  • PrivateChannelによる個別のチャット
  • Channelによるブロードキャスト(コマンドからPUSH)
  • コードを書き換えて認証しているユーザーIDと異なるIDを指定した場合に認可失敗するか

要点だけ知りたい人は下記のコミットが参考になると思います。

ハマったところ

残念ながらLaravel Echo Serverのエラーメッセージがちょっと不親切でけっこうハマりやすかったです。検索するとLaravel Echo Serverで未解決なLaracastsやStackOverflowがたくさんありました。まだ出てきたばかりなのでこれからに期待ですね。

特にハマりやすいところはチャネル名とイベント名の指定でした。Laravel・JavaScriptで数か所に渡って指定する必要があるんですが、概念があやふやなうちは間違えやすいです。間違えやすい状態でEcho Serverがまた不親切なのでハマる、という感じでした。下記が特に具体的にハマりがちなところです。

Socket.io的にイベントがきてるのにEchoのlistenが実行されない

チャネルの購読はできてるのに受信したイベントの指定の仕方が誤っているパターンだと思います。

こちらのLaracastsでも同様の症状がありますが、ぼくの場合はこれはイベント名の指定の仕方が合ってなかったです。Developer ConsoleでWebSocketsのフレームを確認すると実際のイベント名が確認できますが、これとEcho側で設定するnamespaceも含めたイベント名が正しく一致するか?というのをデバッグしたほうがよいです。下記のLaracastsの場合のようにQueueを使うと特にハマりやすい気がします。

Laravel echo listening is not receiving data

接続時に “Could not send authentication request.” というエラーが出てる

Laravel Echo Serverでこんなエラーが出ます。チャネルの仕方が間違ってるか、認可エラーが出てます。

エラーメッセージが悪すぎて接続の設定が悪いのかなと思っちゃうんですが、これは単純にLaravelでPrivateChannelの購読認可に失敗してる時も出ます。ちなみに購読認可に失敗する場合はBroadcastEventProviderの認可ロジックに辿り着く前にそもそもチャネル名の指定の仕方がだめでそれで認可エラーになってこのメッセージが出るというパターンが多いと思います。PrivateChannelは*とかID等の可変の情報を加えたりしてチャネル定義したり購読したりするのでここが噛み合わないといつまでもこのエラーに悩まされると思います。

ちなみに現バージョンのLaravel Echo Serverでエラーメッセージを深掘りしたい場合で注意なんですが、なぜかNPMのdistに含まれるprivate-channel.jsが重複してファイルが3つあったりして罠が多かったです。

まとめ

ということで、簡単な記事になりましたが、LaravelのイベントブロードキャストとEchoの紹介でした。ここでは触れてないですがPresenceChannelというチャットの参加状況などの可視化ができる機能も追加されているようです(まだ触ってない)。以前イベントブロードキャストを調べた時から半年ほどでだいぶ進化しています。

ちなみに今回新しくlaravel newして5.3をまっさらな状態で触りましたが、BrowserifyからWebPackに公式が変わっていたり、Vue.jsが組み込まれていたりフロント周りもかなり精力的に変わってますね。SPAとWebSocketsはともに相性がよくまたモダンなWebアプリケーションでは重要な要素だと思います。Laravelが今後どのように対応していくか特にこの2点はHEADを追いかけていっても面白そうだなと思いました。