2022-06-10|閱讀時間 ‧ 約 10 分鐘

Laravel broadcasting 廣播機制

在Laravel中想達到websocket效果,由後端主動傳訊給前端,需使用broadcasting 將event廣播出去,由前端來接收訊息。
而想要廣播事件,需要透過driver來廣播,常用的有兩種機制可以選擇: 1. pusher: 有使用限制且須收費 2. Redis + socket.io: 免費無限制
因此在業界常看到使用Redis + socket.io的架構,也是本篇選擇的機制。

  • 從伺服器廣播訊息到前端接收的流程,大概會是這樣:
  1. 使用 Laravel broadcasting(Redis) 廣播 Event 到 Queue(Redis)。
  2. Laravel Queue Listener 讀取Event,並使用Redis的Sub/Pub機制把Event發送給laravel-echo-server。
  3. laravel-echo-server 接收到 Event,並透過socket.io將Event發送给laravel-echo。
  4. 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廣播出去。
  • 新增一個出貨狀態更新的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:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <title>Laravel</title>
        
    </head>
    <body>

        <script src="js/app.js"></script>
        <script>
            window.Echo.channel('order')
            .listen('.OrderUpdated', (e) => {
                console.log(e.order);
            });
        </script>
    </body>
</html>
  • 其中script src="js/app.js"/script這行就是引用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
  • 安裝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:
$ 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即可降版。

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.