react-websocket/src/WebSocketMiddleware.php

81 lines
2.6 KiB
PHP

<?php
namespace NoccyLabs\React\WebSocket;
use Evenement\EventEmitterInterface;
use Evenement\EventEmitterTrait;
use NoccyLabs\React\WebSocket\Group\GroupManager;
use Psr\Http\Message\ServerRequestInterface;
use React\EventLoop\Loop;
use React\Http\Message\Response;
use React\Stream\CompositeStream;
use React\Stream\ThroughStream;
use SplObjectStorage;
class WebSocketMiddleware implements EventEmitterInterface
{
const EVENT_CONNECTION = 'connection';
const MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const VERSION = 13;
use EventEmitterTrait;
private GroupManager $groupManager;
private SplObjectStorage $websockets;
public function __construct(?GroupManager $groupManager = null)
{
$this->groupManager = $groupManager ?? new GroupManager();
$this->websockets = new SplObjectStorage();
}
public function addRoute(string $path, callable $handler, array $allowedOrigins=[]): void
{
// TODO implement or remove
}
public function __invoke(ServerRequestInterface $request, callable $next)
{
if ($request->getHeaderLine('upgrade') !== 'websocket') {
return $next($request);
}
$handshakeKey = trim($request->getHeaderLine('sec-websocket-key'));
$handshakeResponse = base64_encode(sha1($handshakeKey.self::MAGIC,true));
// Create the streams we need to pass the websocket data back and forth.
// This is returned with the response, and passed to the WebSocketConnection.
$inStream = new ThroughStream();
$outStream = new ThroughStream();
$stream = new CompositeStream($outStream, $inStream);
$websocket = new WebSocketConnection($request, $inStream, $outStream, $this->groupManager);
$this->websockets->attach($websocket);
$websocket->on('close', function () use ($websocket) {
$this->websockets->detach($websocket);
});
// FIXME how to test with futureTick?
//Loop::futureTick(function () use ($websocket) {
$this->emit(self::EVENT_CONNECTION, [ $websocket ]);
//});
// TODO would it be possible for the 'connection' event to set additional response headers to be sent here?
// For example, to send back Sec-WebSocket-Protocol header.
return new Response(
Response::STATUS_SWITCHING_PROTOCOLS,
array(
'Upgrade' => 'websocket',
'Connection' => 'upgrade',
'Sec-WebSocket-Accept' => $handshakeResponse,
'Sec-WebSocket-Version' => self::VERSION
),
$stream
);
}
}