/** * Coroutine that handles incoming WebSocket frames. */ protected function processIncomingFrames() : \Generator { $e = null; try { while (true) { $frame = (yield from $this->readNextFrame()); if ($frame->isControlFrame()) { if (!(yield from $this->handleControlFrame($frame))) { break; } } else { switch ($frame->opcode) { case Frame::TEXT: yield from $this->handleTextFrame($frame); break; case Frame::BINARY: yield from $this->handleBinaryFrame($frame); break; case Frame::CONTINUATION: yield from $this->handleContinuationFrame($frame); break; } } } } catch (\Throwable $e) { $this->messages->close($e); } finally { $this->messages->close(); try { foreach ($this->pings as $defer) { $defer->fail($e ?? new \RuntimeException('WebSocket connection closed')); } } finally { $this->pings = []; } try { if ($this->socket->isAlive()) { $reason = $e === null || $e->getCode() === 0 ? Frame::NORMAL_CLOSURE : $e->getCode(); (yield $this->writer->sendFrame(new Frame(Frame::CONNECTION_CLOSE, \pack('n', $reason)))); } } finally { if ($this->logger) { $this->logger->debug('WebSocket connection to {peer} closed', ['peer' => $this->socket->getRemoteAddress()]); } $this->socket->close(); } } }
private static function handshake(SocketStream $socket) : \Generator { $transmitter = new SocketTransmitter($socket); list($type, $data) = (yield from $transmitter->receive()); if ($type !== SocketTransmitter::TYPE_HANDSHAKE || !isset($data['id'])) { return $socket->close(); } $id = $data['id']; if (empty(self::$handshakes[$id])) { return $socket->close(); } list($worker, $defer) = self::$handshakes[$id]; unset(self::$handshakes[$id]); if (empty(self::$handshakes) && self::$serverAwait !== null) { self::$serverAwait->cancel(new PoolShutdownException('Handshakes done')); } $worker->connect($socket, $transmitter); self::$sharedWorkers[$id] = $worker; $defer->resolve($worker); }
/** * Coroutine that processes inbound FCGI records. */ protected function handleIncomingRecords() : \Generator { static $header = 'Cversion/Ctype/nid/nlen/Cpad/x'; try { $peer = $this->socket->getRemoteAddress(); if ($this->logger) { $this->logger->debug('Accepted new FCGI connection from {peer}', ['peer' => $peer]); } while (true) { list($version, $type, $id, $len, $pad) = \array_values(\unpack($header, (yield $this->socket->readBuffer(8, true)))); $payload = $len > 0 ? (yield $this->socket->readBuffer($len, true)) : ''; if ($pad > 0) { (yield $this->socket->readBuffer($pad, true)); } $record = new Record($version, $type, $id, $payload); switch ($record->type) { case Record::FCGI_BEGIN_REQUEST: list($role, $flags) = \array_values(\unpack('nrole/Cflags/x5', $record->data)); if ($role != self::FCGI_RESPONDER) { throw new \RuntimeException('Unsupported FGCI role'); } $this->handlers[$id] = new Handler($id, $this, $this->context, $flags & self::FCGI_KEEP_CONNECTION ? true : false); if ($this->logger) { $this->handlers[$id]->setLogger($this->logger); } break; case Record::FCGI_ABORT_REQUEST: if (!(yield $this->closeHandler($id))) { return; } break; case Record::FCGI_PARAMS: $this->handlers[$id]->handleParams($record); break; case Record::FCGI_STDIN: yield from $this->handlers[$id]->handleStdin($record, $this->incoming); break; } } } finally { $this->socket->close(); $this->processor = null; if ($this->logger) { $this->logger->debug('Closed FCGi connection to {peer}', ['peer' => $peer]); } } }