/** * Send an event to the client. * * @param string $message The message payload to be sent. * @param string $event Type of the event. * * @throws \InvalidArgumentException When the given message is not a valid UTF-8 string. */ public function send(string $message, string $event = null) : Awaitable { if (!\preg_match('//u', $message)) { throw new \InvalidArgumentException('SSE messages must be encoded as UTF-8 strings'); } $data = \str_replace("\n", "\ndata: ", $message); if ($event === null) { $event = ''; } else { $event = "event: {$event}\n"; } return $this->channel->send($event . "data: {$data}\n\n"); }
/** * 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(); } } }
/** * {@inheritdoc} */ public function channel(int $chunkSize = 4096, int $length = null) : Channel { return Channel::fromGenerator(16, function (Channel $channel) use($chunkSize, $length) { (yield $this->executor->execute(function () use($channel, $chunkSize, $length) { $remaining = $length ?? \PHP_INT_MAX; while ($remaining && null !== ($chunk = (yield $this->stream->readBuffer(\min($remaining, $chunkSize))))) { $remaining -= \strlen($chunk); (yield $channel->send($chunk)); } })); }); }
protected function doReduce($reduced, callable $reduce, Channel $input, bool $withCount = false) : \Generator { $n = 0; while ($this->eof !== ($val = (yield $input->receive($this->eof)))) { $reduced = $reduce($reduced, $val, $n++); } return $withCount ? [$reduced, $n] : $reduced; }
/** * {@inheritdoc} */ public function channel(int $chunkSize = 4096, int $length = null) : Channel { $size = (int) \max(1, \ceil($this->readBufferSize / $chunkSize)); return Channel::fromGenerator($size, function (Channel $channel) use($chunkSize, $length) { $remaining = $length ?? \PHP_INT_MAX; while ($remaining && null !== ($chunk = (yield $this->readBuffer(\min($remaining, $chunkSize))))) { $remaining -= \strlen($chunk); (yield $channel->send($chunk)); } }); }
protected function socketWriter($socket, Channel $channel, callable $transform = null) : \Generator { $writer = new SocketWriter($socket); $len = 0; try { while (null !== ($chunk = (yield $channel->receive()))) { $len += (yield $writer->write($transform ? $transform($chunk) : $chunk)); } return $len; } catch (\Throwable $e) { $channel->close($e); throw $e; } finally { $writer->dispose(); } }
/** * Handle an FCGI STDIN record that transfers request body data. * * @param Record $record * @param Channel $incoming */ public function handleStdin(Record $record, Channel $incoming) : \Generator { if (!$this->received) { $this->received = true; $request = $this->buildRequest(); $incoming->send([$this, $request]); } if ($record->data === '') { return $this->body->close(); } return (yield $this->body->send($record->data)); }
/** * Coroutine that parses incoming requests and queues them into the request pipeline. * * @param DuplexStream $socket Stream being used to transmit HTTP messages. * @param Channel $pipeline HTTP request pipeline. * @param HttpRequest $request First HTTP request within the pipeline. */ protected function parseIncomingRequests(HttpDriverContext $context, SocketStream $socket, Channel $pipeline, HttpRequest $request) : \Generator { try { do { $request = $request ?? (yield from $this->parseNextRequest($context, $socket)); $close = $this->shouldConnectionBeClosed($request); (yield $pipeline->send([$request, $close])); (yield ((yield $request->getBody()->getReadableStream()))->getAwaitable()); $request = null; } while (!$close); $pipeline->close(); } catch (\Throwable $e) { $pipeline->close($e); } }
/** * Read the next inbound HTTP request. */ public function nextRequest() : Awaitable { return $this->incoming->receive(); }
/** * {@inheritdoc} */ public function channel(int $chunkSize = 4096, int $length = null) : Channel { $chunk = \substr($this->buffer, $this->offset, $length ?? \PHP_INT_MAX); $this->offset += \strlen($chunk); return Channel::fromArray(\str_split($chunk, $chunkSize)); }