深入淺出 Laravel Echo

coding01發表於2018-09-20

看原始碼,解析一次完整的 public channel 下發流程。

深入淺出 Laravel Echo

此圖來自網上,如有侵權,通知我刪除

通過上圖,我們至少要知道兩件事:

  1. Laravel 和我們的前端 (vue) 沒有直接關聯,它們通過 Socket.io Server 來做中轉,這是怎麼做到的呢?
  2. 怎麼傳送 Brocadcasted Data?

下面來一一解析。

BroadcastServiceProvider

深入淺出 Laravel Echo

BroadcastServiceProvider 主要包含了 Broadcast 相關的五個驅動器、Broadcast 事件、Broadcast 佇列等方法,比較簡單就不在解析了,今天主要說說怎麼通過 redis 來驅動 Broadcast 的。

首先還是簡單配置下 Broadcastconfig

// broadcasting.php
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Default Broadcaster
    |--------------------------------------------------------------------------
    |
    | This option controls the default broadcaster that will be used by the
    | framework when an event needs to be broadcast. You may set this to
    | any of the connections defined in the "connections" array below.
    |
    | Supported: "pusher", "redis", "log", "null"
    |
    */

    'default' => env('BROADCAST_DRIVER', 'null'),

    /*
    |--------------------------------------------------------------------------
    | Broadcast Connections
    |--------------------------------------------------------------------------
    |
    | Here you may define all of the broadcast connections that will be used
    | to broadcast events to other systems or over websockets. Samples of
    | each available type of connection are provided inside this array.
    |
    */

    'connections' => [

        'pusher' => [
            'driver' => 'pusher',
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'app_id' => env('PUSHER_APP_ID'),
            'options' => [
                //
            ],
        ],

        'redis' => [
            'driver' => 'redis',
            'connection' => 'default',
        ],

        'log' => [
            'driver' => 'log',
        ],

        'null' => [
            'driver' => 'null',
        ],

    ],

];

// .env
BROADCAST_DRIVER=redis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
複製程式碼

之前瞭解過 Laravel 的 ServiceProvider 的工作原理,所以我們就不用贅述太多這方面的流程了,我們主要看看 BroadcastServiceProvider 的註冊方法:

public function register()
{
    $this->app->singleton(BroadcastManager::class, function ($app) {
        return new BroadcastManager($app);
    });

    $this->app->singleton(BroadcasterContract::class, function ($app) {
        return $app->make(BroadcastManager::class)->connection();
    });

    $this->app->alias(
        BroadcastManager::class, BroadcastingFactory::class
    );
}
複製程式碼

我們寫一個傳送 Broadcast demo:

// routes/console.php
Artisan::command('public_echo', function () {
    event(new RssPublicEvent());
})->describe('echo demo');

// app/Events/RssPublicEvent.php
<?php

namespace AppEvents;

use CarbonCarbon;
use IlluminateBroadcastingChannel;
use IlluminateQueueSerializesModels;
use IlluminateBroadcastingPrivateChannel;
use IlluminateBroadcastingPresenceChannel;
use IlluminateFoundationEventsDispatchable;
use IlluminateBroadcastingInteractsWithSockets;
use IlluminateContractsBroadcastingShouldBroadcast;

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

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

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

    /**
     * 指定廣播資料。
     *
     * @return array
     */
    public function broadcastWith()
    {
        // 返回當前時間
        return ['name' => 'public_channel_'.Carbon::now()->toDateTimeString()];
    }
}
複製程式碼

有了這下發 Event,我們看看它是怎麼執行的,主要看 BroadcastEventhandle 方法:

public function handle(Broadcaster $broadcaster)
{
    // 主要看,有沒有自定義該 Event 名稱,沒有的話,直接使用類名
    $name = method_exists($this->event, 'broadcastAs')
            ? $this->event->broadcastAs() : get_class($this->event);

    $broadcaster->broadcast(
        Arr::wrap($this->event->broadcastOn()), $name,
        $this->getPayloadFromEvent($this->event)
    );
}
複製程式碼

先看怎麼獲取引數的 $this->getPayloadFromEvent($this->event)

