Exemplo n.º 1
0
 /**
  * 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();
         }
     }
 }
Exemplo n.º 2
0
 /**
  * {@inheritdoc}
  */
 public function upgradeConnection(SocketStream $socket, HttpRequest $request, HttpResponse $response) : \Generator
 {
     $endpoint = $response->getAttribute(Endpoint::class);
     if (!$endpoint instanceof Endpoint) {
         throw new \InvalidArgumentException('No endpoint object passed to WebSocket handler');
     }
     if ($this->logger) {
         $this->logger->debug('HTTP/{protocol} connection from {peer} upgraded to WebSocket', ['protocol' => $request->getProtocolVersion(), 'peer' => $socket->getRemoteAddress()]);
     }
     $conn = new Connection($socket, false, $response->getHeaderLine('Sec-WebSocket-Protocol'));
     if ($this->logger) {
         $conn->setLogger($this->logger);
     }
     if ($deflate = $response->getAttribute(PerMessageDeflate::class)) {
         $conn->enablePerMessageDeflate($deflate);
     }
     yield from $this->delegateToEndpoint($conn, $endpoint);
 }
Exemplo n.º 3
0
 /**
  * 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]);
             }
         }
     }
 }
Exemplo n.º 4
0
 /**
  * {@inheritdoc}
  */
 public function handleConnection(HttpDriverContext $context, SocketStream $socket, callable $action) : Awaitable
 {
     return new Coroutine(function () use($context, $socket, $action) {
         $remotePeer = $socket->getRemoteAddress();
         $upgraded = false;
         if ($this->logger) {
             $this->logger->debug('Accepted new HTTP/1 connection from {peer}', ['peer' => $remotePeer]);
         }
         try {
             $request = (yield from $this->parseNextRequest($context, $socket));
             if ($request->getProtocolVersion() !== '1.0' && $request->hasHeader('Host')) {
                 try {
                     yield from $this->upgradeConnection($context, $socket, $request, $action, $upgraded);
                     if ($upgraded) {
                         return;
                     }
                 } catch (\Throwable $e) {
                     return yield from $this->sendErrorResponse($socket, $request, $e);
                 }
             }
             $tokens = $request->getHeaderTokenValues('Connection');
             if (\in_array('upgrade', $tokens, true)) {
                 foreach ($tokens as $i => $token) {
                     if ($token === 'keep-alive') {
                         unset($tokens[$i]);
                     }
                 }
                 // Ensure connections with an upgrade token in the connection header are not pipelined / persistent.
                 $request = $request->withHeader('Connection', \implode(', ', \array_merge($tokens, ['close'])));
             }
             $pipeline = Channel::fromGenerator(10, function (Channel $channel) use($socket, $request, $context) {
                 yield from $this->parseIncomingRequests($context, $socket, $channel, $request);
             });
             try {
                 while (null !== ($next = (yield $pipeline->receive()))) {
                     if (!(yield from $this->processRequest($context, $socket, $action, $upgraded, ...$next))) {
                         break;
                     }
                 }
                 $pipeline->close();
             } catch (\Throwable $e) {
                 $pipeline->close($e);
             }
         } finally {
             try {
                 $socket->close();
             } finally {
                 if ($this->logger && !$upgraded) {
                     $this->logger->debug('Closed HTTP/1 connection to {peer}', ['peer' => $remotePeer]);
                 }
             }
         }
     });
 }
Exemplo n.º 5
0
 /**
  * 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]);
         }
     }
 }