自定义握手

在常见业务场景中,我们通常需要验证客户端的身份,所以可以通过自定义WebSocket握手规则来完成。

创建App/WebSocket/WebSocketEvent.php文件,写入以下内容

  1. namespace App\WebSocket;
  2. /**
  3. * Class WebSocketEvent
  4. *
  5. * 此类是 WebSocket 中一些非强制的自定义事件处理
  6. *
  7. * @package App\WebSocket
  8. */
  9. class WebSocketEvent
  10. {
  11. /**
  12. * 握手事件
  13. *
  14. * @param \swoole_http_request $request
  15. * @param \swoole_http_response $response
  16. * @return bool
  17. */
  18. public function onHandShake(\swoole_http_request $request, \swoole_http_response $response)
  19. {
  20. /** 此处自定义握手规则 返回 false 时中止握手 */
  21. if (!$this->customHandShake($request, $response)) {
  22. $response->end();
  23. return false;
  24. }
  25. /** 此处是 RFC规范中的WebSocket握手验证过程 必须执行 否则无法正确握手 */
  26. if ($this->secWebsocketAccept($request, $response)) {
  27. $response->end();
  28. return true;
  29. }
  30. $response->end();
  31. return false;
  32. }
  33. /**
  34. * 自定义握手事件
  35. *
  36. * @param \swoole_http_request $request
  37. * @param \swoole_http_response $response
  38. * @return bool
  39. */
  40. protected function customHandShake(\swoole_http_request $request, \swoole_http_response $response): bool
  41. {
  42. /**
  43. * 这里可以通过 http request 获取到相应的数据
  44. * 进行自定义验证后即可
  45. * (注) 浏览器中 JavaScript 并不支持自定义握手请求头 只能选择别的方式 如get参数
  46. */
  47. $headers = $request->header;
  48. $cookie = $request->cookie;
  49. // if (如果不满足我某些自定义的需求条件,返回false,握手失败) {
  50. // return false;
  51. // }
  52. return true;
  53. }
  54. /**
  55. * RFC规范中的WebSocket握手验证过程
  56. * 以下内容必须强制使用
  57. *
  58. * @param \swoole_http_request $request
  59. * @param \swoole_http_response $response
  60. * @return bool
  61. */
  62. protected function secWebsocketAccept(\swoole_http_request $request, \swoole_http_response $response): bool
  63. {
  64. // ws rfc 规范中约定的验证过程
  65. if (!isset($request->header['sec-websocket-key'])) {
  66. // 需要 Sec-WebSocket-Key 如果没有拒绝握手
  67. var_dump('shake fai1 3');
  68. return false;
  69. }
  70. if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $request->header['sec-websocket-key'])
  71. || 16 !== strlen(base64_decode($request->header['sec-websocket-key']))
  72. ) {
  73. //不接受握手
  74. var_dump('shake fai1 4');
  75. return false;
  76. }
  77. $key = base64_encode(sha1($request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
  78. $headers = array(
  79. 'Upgrade' => 'websocket',
  80. 'Connection' => 'Upgrade',
  81. 'Sec-WebSocket-Accept' => $key,
  82. 'Sec-WebSocket-Version' => '13',
  83. 'KeepAlive' => 'off',
  84. );
  85. if (isset($request->header['sec-websocket-protocol'])) {
  86. $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
  87. }
  88. // 发送验证后的header
  89. foreach ($headers as $key => $val) {
  90. $response->header($key, $val);
  91. }
  92. // 接受握手 还需要101状态码以切换状态
  93. $response->status(101);
  94. var_dump('shake success at fd :' . $request->fd);
  95. return true;
  96. }
  97. }