protected function getPayloadFromEvent($event)
{
    if (method_exists($event, 'broadcastWith')) {
        return array_merge(
            $event->broadcastWith(), ['socket' => data_get($event, 'socket')]
        );
    }

    $payload = [];

    foreach ((new ReflectionClass($event))->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
        $payload[$property->getName()] = $this->formatProperty($property->getValue($event));
    }

    unset($payload['broadcastQueue']);

    return $payload;
}
複製程式碼

主要傳入我們自定義的陣列,見函式 $event->broadcastWith()、['socket' => data_get($event, 'socket')] 和 Event 中定義的所有 public 屬性。

最後就是執行方法了:

$broadcaster->broadcast(
    Arr::wrap($this->event->broadcastOn()), $name,
    $this->getPayloadFromEvent($this->event)
);
複製程式碼

看上面的例子,$this->event->broadcastOn() 對應的是:

return new Channel('public_channel');
複製程式碼

好了,該是看看介面 Broadcaster 了。

<?php

namespace IlluminateContractsBroadcasting;

interface Broadcaster
{
    /**
     * Authenticate the incoming request for a given channel.
     *
     * @param  IlluminateHttpRequest  $request
     * @return mixed
     */
    public function auth($request);

    /**
     * Return the valid authentication response.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  mixed  $result
     * @return mixed
     */
    public function validAuthenticationResponse($request, $result);

    /**
     * Broadcast the given event.
     *
     * @param  array  $channels
     * @param  string  $event
     * @param  array  $payload
     * @return void
     */
    public function broadcast(array $channels, $event, array $payload = []);
}
複製程式碼

這裡主要提供三個函式,我們暫時看目前最關心的 broadcast(),通過「PhpStorm」IDE,我們也能看出,繼承這個介面的,主要就是平臺 config 配置提供的幾個驅動器:

深入淺出 Laravel Echo

我們開始往下走,看 redis 驅動器:

public function broadcast(array $channels, $event, array $payload = [])
{
    $connection = $this->redis->connection($this->connection);

    $payload = json_encode([
        'event' => $event,
        'data' => $payload,
        'socket' => Arr::pull($payload, 'socket'),
    ]);

    foreach ($this->formatChannels($channels) as $channel) {
        $connection->publish($channel, $payload);
    }
}
複製程式碼

這就簡單的,無非就是建立 redis 連線,然後將資料 (包含 eventdatasocket 構成的陣列),利用 redis publish 出去,等著 laravel-echo-server 監聽接收!

注:redis 有釋出 (publish),就會有訂閱,如:Psubscribe

好了,我們開始研究 laravel-echo-server,看它怎麼訂閱的。

laravel-echo-server

在 Laravel 專案沒有專門提供該 Server,很多專案都是使用 tlaverdure/laravel-echo-server (github.com/tlaverdure/…),其中我們的偶像 Laradock 也整合了該工具。

所以我們就拿 Laradock 配置來說一說。

.
|____Dockerfile
|____laravel-echo-server.json
|____package.json
複製程式碼

主要包含三個檔案,一個 Dockerfile 檔案,用來建立容器;package.json 主要是安裝 tlaverdure/laravel-echo-server 外掛;laravel-echo-server.json 檔案就是與 Laravel 互動的配置檔案。

看看 Dockfile 內容:

FROM node:alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Install app dependencies
COPY package.json /usr/src/app/

RUN apk add --update \n    python \n    python-dev \n    py-pip \n    build-base

RUN npm install

# Bundle app source
COPY laravel-echo-server.json /usr/src/app/laravel-echo-server.json

EXPOSE 3000
CMD [ "npm", "start" ]
複製程式碼

主要是以 node:alpine 為底,將專案部署在路徑 /usr/src/app/,執行命令 npm install 安裝外掛,參考檔案 package.json:

{
  "name": "laravel-echo-server-docker",
  "description": "Docker container for running laravel-echo-server",
  "version": "0.0.1",
  "license": "MIT",
  "dependencies": {
    "laravel-echo-server": "^1.3.9"
  },
  "scripts": {
    "start": "laravel-echo-server start"
  }
}
複製程式碼

然後,在將配置檔案載入進該路徑下,最後執行 npm start,也就是執行命令 laravel-echo-server start,並且放出 3000 埠。

我們通過啟動容器,然後進入容器看看檔案結構:

深入淺出 Laravel Echo

執行 docker-compose up laravel-echo-server 後就可以看到 server 啟動:

深入淺出 Laravel Echo

