Active connections */ private SplObjectStorage $connections; public function __construct() { $this->connections = new SplObjectStorage(); } public function __invoke(ServerRequestInterface $request, ?callable $next=null) { // expect upgrade h2 for secure connections, h2c for plaintext $requestSecure = $request->getHeaderLine("x-forwarded-proto") ?: "http"; $requestUpgrade = $request->getHeaderLine("upgrade"); $connectionFlags = array_map( fn($v) => strtolower(trim($v)), explode(",", $request->getHeaderLine("connection")) ); // Pass everything we aren't interested in on to the next handler if (!in_array('upgrade', $connectionFlags) || !in_array($requestUpgrade, ['h2', 'h2c'])) { if (is_callable($next)) return $next($request); return Response::plaintext("Unsupported protocol")->withStatus(Response::STATUS_BAD_REQUEST); } // handle HTTP/2 upgrade from HTTP/1.1 $http2SettingsData = $request->getHeaderLine("http2-settings"); if ($http2SettingsData) { $http2Settings = $this->parseSettingsFromBase64String($http2SettingsData); } else { // TODO handle HTTP/2 with prior knowledge return Response::plaintext("Expected HTTP2-Settings header")->withStatus(Response::STATUS_BAD_REQUEST); } $responseInputStream = new ThroughStream(); $responseOutputStream = new ThroughStream(); $responseStream = new CompositeStream($responseOutputStream, $responseInputStream); $serverStream = new CompositeStream($responseInputStream, $responseOutputStream); $connection = new Http2Connection($serverStream, $request, $http2Settings??null); $headers = [ 'Connection' => 'Upgrade', 'Upgrade' => $requestUpgrade ]; return (new Response(Response::STATUS_SWITCHING_PROTOCOLS, $headers, $responseStream)); } /** * Parse the settings frame present in the HTTP/1.1 upgrade request. * * @param string $settings * @return SettingsFrame */ private function parseSettingsFromBase64String(string $settings): SettingsFrame { $decoded = base64_decode($settings); $frame = new SettingsFrame(); $frame->fromBinary($decoded); return $frame; } /** * Prepare a connection for the HTTP/2 session. * * @param ServerRequestInterface $request * @return Http2Connection */ private function setupConnection(ServerRequestInterface $request): Http2Connection { $stream = new ThroughStream(); $connection = new Http2Connection($stream); $this->connections->attach($connection); $connection->on('close', function () use ($connection) { $this->connections->detach($connection); }); return $connection; } }