/** * {@inheritdoc} */ public function start(HttpRequest $request, LoggerInterface $logger = null) { $this->logger = $logger; if ($this->logger) { $this->address = $request->getClientAddress(); $this->logger->debug('Enabled SSE for {address} using HTTP/{version}', ['address' => $this->address, 'version' => $request->getProtocolVersion()]); } }
/** * Perform a direct upgrade of the connection to HTTP/2. * * @param HttpDriverContext $context HTTP context related to the HTTP endpoint. * @param SocketStream $socket The underlying socket transport. * @param HttpRequest $request The HTTP request that caused the connection upgrade. * @param callable $action Server action to be performed for each incoming HTTP request. */ protected function upgradeConnectionDirect(HttpDriverContext $context, SocketStream $socket, HttpRequest $request, callable $action) : \Generator { $preface = (yield $socket->readBuffer(\strlen(Connection::PREFACE_BODY), true)); if ($preface !== Connection::PREFACE_BODY) { throw new StatusException(Http::BAD_REQUEST, 'Invalid HTTP/2 connection preface body'); } if ($this->logger) { $this->logger->info('{ip} "{method} {target} HTTP/{protocol}" {status} {size}', ['ip' => $request->getClientAddress(), 'method' => $request->getMethod(), 'target' => $request->getRequestTarget(), 'protocol' => $request->getProtocolVersion(), 'status' => Http::SWITCHING_PROTOCOLS, 'size' => '-']); } $conn = new Connection($socket, new HPack($this->hpackContext), $this->logger); (yield $conn->performServerHandshake(null, true)); if ($this->logger) { $this->logger->info('HTTP/{protocol} connection from {peer} upgraded to HTTP/2', ['protocol' => $request->getProtocolVersion(), 'peer' => $socket->getRemoteAddress()]); } $remotePeer = $socket->getRemoteAddress(); try { while (null !== ($received = (yield $conn->nextRequest($context)))) { new Coroutine($this->processRequest($conn, $action, ...$received), true); } } finally { try { $conn->shutdown(); } finally { if ($this->logger) { $this->logger->debug('Closed HTTP/2 connection to {peer}', ['peer' => $remotePeer]); } } } }
protected function sendDeferredResponse(SocketStream $socket, HttpRequest $request, HttpResponse $response, bool $nobody, bool $close) : \Generator { if ($this->logger) { $this->logger->info('{ip} "{method} {target} HTTP/{protocol}" {status} {size}', ['ip' => $request->getClientAddress(), 'method' => $request->getMethod(), 'target' => $request->getRequestTarget(), 'protocol' => $request->getProtocolVersion(), 'status' => $response->getStatusCode(), 'size' => '-']); } if (!$nobody) { $close = true; } (yield $socket->write($this->serializeHeaders($response, $close, null, $nobody, true) . "\r\n")); (yield $socket->flush()); if ($nobody) { $response->getBody()->close(false); return !$close; } $watcher = null; $task = new Coroutine(function () use(&$watcher, $socket, $request, $response) { $body = $response->getBody(); $body->start($request, $this->logger); $bodyStream = (yield $body->getReadableStream()); $e = null; try { while (null !== ($chunk = (yield $bodyStream->read()))) { (yield $socket->write($chunk)); } } catch (StreamClosedException $e) { // Client disconnected from server. } finally { try { $bodyStream->close(); } finally { $body->close($e ? true : false); } $watcher->cancel(new \RuntimeException()); } return false; }, true); $watcher = new Coroutine(function () use($socket, $task) { $socket = $socket->getSocket(); while (\is_resource($socket) && !\feof($socket)) { (yield new AwaitRead($socket)); } $task->cancel(new StreamClosedException('Client disconnected')); }); return (yield $task); }