同樣的,我們也可以下載它的原始碼,來執行達到效果。

tlaverdure/laravel-echo-server

Laravel Echo Node JS Server for Socket.io

下載原始碼:

git clone https://github.com/tlaverdure/laravel-echo-server.git
複製程式碼

進入專案安裝外掛:

npm install
複製程式碼

執行後,直接生成 dist 資料夾:

.
|____api
| |____http-api.js
| |____index.js
|____channels
| |____channel.js
| |____index.js
| |____presence-channel.js
| |____private-channel.js
|____cli
| |____cli.js
| |____index.js
|____database
| |____database-driver.js
| |____database.js
| |____index.js
| |____redis.js
| |____sqlite.js
|____echo-server.js
|____index.js
|____log.js
|____server.js
|____subscribers
| |____http-subscriber.js
| |____index.js
| |____redis-subscriber.js
| |____subscriber.js
複製程式碼

通過提供的 example 可以知道執行的入口在於 EchoServerrun 方法,簡單修改下 options 配置:

var echo = require('../dist/index.js');

var options = {
  "authHost": "http://lrss.learning.test",
  "authEndpoint": "/broadcasting/auth",
  "clients": [],
  "database": "redis",
  "databaseConfig": {
    "redis": {
      "port": "63794",
      "host": "0.0.0.0"
    }
  },
  "devMode": true,
  "host": null,
  "port": "6001",
  "protocol": "http",
  "socketio": {},
  "sslCertPath": "",
  "sslKeyPath": ""
};

echo.run(options);
複製程式碼

測試一下看看,是否和 Laravel 服務連線到位:

深入淺出 Laravel Echo

Laravel-echo-server 列印結果:

深入淺出 Laravel Echo

說明連線上了。

剛才的 dist 資料夾是通過 TypeScript 生成的結果,當然,我們需要通過它的原始碼來解讀:

.
|____api
| |____http-api.ts
| |____index.ts
|____channels
| |____channel.ts
| |____index.ts
| |____presence-channel.ts
| |____private-channel.ts
|____cli
| |____cli.ts
| |____index.ts
|____database
| |____database-driver.ts
| |____database.ts
| |____index.ts
| |____redis.ts
| |____sqlite.ts
|____echo-server.ts
|____index.ts
|____log.ts
|____server.ts
|____subscribers
| |____http-subscriber.ts
| |____index.ts
| |____redis-subscriber.ts
| |____subscriber.ts
複製程式碼

主要包含:介面 (api)、頻道 (channels)、 資料庫 (database)、訂閱 (subscribers) 等,我們會一個個來說的。

我們先看 echo-server.tslisten 函式:

/**
 * Listen for incoming event from subscibers.
 *
 * @return {void}
 */
listen(): Promise<any> {
    return new Promise((resolve, reject) => {
        let http = this.httpSub.subscribe((channel, message) => {
            return this.broadcast(channel, message);
        });

        let redis = this.redisSub.subscribe((channel, message) => {
            return this.broadcast(channel, message);
        });

        Promise.all([http, redis]).then(() => resolve());
    });
}
複製程式碼

我們主要看 this.redisSub.subscribe() 無非就是通過 redis 訂閱,然後再把 channelmessage 廣播出去,好了,我們看看怎麼做到訂閱的,看 redis-subscribersubscribe() 函式:

/**
 * Subscribe to events to broadcast.
 *
 * @return {Promise<any>}
 */
subscribe(callback): Promise<any> {

    return new Promise((resolve, reject) => {
        this._redis.on('pmessage', (subscribed, channel, message) => {
            try {
                message = JSON.parse(message);

                if (this.options.devMode) {
                    Log.info("Channel: " + channel);
                    Log.info("Event: " + message.event);
                }

                callback(channel, message);
            } catch (e) {
                if (this.options.devMode) {
                    Log.info("No JSON message");
                }
            }
        });

        this._redis.psubscribe('*', (err, count) => {
            if (err) {
                reject('Redis could not subscribe.')
            }

            Log.success('Listening for redis events...');

            resolve();
        });
    });
}
複製程式碼

這裡我們就可以看到之前提到的 redis 訂閱函式了:

this._redis.psubscribe('*', (err, count) => {
    if (err) {
        reject('Redis could not subscribe.')
    }

    Log.success('Listening for redis events...');

    resolve();
});
複製程式碼

