/** * 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; }
public static function connect(string $dsn, string $username, string $password, LoggerInterface $logger = NULL) : \Generator { if ('mysql:' !== substr($dsn, 0, 6)) { throw new \InvalidArgumentException(sprintf('Invalid MySQL DSN: "%s"', $dsn)); } $settings = []; foreach (explode(';', substr($dsn, 6)) as $part) { list($k, $v) = array_map('trim', explode('=', $part)); switch ($k) { case 'host': case 'dbname': case 'unix_socket': $settings[$k] = $v; break; case 'port': $settings[$k] = (int) $v; break; default: throw new \InvalidArgumentException(sprintf('Unknown MySQL DSN param: "%s"', $k)); } } if (empty($settings['host']) && empty($settings['unix_socket'])) { throw new \InvalidArgumentException('Neighter MySQL host nor Unix domain socket specified in MySQL DSN'); } if (!empty($settings['unix_socket'])) { if (!Socket::isUnixSocketSupported()) { throw new \RuntimeException(sprintf('Cannot connect to MySQL socket "%s", PHP was not compiled with support for Unix domain sockets', $settings['unix_socket'])); } $client = new Client(yield from SocketStream::fromUrl('unix://' . $settings['unix_socket']), $logger); } else { $client = new Client(yield from SocketStream::connect($settings['host'], $settings['port'] ?? 3306), $logger); } yield from $client->handleHandshake($username, $password); $conn = new static($client, (yield currentExecutor()), $logger); if (!empty($settings['dbname'])) { yield from $conn->changeDefaultSchema($settings['dbname']); } return $conn; }