Skip to content

Laravel Reverb - Websocket

Published: at 12:00 AM

Laravel Reverb

Laravel Reverb 是 Laravel 11 新增的一個套件,適用於 Websocket 的應用程式開發,這篇嘗試使用 Laravel Reverb 來建立一個簡單的 Websocket 應用程式。

Laravel 的 Channel 種類:(根據目前查詢到的資料)

  • Channel

公開的頻道,任何人都可以訂閱

  • Presence Channel

需要驗證的頻道,只有登入的使用者可以訂閱,
訂閱 並且可以知道有多少人在,他會追蹤頻道訂閱者的狀態

  • Private Channel

需要驗證的頻道,只有特定的使用者可以訂閱

reverb

安裝

php artisan install:broadcasting

啟動服務

php artisan reverb:start --debug # 開發時使用相對來說比較簡單(有打印訊息的話可能會比較慢,正式環境不建議使用)

基礎設定(這邊要確保正確,否則會無法正常運作)

BROADCAST_CONNECTION=reverb
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http

Credential - 憑證

REVERB_APP_ID="my-app-id"
REVERB_APP_KEY="my-app-key"
REVERB_APP_SECRET="my-app-secret"

# 如果用 vite 的話
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="localhost"
VITE_REVERB_PORT=8080
VITE_REVERB_SCHEME=http

使用 reverb 是透過事件觸發來推發廣播資訊,因此要先建立事件 event

事件需要實踐 ShouldBroadcast 這個介面。

Laravel 方法Laravel 方法回傳必填描述Laravel Echo 對應的方法
broadcastOn()參照: ShouldBroadcasttrue即將要發送的頻道
(當要發送時,選定要發送的頻道;可以有多個頻道)
Echo.channel('{{ 這裡 }}')
broadcastAs()stringfalse監聽事件的名稱,如果未定義則就是 event 本身名稱
但如果有定義的話,前端監聽名稱需要增加 . 為前綴
Echo.channel('test-channel').listen('{{ 這裡 }}', ev=>{ console.log(ev) })
broadcastWith()arrayfalse前端接收到的內容,也就是 listen(‘TestEvent’, ev=>{ 這個 ev }) 改變資料格式listen('TestEvent', ev=>{}) 就是這個 ev 可以改變成自己想要的內容

開發需要的指令

嘗試使用:(先使用 Public Channel)

個人習慣是先從簡單的開始測試,如果以廣播來說,公共頻道應該是最簡單的。

事件需要實作 ShouldBroadcast 介面

class TestEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public string $message = '') { /** empty section */ }

    public function broadcastOn()
    {
        return new Channel('test-channel');
    }

    public function broadcastWith(): array
    {
        return ['msg' => 'pub channel say: ' . $this->message];
    }
}

由於 Laravel 在跑 install:broadcasting 時,會自動幫我們建立一個 resources/js/echo.js 的檔案,並且加載進入 app.js 中, 因此我們可以直接使用 window.Echo 來監聽事件

window.Echo.channel('test-channel')
   .listen('TestEvent', (e) => {
       console.log(e);
  });

嘗試使用:(再來是 Private Channel)

Private Channel 需要驗證,因此需要先確認使用者是否登入,
主要多了兩件事情:

    1. (這框架會默默幫你做掉,但是僅限於 session/cookie 的使用者)使用者登入的認證: 會是我下面說的 /broadcasting/auth 路由
    1. 多定義 routes/channels.php 的認證方法。 (broadcastOn() 要去想一下怎麼對)
  • app/Events/PrivateEventPublish.php
class PrivateEventPublish implements ShouldBroadcast
{
   use Dispatchable, InteractsWithSockets, SerializesModels;

   public function __construct(public string $message) { }

   public function broadcastOn(): array
   {
       return [
           new PrivateChannel('private-channel.' . auth()->id()),
       ];
   }

   public function broadcastAs(): string
   {
       return 'private-event';
   }

   public function broadcastWith(): array
   {
       return [
           'msg' => 'from private: ' . $this->message,
           'time' => now()->format('Y-m-d H:i:s'),
       ];
   }
}
  • routes/channels.php
Broadcast::channel('private-channel.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

基本上就是看 broadcastOn 的值,去跟 channels.php 去做對應。

也就是: Broadcast::channel()

  • 第一個參數為頻道名稱 -> 會對應到 broadcastOn() 的值(含辨識值)
  • 第二個參數為 callback -> 用來判斷是否可以訂閱該頻道
    • 這邊的 callback 會有兩個參數,第一個是使用者,第二個是辨識值

一開始機制是使用 session/cookie 來進行驗證,思考看看能否改成 token 來進行驗證

上述,使用 session/cookie 來驗證使用者是否登入,那這邊要想辦法改成 token 的機制。

  1. 多註冊一個路由,並且幫他掛上驗證的 middleware
Broadcast::routes(['prefix' => 'api', 'middleware' => ['auth:sanctum']]);
  1. 前端 Laravel Echo 要調整驗證路由
window.Echo = new Echo({
    // 省略一些設定
    // 需要關注的是下列 authorizer 設定
    authorizer: (channel, options) => {
        return {
            authorize: (socketId, callback) => {
            fetch('/api/broadcasting/auth', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: 'Bearer ' + localStorage.getItem('token'),
                },
                body: JSON.stringify({
                    socket_id: socketId,
                    channel_name: channel.name
                })
            })
                .then(response => response.json())
                .then(data => {
                    callback(false, data);
                })
                .catch((error) => {
                    callback(true, error);
                });
            }
        };
    },
});

Queue worker vs Reverb server

queue-work-with-echo-server


Broadcasting network 看到的東西進而下去追蹤

Private/Presence/Public Channel

broadcasting 如果是走私有頻道,需要確定認證機制,他會去用 laravel 的 broadcasting/auth 路由驗證使用者是否登入

private-channel-network

需驗驗證的頻道流程

vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.phproutes 方法
透過該方法,當執行到客戶端與 ws 建立連線後,隨後會透過 broadcasting/auth 路由去驗證使用者是否登入,
如果使用者未登入,則會回傳 403,否則回傳需要監聽的頻道資訊。(暫時未看完整個流程)

嘗試找出 broadcasting/auth 的路由做了什麼事:

到這邊有點卡關,換個方向思考看看
既然他是 Facade,那我直接呼叫呢?

已經快到最後了,這邊的話可以先埋個 dd(method_exists($this->pusher, 'authorizeChannel')) 看他會跑去哪裡 看到回傳是 true,則進到 authorizeChannel 方法

debug-8

參考資料


Previous Post
Chrome DevTools Protocol
Next Post
Ollama(01)