好了,只要獲取資訊,就可以廣播出去了:

this._redis.on('pmessage', (subscribed, channel, message) => {
    try {
        message = JSON.parse(message);

        if (this.options.devMode) {
            Log.info("Channel: " + channel);
            Log.info("Event: " + message.event);
        }

        // callback(channel, message);
        // return this.broadcast(channel, message);
        if (message.socket && this.find(message.socket)) {
            this.server.io.sockets.connected[message.socket](channel)
            .emit(message.event, channel, message.data);

            return true
        } else {
            this.server.io.to(channel)
            .emit(message.event, channel, message.data);

            return true
        }
    } catch (e) {
        if (this.options.devMode) {
            Log.info("No JSON message");
        }
    }
});
複製程式碼

到此,我們已經知道 Laravel 是怎麼和 Laravel-echo-server 利用 redis 訂閱和釋出訊息的。同時,也知道是用 socket.io 和前端 emit/on 互動的。

下面我們看看前端是怎麼接收訊息的。

laravel-echo

前端需要安裝兩個外掛:laravel-echosocket.io-client,除了做配置外,監聽一個公開的 channel,寫法還是比較簡單的:

window.Echo.channel('public_channel')
.listen('RssPublicEvent', (e) => {
    that.names.push(e.name)
});
複製程式碼

達到的效果就是,只要接收到伺服器發出的在公開頻道 public_channel 的事件 RssPublicEvent,就會把訊息內容顯示出來:

深入淺出 Laravel Echo

我們開始看看這個 Laravel-echo 原始碼了:

深入淺出 Laravel Echo

先看配置資訊:

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001',
    auth:
        {
            headers:
                {
                'authorization': 'Bearer ' + store.getters.token
                }
        }
});
複製程式碼

配置的 broadcaster 是: socket.io,所有用的是:

// echo.ts
constructor(options: any) {
    this.options = options;

    if (typeof Vue === 'function' && Vue.http) {
        this.registerVueRequestInterceptor();
    }

    if (typeof axios === 'function') {
        this.registerAxiosRequestInterceptor();
    }

    if (typeof jQuery === 'function') {
        this.registerjQueryAjaxSetup();
    }

    if (this.options.broadcaster == 'pusher') {
        this.connector = new PusherConnector(this.options);
    } else if (this.options.broadcaster == 'socket.io') {
        this.connector = new SocketIoConnector(this.options);
    } else if (this.options.broadcaster == 'null') {
        this.connector = new NullConnector(this.options);
    }
}
複製程式碼

接著看 channel 函式:

// echo.ts
channel(channel: string): Channel {
    return this.connector.channel(channel);
}

// socketio-connector.ts
channel(name: string): SocketIoChannel {
    if (!this.channels[name]) {
        this.channels[name] = new SocketIoChannel(
            this.socket,
            name,
            this.options
        );
    }

    return this.channels[name];
}
複製程式碼

主要是建立 SocketIoChannel,我們看看怎麼做 listen

// socketio-connector.ts
listen(event: string, callback: Function): SocketIoChannel {
    this.on(this.eventFormatter.format(event), callback);

    return this;
}
複製程式碼

繼續看 on()

on(event: string, callback: Function): void {
    let listener = (channel, data) => {
        if (this.name == channel) {
            callback(data);
        }
    };

    this.socket.on(event, listener);
    this.bind(event, listener);
}
複製程式碼

到這就比較清晰了,只用利用 this.socket.on(event, listener);

注:更多有關 socketio/socket.io-client,可以看官網:github.com/socketio/so…

總結

到目前為止,通過解讀這幾個外掛和原始碼,我們基本跑通了一個 public channel 流程。

這過程主要參考:

  1. 簡單 16步走一遍 Laravel Echo 的使用
  2. 看 Laravel 原始碼瞭解 ServiceProvider 的載入過程

下一步主要看看怎麼解析一個 private channel

看完 public channel 的流程,我們該來說說怎麼跑通 private channel 了。

本文結合之前使用的 JWT 來做身份認證。

但這個流程,我們要先從前端說起。

socker.io

我們先寫一個 demo:

window.Echo.private('App.User.3')
.listen('RssCreatedEvent', (e) => {
    that.names.push(e.name)
});
複製程式碼

