LaravelでBroadcasting(Echo)を利用する場合、laravel-echo-serverを利用する方法とpusherを利用する方法があります。
laravel-echo-serverを利用する場合、別途redisの構築も必要になるなど、めんどくさいので、今回はpusherを利用する方法を試してみます。
Pusherの準備
まずはPusherから。
アカウント作成
アカウントがない場合は作成します。
無料で1日100connection(同時接続数), 20万配信までできるようです。
App作成
アカウントができたら、アプリケーションを作成します。
アイコンでfrant-endとしてJSを、backendとしてLaravelを選んでおきます。
- front-end : JS(Vanilla JS)
- back-end : Laravel
アプリケーションが生成されたら、App Keys情報を確認しておきます(これを.envに設定します)。
Pusher側の設定はこれだけ。
Laravel(環境設定)
続いてLaravel側でPusherを利用する設定を行います。
ライブラリのインストール
pusherの利用に必要なライブラリをインストールします。
composer require pusher/pusher-php-server
.env(抜粋)
必要な変数を設定します。
BROADCAST_DRIVER=pusher
下記はPusherのApp Keysで確認できる値となります。
PUSHER_APP_ID=6xxxxx
PUSHER_APP_KEY=9695e12b43abxxxxxxxx
PUSHER_APP_SECRET=0ae47e5a9448xxxxxxxx
PUSHER_APP_CLUSTER=apx
config/app.php(抜粋)
app.phpの下記の行をコメントインします。
App\Providers\BroadcastServiceProvider::class,
Laravel(Eventの実装)
Eventファイルの作成
下記コマンドを実行してEventを作成します。app/Events以下にファイルが生成されます。
なお、イベント(Channel)の種類には全員に配信するパブリックチャネルと特定のクライアントに配信するプライベートチャネルがありますが、とりあえずパブリックチャネルを実装してみます。
php artisan make:event PublicEvent
Eventの実装
以下の用に記述します。
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
+class PublicEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct()
{
//
}
public function broadcastOn()
{
+ return new Channel('public-event');
}
+ public function broadcastWith()
+ {
+ return [
+ 'message' => 'PUBLIC',
+ ];
+ }
}
ルートの追加
Eventの発火は任意のタイミングで行えますが、今回は特定のURLにアクセスしたら発火するようにします。
/public-eventにアクセスするとイベントが発火します。
Route::get('/public-event', function(){
broadcast(new \App\Events\PublicEvent);
return 'public';
});
サーバ側の実装は以上です。
Laravel(クライアント側の実装)
続いてクライアント側です。
なお、個人的なゴールはReactNativeからの利用なのでここでは最低限の実装しかしません。すみません。
ライブラリのインストール
npm install --save laravel-echo pusher-js
bootstrap.js
とりあえずresources/js/bootstrap.jsの最後を以下の用にします。既にpusherのための記述がされているのでコメントインして、サーバ側で設定したEvent(Channel)を購読するコードを追加します。
import Echo from 'laravel-echo'
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
encrypted: true
});
//追加
window.Echo.channel('public-event')
.listen('PublicEvent', (e) => {
console.log(e);
})
app.js
以下の記述はいらないのでコメントアウトしておきます。
// const app = new Vue({
// el: '#app'
// });
周辺ライブラリのインストールとコンパイル
npm install && npm run devして、コンパイルします。
npm install
npm run dev
welcome.blade.phpの編集(表示)
配信された情報を表示しています。今回はconsole.log()でbroadcastWith()で送られてきたオブジェクト内容を表示するだけです。
そのために元々存在するwelome.blade.phpに以下の記述を追加します。
csrf tokenの追加
購読の認証の際にcsrf tokenが必要らしいのでtokenを連携できるよう記述します。
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="csrf-token" content="{{ csrf_token() }}">
(bootstrap.jsとかに書いた)jsの呼び出し
先程書いたjsがコンパイルされた結果のapp.jsを参照するための記述を追加します。
+<script src="{{ asset('js/app.js')}}"></script>
</body>
動作確認
Laravelの起動(購読側)
serveして、トップページ(welcome.blade.php)を開き、デバッグコンソールを開いてきます。
php artisan serve
Eventの発火
http://localhost:8000/public-event にアクセスしてEventを発火させます。
Console画面の確認
PUBLICと表示されていればOKです。
Private-Channel
では続いてPrivate-Channelの実装を行ってみます。
認証機能の追加
Private-ChannelではLaravelで認証された特定のユーザーに対する配信となるので、まずは認証を実装します。
また、認証方法としてはAPI経由での認証を利用します。
Userのマイグレーションファイルを以下のように変更してmigrateします。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
+ $table->string('api_token')->unique()->nullable();
});
}
public function down()
{
Schema::dropIfExists('users');
}
}
migrate。既にmigrateしてる場合はrefreshします。
php artisan migrate
tinkerを利用して認証で利用するユーザーを追加しておきます。
php artisan tinker
>>> $user = new App\User;
=> App\User {#2907}
>>> $user->name = 'user1';
=> "user1"
>>> $user->email = 'user1@test.com';
=> "user1@test.com"
>>> $user->password = Hash::make('testtest');
=> "$2y$10$pN7.zZyJ87zgusHvkjkMv.bnFyT/g174WTi7kMzezES8V5KWPMGp."
>>> $user->api_token = 'user1user1';
=> "user1user1"
>>> $user->save();
=> true
認証方法の変更
デフォルトではBroadcast用のAPIにはWebミドルウエアが適用されるのでAPIミドルウエアが適用されるように変更します。
app/Providers/BroadcastServiceProvider.phpを以下のように変更します。
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
class BroadcastServiceProvider extends ServiceProvider
{
public function boot()
{
+ Broadcast::routes(['middleware' => 'auth:api']);
require base_path('routes/channels.php');
}
}
PrivateEventの追加
Private用のEventを生成します。
php artisan make:event PrivateEvent
PrivateEvent
とりあえず以下のように実装します。
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
+class PrivateEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct()
{
//
}
public function broadcastOn()
{
+ return new PrivateChannel('private-event');
}
public function broadcastWith()
{
return [
+ 'message' => 'PRIVATE',
];
}
}
routes/channel.php
Private-Channelではchannel.phpでルート毎に認証を実装する必要がありますが、ここでは一旦trueを返し、常に認証OKとします。
Broadcast::channel('private-event', function () {
return true;
});
routes
Private-Eventを発火させるルートを定義します。
もちろん、発火はルートへのアクセスである必要がありません。
Route::get('/private-event', function(){
broadcast(new \App\Events\PrivateEvent);
return 'private';
});
bootstrap.js(抜粋)
クライアント側の処理として、private-eventを購読するよう設定します。
window.Echo.connector.pusher.config.auth.headers['Authorization'] = 'Bearer user1user1';
window.Echo.connector.pusher.config.auth.headers['Accept'] = 'application/json';
window.Echo.connector.pusher.config.authEndpoint = 'http://localhost:8000/broadcasting/auth';
window.Echo.private('private-event')
.listen('PrivateEvent', (e) => {
console.log(e);
});
動作確認
ここまでで一度動作を確認してみます。
/private-eventにアクセスし、welcome.blade.phpのコンソールにPRIVATEと表示させるか確認します。
PrivateChannelの改善
PublicとPrivate-Channelが使えるようになりましたが、上記の実装では全員がprivate-eventという同じイベントを購読するため機能的にPublicと変わりありません。
ユーザー毎に異なったChannelを購読するよう変更します。
Eventの引数に$userを渡す。
Route::get('/private-event', function(){
//user1を取得
$user = App\User::find(1);
//userを渡す
broadcast(new \App\Events\PrivateEvent($user));
return 'private';
});
チェネル名をprivate-channel.user_idとなるよう変更
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use App\User;
class PrivateEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function broadcastOn()
{
return new PrivateChannel('private-event.' . $this->user->id);
}
public function broadcastWith()
{
return [
'message' => 'PRIVATE',
];
}
}
channel.phpで簡易認証を行う
Broadcast::channel('private-event.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
クライアント側でもprivate-channel.user_idのChannelを購読する
const user_id = 1;
window.Echo.connector.pusher.config.auth.headers['Authorization'] = 'Bearer user1user1';
window.Echo.connector.pusher.config.auth.headers['Accept'] = 'application/json';
window.Echo.connector.pusher.config.authEndpoint = 'http://localhost:8000/broadcasting/auth';
window.Echo.private('private-event.' + user_id.toString())
.listen('PrivateEvent', (e) => {
console.log(e);
});
以上。