在Laravel中想達到websocket效果,由後端主動傳訊給前端,需使用broadcasting 將event廣播出去,由前端來接收訊息。
而想要廣播事件,需要透過driver來廣播,常用的有兩種機制可以選擇:
1. pusher: 有使用限制且須收費
2. Redis + socket.io: 免費無限制
因此在業界常看到使用Redis + socket.io的架構,也是本篇選擇的機制。
- 使用 Laravel broadcasting(Redis) 廣播 Event 到 Queue(Redis)。
- Laravel Queue Listener 讀取Event,並使用Redis的Sub/Pub機制把Event發送給laravel-echo-server。
- laravel-echo-server 接收到 Event,並透過socket.io將Event發送给laravel-echo。
- laravel-echo解析接收到的 Event。
- 關於事件廣播的設定檔在這: config/broadcasting.php
pusher/ably/redis/log這些是廣播用的driver,log是在local開發用的。
安裝與設定Redis
若無安裝過predis需先安裝:
$ composer require predis/predis
.env設定:
QUEUE_CONNECTION=redis
BROADCAST_DRIVER=redis
如同上述的流程,QUEUE 與 BROADCAST driver都是選用redis。
註冊BroadcastServiceProvider
在使用廣播之前,需要先註冊BroadcastServiceProvider,在config/app.php 設定檔中找到 providers 陣列,並取消註解App\Providers\BroadcastServiceProvider即可:
App\Providers\BroadcastServiceProvider預設是被註解的,需打開:
新增Event來廣播
假設出貨狀態更新時,我們想要主動通知前端這個訊息,就必須把這個Event廣播出去。
$ php artisan make:event ShippingStatusUpdated
將code改成如下:
- implements ShouldBroadcast是為了當event被觸發時,把event廣播出去。
- broadcastOn()用來設定要廣播到哪個頻道,如果我們希望只有這個訂單建立者可以查看更新狀態,就要在訂單的私人頻道廣播這個event,如L26註解的。
- 廣播頻道有三種可以選: Channel/PrivateChannel/PresenceChannel
- Channel 代表任何使用者都可以訂閱的公共頻道,而 PrivateChannel 和 PresenceChannel 則代表需要授權的私人頻道。
- broadcastAs()用來自訂廣播名稱。
定義頻道授權規則
- 如果是用PrivateChannel,需要定義頻道授權規則,如果是公共頻道(Channel)就不用。
- 在routes/channels.php這邊可以定義頻道授權規則,比如「驗證任何在 order.{orderId} 的私人頻道上嘗試監聽的使用者是否為實際該訂單的建立人」:
Broadcast::channel('order.{orderId}', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
- 注意,如果是用JWT來做認證,需修改BroadcastServiceProvider:
原本是 Broadcast::routes(); 改成:
Broadcast::routes(['middleware' => ['auth:api']]);
自訂廣播名稱:
Laravel 預設會使用Event類別名稱去廣播事件,要自訂廣播名稱需定義broadcastAs() method:
public function broadcastAs()
{
return 'OrderUpdated';
}
Laravel Echo
$ npm install --save socket.io-client
$ npm install --save laravel-echo
resources/js/bootstrap.js最下方有寫好範例程式,去掉註解,改成用socket.io:
import Echo from 'laravel-echo';
window.io = require('socket.io-client');
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001'
});
build code:
$ npm run dev
- 會將resources/js build到 public/js(resources/js/app.js => public/js/app.js)
- 由於app.js中有引用bootstrap.js,所以在頁面中引用app.js就能使用Echo了。
在 resources/views/ 下建立頁面 echo_ex.blade.php:
Laravel
window.Echo.channel('order')
.listen('.OrderUpdated', (e) => {
console.log(e.order);
});
- 其中這行就是引用public/js/app.js。
- 這邊值得提的是,listen OrderUpdated前面要多一個「.」,OrderUpdated是對應到broadcastAs()自訂的廣播名稱。
- Laravel Echo 會需要存取當前 session 的 CSRF token,header中要設定CSRF token。
- L13~L16就是laravel echo在監聽laravel echo server透過socket.io傳過來的訊息。
Laravel Echo Server
$ npm install -g laravel-echo-server
- project根目錄下,初始化laravel-echo-server,所有問題都用預設的:
$ laravel-echo-server init
會在根目錄產生一個laravel-echo-server.json,把devMode改成true方便debug:
$ laravel-echo-server start
Demo
trigger OrderController ship() method,
event(new ShippingStatusUpdated($order)); 這行會觸發event並廣播出去。
- 啟動laravel echo server與queue listener,瀏覽器開啟前端,在監聽到event的時候把message print出來。
- 如上述code,為了簡便,本範例是以廣播至公共頻道來demo,雖然訂單狀態應該是只有建立者可以看的到,需改成私有頻道會較合理。若今天應用情境是新聞推播系統,就很符合範例用的公共頻道。
後記
若發現Channel name多出prefix,可在config/database.php中設定拿掉:
laravel_database_就是prefix。
- 將prefix那行註解,就會變成Channel: order。
Laravel Echo listen不到訊息
發現前端一直收不到訊息,解法是將socket.io-client降為安裝2.3.0版本的。(原本裝的是4.5.1)
修改package.json後再下npm install即可降版。