先建立 private channel

/**
 * Get a private channel instance by name.
 *
 * @param  {string} name
 * @return {SocketIoChannel}
 */
privateChannel(name: string): SocketIoPrivateChannel {
    if (!this.channels['private-' + name]) {
        this.channels['private-' + name] = new SocketIoPrivateChannel(
            this.socket,
            'private-' + name,
            this.options
        );
    }

    return this.channels['private-' + name];
}
複製程式碼

它與 public channel 的區別在於為 private channelchannel 名前頭增加 private-

接著我們需要為每次請求新增認證資訊 headers

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001',
    auth:
        {
            headers:
                {
                    'authorization': 'Bearer ' + store.getters.token
                }
        }
});
複製程式碼

這裡,我們用 store.getters.token 儲存著 jwt 登入後下發的認證 token

好了,只要創新頁面,就會先往 Laravel-echo-server 傳送一個 subscribe 事件:

/**
 * Subscribe to a Socket.io channel.
 *
 * @return {object}
 */
subscribe(): any {
    this.socket.emit('subscribe', {
        channel: this.name,
        auth: this.options.auth || {}
    });
}
複製程式碼

我們來看看 Laravel-echo-server 怎麼接收到這個事件,並把 auth,也就是 jwt token 發到後臺的?在研究怎麼發之前,我們還是先把 Laravel 的 private channel Event 建好。

RssCreatedEvent

我們建立 Laravel PrivateChannel

// RssCreatedEvent
<?php

namespace AppEvents;

use AppUser;
use CarbonCarbon;
use IlluminateBroadcastingChannel;
use IlluminateQueueSerializesModels;
use IlluminateBroadcastingPrivateChannel;
use IlluminateBroadcastingPresenceChannel;
use IlluminateFoundationEventsDispatchable;
use IlluminateBroadcastingInteractsWithSockets;
use IlluminateContractsBroadcastingShouldBroadcast;

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

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return IlluminateBroadcastingChannel|array
     */
    public function broadcastOn()
    {
        // 14. 建立頻道
        info('broadcastOn');
        return new PrivateChannel('App.User.3');
    }

    /**
     * 指定廣播資料。
     *
     * @return array
     */
    public function broadcastWith()
    {
        // 返回當前時間
        return ['name' => 'private_channel_'.Carbon::now()->toDateTimeString()];
    }
}

// routes/console.php
Artisan::command('echo', function () {
    event(new RssCreatedEvent());
})->describe('echo demo');
複製程式碼

與 jwt 結合

修改 BroadcastServiceprovider 的認證路由為 api:

// 修改前
// Broadcast::routes();

// 修改後
Broadcast::routes(["middleware" => "auth:api"]);

複製程式碼

當然,我們的認證方式也已經改成 JWT 方式了:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'api',
        'passwords' => 'users',
    ],

...

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],
複製程式碼

最後,別忘了把 BroadcastServiceprovider 加入 app.config 中。

深入淺出 Laravel Echo

注:更多有關 JWT 歡迎檢視之前的文章

  1. 《學習 Lumen 使用者認證 (一)》
  2. 學習 Lumen 使用者認證 (二) —— 使用 jwt-auth 外掛

Laravel-echo-server

有了前端和後臺的各自 private channel,那必然需要用 Laravel-echo-server 來銜接。

先說回怎麼接收前端發過來的 subscribe 事件和 token

首先看 echo-server 初始化:

init(io: any): Promise<any> {
    return new Promise((resolve, reject) => {
        this.channel = new Channel(io, this.options);
        this.redisSub = new RedisSubscriber(this.options);
        this.httpSub = new HttpSubscriber(this.server.express, this.options);
        this.httpApi = new HttpApi(io, this.channel, this.server.express, this.options.apiOriginAllow);
        this.httpApi.init();

        this.onConnect();
        this.listen().then(() => resolve(), err => Log.error(err));
    });
}
複製程式碼

其中,this.onConnect()

onConnect(): void {
    this.server.io.on('connection', socket => {
        this.onSubscribe(socket);
        this.onUnsubscribe(socket);
        this.onDisconnecting(socket);
        this.onClientEvent(socket);
    });
}
複製程式碼

主要註冊了四個事件,第一個就是我們需要關注的:

