timostamm / websocket-server
一个简单的非阻塞服务器,专门用于WebSocket。
v3.1.1
2023-06-19 11:48 UTC
Requires
- php: ^7.2 || ^8.0
- guzzlehttp/psr7: ^1.9 || ^2.5
- ratchet/rfc6455: ^0.3
- react/socket: ^1.0
Requires (Dev)
- phpunit/phpunit: ^8.5.23 || ^9
README
一个简单的非阻塞服务器,专门用于WebSocket。
- 升级HTTP请求
- 将HTTP请求路由到简单的WebSocket控制器
- 可以过滤HTTP请求
- 通过Autobahn WebSocket 测试套件
- 不实现压缩
- 与apache >= 2.4配合良好
- 使用以下命令安装:
composer require timostamm/websocket-server - 最小依赖(react/socket,ratchet/rfc6455,guzzlehttp/psr7)
- 通过信号(或手动)进行优雅关闭
WebSocket协议实现的功劳归功于ratchet/rfc6455。
示例
$loop = Factory::create(); // use a react event loop // start server $server = new WebsocketServer($loop, [ 'uri' => '127.0.0.1:23080' ]); // add a controller $server->route([ 'match' => '/example/*', 'controller' => new class() implements ControllerInterface { function onOpen(WebSocket $connection): void { print $connection . ' connected. Sending a "Hello".' . PHP_EOL; $connection->send('Hello'); } function onMessage(WebSocket $from, string $payload, bool $binary): void { print $from . ' sent: ' . $payload . PHP_EOL; } function onClose(WebSocket $connection, ?Throwable $error): void { print $connection . ' disconnected.' . PHP_EOL; } } ]); // This error handler will be called when an exception was thrown // by a filter, a controller method or the underlying tcp server. $server->on('error', function (Throwable $error) { print 'Server error: ' . $error->getMessage() . PHP_EOL; }); $loop->run(); // the react event loop processes socket connections
路由
此路由将匹配以/example/开头的路径。
$server->route([ 'match' => '/example/*', 'controller' => $controller ]);
占位符使用fnmatch()实现。
此路由将匹配任何路径
$server->route([ 'controller' => $controller ]);
如果客户端没有指定以下子协议之一,则此路由将拒绝WebSocket握手
$server->route([ 'protocols' => ['soap'], 'controller' => $controller ]);
请求过滤器
此过滤器以HTTP 403响应
$server->filter('example/403', function () { throw ResponseException::create(403); });
此过滤器修改请求
$server->filter('example/add-attribute', function (ServerRequestInterface $request) { return $request->withAttribute('X-filter', 'passed'); });
此过滤器只允许指定的源
$server->filter('example/origin', new OriginFilter(['example.com']));
您可以提供自己的RequestMatcherInterface和RequestFilterInterface实现。过滤器也可以通过route()添加
$server->route([ 'match' => '/example/*', 'filter' => function(ServerRequestInterface $request){ if ($request->getRequestTarget() === '/example/forbidden') { throw ResponseException::create(403); } } 'controller' => ... ]);
身份验证
此库不提供会话集成,但提供支持Bearer令牌身份验证。
通过使用您的令牌验证代码扩展AbstractTokenAuthenticator并返回一个用户对象。用户对象将在请求属性“user”中可用。如果没有令牌,则用户属性将为空。
使用AuthorizationFilter检查用户是否存在。提供$checkUser函数以检查用户是否已授权。
请参阅examples/token-auth.php
Apache配置
将以下内容添加到.htaccess文件中,以将带有Upgrade: websocket头部的所有请求代理到WebSocket服务器。
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule ^(.*)$ ws://127.0.0.1:23080/$1 [P,L]
</IfModule>
JavaScript
var ws = new WebSocket("ws://:23080/hello/foo"); ws.onmessage = function (event) { console.log("message", event.data); };
更多控制器功能
您可以实现以下接口之一或多个,以获取对循环、连接到此控制器的客户端等的访问。
class MyCtrl implements ControllerInterface, LoopAwareInterface ConnectionListAwareInterface, OnShutDownInterface, OnLastCloseInterface, OnFirstOpenInterface { function setLoop(\React\EventLoop\LoopInterface $loop, callable $exceptionHandler): void { print 'Got loop.' . PHP_EOL; } function setConnections(\SplObjectStorage $webSockets): void { print 'Got connection list.' . PHP_EOL; } function onShutDown(): PromiseInterface { // Will be called when the server is asked to shutdown. // Use this hook to finish important tasks, then resolve the promise. } function onLastClose(WebSocket $socket): void { print 'Last connection closed.' . PHP_EOL; } function onFirstOpen(WebSocket $socket): void { print 'First connection opened.' . PHP_EOL; } function onOpen(WebSocket $socket): void { print $socket . ' connected. Sending a "Hello".' . PHP_EOL; $socket->send('Hello'); } function onMessage(WebSocket $from, string $payload, bool $binary): void { print $from . ' received: ' . $payload . PHP_EOL; } function onClose(WebSocket $socket): void { print $socket . ' disconnected.' . PHP_EOL; } function onError(WebSocket $socket, \Throwable $error): void { print $socket . ' error: ' . $error->getMessage() . PHP_EOL; } }