Custom handshake

In a common business scenario, we usually need to verify the identity of the client, so it can be done by customizing the WebSocket handshake rule.

Create an App/WebSocket/WebSocketEvent.php file and write the following

namespace App\WebSocket;

/**
 * Class WebSocketEvent
 *
 * This class is some non-mandatory custom event handling in WebSocket
 *
 * @package App\WebSocket
 */
class WebSocketEvent
{
    /**
     * Handshake event
     *
     * @param \swoole_http_request  $request
     * @param \swoole_http_response $response
     * @return bool
     */
    public function onHandShake(\swoole_http_request $request, \swoole_http_response $response)
    {
        /** Here the custom handshake rule returns false when the handshake is aborted */
        if (!$this->customHandShake($request, $response)) {
            $response->end();
            return false;
        }

        /** Here is the WebSocket handshake verification process in the RFC specification. It must be executed. Otherwise, the handshake cannot be performed correctly. */
        if ($this->secWebsocketAccept($request, $response)) {
            $response->end();
            return true;
        }

        $response->end();
        return false;
    }

    /**
     * Custom handshake event
     *
     * @param \swoole_http_request  $request
     * @param \swoole_http_response $response
     * @return bool
     */
    protected function customHandShake(\swoole_http_request $request, \swoole_http_response $response): bool
    {
        /**
         * Here you can get the corresponding data through http request
         * After custom verification
         * (Note) JavaScript does not support custom handshake request headers in the browser. You can only choose other methods such as get parameters.
         */
        $headers = $request->header;
        $cookie = $request->cookie;

        // if (If I don't meet some of my custom requirements, return false, the handshake fails.) {
        //    return false;
        // }
        return true;
    }

    /**
     * WebSocket handshake verification process in RFC specification
     * The following must be mandatory
     *
     * @param \swoole_http_request  $request
     * @param \swoole_http_response $response
     * @return bool
     */
    protected function secWebsocketAccept(\swoole_http_request $request, \swoole_http_response $response): bool
    {
        // The verification process agreed in the ws rfc specification
        if (!isset($request->header['sec-websocket-key'])) {
            // Requires Sec-WebSocket-Key if no handshake is refused
            var_dump('shake fai1 3');
            return false;
        }
        if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $request->header['sec-websocket-key'])
            || 16 !== strlen(base64_decode($request->header['sec-websocket-key']))
        ) {
            //Do not accept handshake
            var_dump('shake fai1 4');
            return false;
        }

        $key = base64_encode(sha1($request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        $headers = array(
            'Upgrade'               => 'websocket',
            'Connection'            => 'Upgrade',
            'Sec-WebSocket-Accept'  => $key,
            'Sec-WebSocket-Version' => '13',
            'KeepAlive'             => 'off',
        );

        if (isset($request->header['sec-websocket-protocol'])) {
            $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
        }

        // Send verified header
        foreach ($headers as $key => $val) {
            $response->header($key, $val);
        }

        // Accepting the handshake also requires a 101 status code to switch status
        $response->status(101);
        var_dump('shake success at fd :' . $request->fd);
        return true;
    }
}

Add the following code under the mainServerCreate method of the EasySwooleEvent.php file in the root directory

//Note: Introduce the following namespace in this file
use EasySwoole\Socket\Dispatcher;
use App\WebSocket\WebSocketParser;
use App\WebSocket\WebSocketEvent;

public static function mainServerCreate(EventRegister $register): void
{
    /**
     * **************** Websocket controller **********************
     */
    // Create a Dispatcher configuration
    $conf = new \EasySwoole\Socket\Config();
    // Set Dispatcher to WebSocket mode
    $conf->setType(\EasySwoole\Socket\Config::WEB_SOCKET);
    // Set the parser object
    $conf->setParser(new WebSocketParser());
    // Create a Dispatcher object and inject the config object
    $dispatch = new Dispatcher($conf);
    
    // Register the relevant event for the server In the WebSocket mode, the on message event must be registered and passed to the Dispatcher object for processing.
    $register->set(EventRegister::onMessage, function (\swoole_websocket_server $server, \swoole_websocket_frame $frame) use ($dispatch) {
        $dispatch->dispatch($server, $frame->data, $frame);
    });
    
    // Custom handshake event
    $websocketEvent = new WebSocketEvent();
    $register->set(EventRegister::onHandShake, function (\swoole_http_request $request, \swoole_http_response $response) use ($websocketEvent) {
        $websocketEvent->onHandShake($request, $response);
    });
}

Custom Close Event

In a common business scenario, we usually need to set a callback event when the user disconnects or the server actively disconnects.

Create the App/WebSocket/WebSocketEvent.php file and add the following

/**
 * Closing event
 *
 * @param \swoole_server $server
 * @param int            $fd
 * @param int            $reactorId
 */
public function onClose(\swoole_server $server, int $fd, int $reactorId)
{
    /** @var array $info */
    $info = $server->getClientInfo($fd);
    /**
     * Determine if this fd is a valid websocket connection
     * See https://wiki.swoole.com/wiki/page/490.html
     */
    if ($info && $info['websocket_status'] === WEBSOCKET_STATUS_FRAME) {
        /**
         * Determine if the connection is server actively closed
         * see https://wiki.swoole.com/wiki/page/p-event/onClose.html
         */
        if ($reactorId < 0) {
            echo "server close \n";
        }
    }
}

Add the following code under the mainServerCreate method of the EasySwooleEvent.php file in the root directory

    /**
     * **************** Websocket controller **********************
     */
    //Create a Dispatcher configuration
    $conf = new \EasySwoole\Socket\Config();
    // Set Dispatcher to WebSocket mode
    $conf->setType(\EasySwoole\Socket\Config::WEB_SOCKET);
    // Set the parser object
    $conf->setParser(new WebSocketParser());
    // Create a Dispatcher object and inject the config object
    $dispatch = new Dispatcher($conf);

    // Register the relevant event for the server In the WebSocket mode, the on message event must be registered and passed to the Dispatcher object for processing.
    $register->set(EventRegister::onMessage, function (\swoole_websocket_server $server, \swoole_websocket_frame $frame) use ($dispatch) {
        $dispatch->dispatch($server, $frame->data, $frame);
    });

    //Custom handshake event
    $websocketEvent = new WebSocketEvent();
    $register->set(EventRegister::onHandShake, function (\swoole_http_request $request, \swoole_http_response $response) use ($websocketEvent) {
        $websocketEvent->onHandShake($request, $response);
    });

    //Custom close event
    $register->set(EventRegister::onClose, function (\swoole_server $server, int $fd, int $reactorId) use ($websocketEvent) {
        $websocketEvent->onClose($server, $fd, $reactorId);
    });