onSubscribe(socket: any): void {
    socket.on('subscribe', data => {
        this.channel.join(socket, data);
    });
}
複製程式碼

這就和前端呼應上了,接著看 join 函式:

join(socket, data): void {
    if (data.channel) {
        if (this.isPrivate(data.channel)) {
            this.joinPrivate(socket, data);
        } else {
            socket.join(data.channel);
            this.onJoin(socket, data.channel);
        }
    }
}
複製程式碼

isPrivate() 函式:

/**
 * Channels and patters for private channels.
 *
 * @type {array}
 */
protected _privateChannels: string[] = ['private-*', 'presence-*'];
    
    
/**
 * Check if the incoming socket connection is a private channel.
 *
 * @param  {string} channel
 * @return {boolean}
 */
isPrivate(channel: string): boolean {
    let isPrivate = false;

    this._privateChannels.forEach(privateChannel => {
        let regex = new RegExp(privateChannel.replace('*', '.*'));
        if (regex.test(channel)) isPrivate = true;
    });

    return isPrivate;
}
複製程式碼

這也是印證了,為什麼 private channel 要以 private- 開頭了。接著看程式碼:

/**
 * Join private channel, emit data to presence channels.
 *
 * @param  {object} socket
 * @param  {object} data
 * @return {void}
 */
joinPrivate(socket: any, data: any): void {
    this.private.authenticate(socket, data).then(res => {
        socket.join(data.channel);

        if (this.isPresence(data.channel)) {
            var member = res.channel_data;
            try {
                member = JSON.parse(res.channel_data);
            } catch (e) { }

            this.presence.join(socket, data.channel, member);
        }

        this.onJoin(socket, data.channel);
    }, error => {
        if (this.options.devMode) {
            Log.error(error.reason);
        }

        this.io.sockets.to(socket.id)
            .emit('subscription_error', data.channel, error.status);
    });
}
複製程式碼

就因為是 private channel,所以需要走認證流程:

/**
 * Send authentication request to application server.
 *
 * @param  {any} socket
 * @param  {any} data
 * @return {Promise<any>}
 */
authenticate(socket: any, data: any): Promise<any> {
    let options = {
        url: this.authHost(socket) + this.options.authEndpoint,
        form: { channel_name: data.channel },
        headers: (data.auth && data.auth.headers) ? data.auth.headers : {},
        rejectUnauthorized: false
    };

    return this.serverRequest(socket, options);
}

/**
 * Send a request to the server.
 *
 * @param  {any} socket
 * @param  {any} options
 * @return {Promise<any>}
 */
protected serverRequest(socket: any, options: any): Promise<any> {
    return new Promise<any>((resolve, reject) => {
        options.headers = this.prepareHeaders(socket, options);
        let body;

        this.request.post(options, (error, response, body, next) => {
            if (error) {
                if (this.options.devMode) {
                    Log.error(`[${new Date().toLocaleTimeString()}] - Error authenticating ${socket.id} for ${options.form.channel_name}`);
                    Log.error(error);
                }

                reject({ reason: 'Error sending authentication request.', status: 0 });
            } else if (response.statusCode !== 200) {
                if (this.options.devMode) {
                    Log.warning(`[${new Date().toLocaleTimeString()}] - ${socket.id} could not be authenticated to ${options.form.channel_name}`);
                    Log.error(response.body);
                }

                reject({ reason: 'Client can not be authenticated, got HTTP status ' + response.statusCode, status: response.statusCode });
            } else {
                if (this.options.devMode) {
                    Log.info(`[${new Date().toLocaleTimeString()}] - ${socket.id} authenticated for: ${options.form.channel_name}`);
                }

                try {
                    body = JSON.parse(response.body);
                } catch (e) {
                    body = response.body
                }

                resolve(body);
            }
        });
    });
}
複製程式碼

到此,相信你就看的出來了,會把前端發過來的 auth.headers 加入發往後臺的請求中。

測試

好了,我們測試下,先重新整理頁面,加入 private channel 中,

深入淺出 Laravel Echo

然後在後臺,發一個事件出來,看前端是不是可以接收

深入淺出 Laravel Echo

總結

到此,基本就解釋了怎麼建立 private channel,然後利用 jwt 認證身份,最後將 Event 內容下發出去。

接下來我們就可以看看怎麼建 chat room,然更多客戶端加入進來聊天。

*未完待續