在根目录下EasySwooleEvent.php文件mainServerCreate方法下加入以下代码

  1. //注意:在此文件引入以下命名空间
  2. use EasySwoole\Socket\Dispatcher;
  3. use App\WebSocket\WebSocketParser;
  4. use App\WebSocket\WebSocketEvent;
  5. public static function mainServerCreate(EventRegister $register): void
  6. {
  7. /**
  8. * **************** websocket控制器 **********************
  9. */
  10. // 创建一个 Dispatcher 配置
  11. $conf = new \EasySwoole\Socket\Config();
  12. // 设置 Dispatcher 为 WebSocket 模式
  13. $conf->setType(\EasySwoole\Socket\Config::WEB_SOCKET);
  14. // 设置解析器对象
  15. $conf->setParser(new WebSocketParser());
  16. // 创建 Dispatcher 对象 并注入 config 对象
  17. $dispatch = new Dispatcher($conf);
  18. // 给server 注册相关事件 在 WebSocket 模式下 on message 事件必须注册 并且交给 Dispatcher 对象处理
  19. $register->set(EventRegister::onMessage, function (\swoole_websocket_server $server, \swoole_websocket_frame $frame) use ($dispatch) {
  20. $dispatch->dispatch($server, $frame->data, $frame);
  21. });
  22. //自定义握手事件
  23. $websocketEvent = new WebSocketEvent();
  24. $register->set(EventRegister::onHandShake, function (\swoole_http_request $request, \swoole_http_response $response) use ($websocketEvent) {
  25. $websocketEvent->onHandShake($request, $response);
  26. });
  27. }

自定义关闭事件

在常见业务场景中,我们通常需要在用户断开或者服务器主动断开连接时设置回调事件。

创建App/WebSocket/WebSocketEvent.php文件,增加以下内容

  1. /**
  2. * 关闭事件
  3. *
  4. * @param \swoole_server $server
  5. * @param int $fd
  6. * @param int $reactorId
  7. */
  8. public function onClose(\swoole_server $server, int $fd, int $reactorId)
  9. {
  10. /** @var array $info */
  11. $info = $server->getClientInfo($fd);
  12. /**
  13. * 判断此fd 是否是一个有效的 websocket 连接
  14. * 参见 https://wiki.swoole.com/wiki/page/490.html
  15. */
  16. if ($info && $info['websocket_status'] === WEBSOCKET_STATUS_FRAME) {
  17. /**
  18. * 判断连接是否是 server 主动关闭
  19. * 参见 https://wiki.swoole.com/wiki/page/p-event/onClose.html
  20. */
  21. if ($reactorId < 0) {
  22. echo "server close \n";
  23. }
  24. }
  25. }

在根目录下EasySwooleEvent.php文件mainServerCreate方法下加入以下代码

  1. /**
  2. * **************** websocket控制器 **********************
  3. */
  4. // 创建一个 Dispatcher 配置
  5. $conf = new \EasySwoole\Socket\Config();
  6. // 设置 Dispatcher 为 WebSocket 模式
  7. $conf->setType(\EasySwoole\Socket\Config::WEB_SOCKET);
  8. // 设置解析器对象
  9. $conf->setParser(new WebSocketParser());
  10. // 创建 Dispatcher 对象 并注入 config 对象
  11. $dispatch = new Dispatcher($conf);
  12. // 给server 注册相关事件 在 WebSocket 模式下 on message 事件必须注册 并且交给 Dispatcher 对象处理
  13. $register->set(EventRegister::onMessage, function (\swoole_websocket_server $server, \swoole_websocket_frame $frame) use ($dispatch) {
  14. $dispatch->dispatch($server, $frame->data, $frame);
  15. });
  16. //自定义握手事件
  17. $websocketEvent = new WebSocketEvent();
  18. $register->set(EventRegister::onHandShake, function (\swoole_http_request $request, \swoole_http_response $response) use ($websocketEvent) {
  19. $websocketEvent->onHandShake($request, $response);
  20. });
  21. //自定义关闭事件
  22. $register->set(EventRegister::onClose, function (\swoole_server $server, int $fd, int $reactorId) use ($websocketEvent) {
  23. $websocketEvent->onClose($server, $fd, $reactorId);
  24. });