/**
  * Coroutine that connects to a WebSocket server.
  * 
  * @param Uri $uri HTTP URI (use http / https as scheme instead of ws / wss).
  * @param array $options Stream context options (e.g. SSL settings) to be passed to SocketStream::connect().
  * @param LoggerInterface $logger
  * @return WebSocketConnector
  */
 public static function connect(Uri $uri, array $options = [], LoggerInterface $logger = NULL) : \Generator
 {
     $secure = $uri->getScheme() === 'https';
     $host = $uri->getHost();
     $port = $uri->getPort() ?? ($secure ? 443 : 80);
     $socket = (yield from SocketStream::connect($host, $port, 'tcp', 0, array_replace_recursive($options, ['socket' => ['tcp_nodelay' => true]])));
     try {
         if ($secure) {
             yield from $socket->encryptClient();
         }
         $nonce = base64_encode(random_bytes(16));
         $message = sprintf("GET /%s HTTP/1.1\r\n", $uri->getPath());
         $message .= "Upgrade: websocket\r\n";
         $message .= "Connection: Upgrade\r\n";
         $message .= "Content-Length: 0\r\n";
         $message .= sprintf("Host: %s\r\n", $host);
         $message .= sprintf("Sec-WebSocket-Key: %s\r\n", $nonce);
         $message .= "Sec-WebSocket-Version: 13\r\n";
         $message .= "\r\n";
         yield from $socket->write($message);
         $m = NULL;
         $line = (yield from $socket->readLine());
         if (!preg_match("'^HTTP/(1\\.[01])\\s+([1-5][0-9]{2})\\s*(.*)\$'i", $line, $m)) {
             throw new WebSocketException('Server did not respond with a valid HTTP/1 status line');
         }
         $response = new HttpResponse();
         $response = $response->withStatus((int) $m[2], trim($m[3]));
         $response = $response->withProtocolVersion($m[1]);
         while (!$socket->eof()) {
             $line = (yield from $socket->readLine());
             if ($line === '') {
                 break;
             }
             $header = array_map('trim', explode(':', $line, 2));
             $response = $response->withAddedHeader(...$header);
         }
         if ($response->getProtocolVersion() !== '1.1') {
             throw new WebSocketException('WebSocket handshake requires HTTP/1.1 response');
         }
         if ($response->getStatusCode() !== 101) {
             throw new WebSocketException('WebSocket protocol switching failed');
         }
         $accept = base64_encode(sha1($nonce . self::GUID, true));
         if ($accept !== $response->getHeaderLine('Sec-WebSocket-Accept')) {
             throw new WebSocketException('Invalid WebSocket Accept header received');
         }
     } catch (\Throwable $e) {
         $socket->close();
         throw $e;
     }
     $conn = new static($socket, (yield eventEmitter()), $logger);
     $conn->watcher = (yield runTask($conn->handleFrames(), 'WebSocket Message Handler'));
     $conn->watcher->setAutoShutdown(true);
     return $conn;
 }
Esempio n. 2
0
 public function releaseConnection(Uri $uri, ConnectorContext $context, int $ttl = null, int $remaining = null)
 {
     $key = \sprintf('%s://%s', $uri->getScheme(), $uri->getHostWithPort(true));
     if ($ttl === null) {
         $ttl = $this->maxLifetime;
     } else {
         $ttl = \min($ttl, $this->maxLifetime);
     }
     if ($remaining === null) {
         $remaining = $this->max;
     } else {
         $remaining = \min($remaining, $this->max);
     }
     if ($remaining > 0 && $ttl > 0 && $context->socket->isAlive()) {
         $context->connected = true;
         if (empty($this->conns[$key])) {
             $this->conns[$key] = new \SplQueue();
         }
         if (!empty($this->connecting[$key])) {
             $defer = \array_shift($this->connecting[$key]);
             return $defer->resolve($context);
         }
         $context->expires = \time() + $ttl;
         $context->remaining = $remaining;
         $this->conns[$key]->enqueue($context);
         return;
     }
     $context->socket->close();
     $this->connectionDisposed($key);
 }
Esempio n. 3
0
 protected function connectSocket(Uri $uri) : Awaitable
 {
     $host = $uri->getHost();
     if (Address::isResolved($host)) {
         $factory = new SocketFactory(new Address($host) . ':' . $uri->getPort(), 'tcp');
     } else {
         $factory = new SocketFactory($uri->getHostWithPort(true), 'tcp');
     }
     $factory->setTcpNoDelay(true);
     if ($this->protocols && Socket::isAlpnSupported()) {
         $factory->setOption('ssl', 'alpn_protocols', \implode(',', $this->protocols));
     }
     return $factory->createSocketStream(5, $uri->getScheme() === 'https');
 }
Esempio n. 4
0
 /**
  * {@inheritdoc}
  */
 public function getConnectorContext(Uri $uri) : Awaitable
 {
     $key = \sprintf('%s://%s', $uri->getScheme(), $uri->getHostWithPort(true));
     if (isset($this->connections[$key])) {
         if ($this->connections[$key]->isAlive()) {
             $context = new ConnectorContext();
             $context->connected = true;
             $context->conn = $this->connections[$key];
             return new Success($context);
         }
         unset($this->connections[$key]);
     }
     if (isset($this->connecting[$key])) {
         $defer = new Deferred();
         $this->connecting[$key]->enqueue($defer);
         return $defer;
     }
     $this->connecting[$key] = new \SplQueue();
     $context = new ConnectorContext(function ($context) use($key) {
         if ($this->connecting[$key]->isEmpty()) {
             unset($this->connecting[$key]);
         } else {
             $this->connecting[$key]->dequeue()->resolve($context);
         }
     });
     return new Success($context);
 }