/** * @coroutine * * @param string $connectionString * @param float|int $timeout * * @return \Generator * * @resolve \Icicle\Postgres\Connection * * @throws \Icicle\Postgres\Exception\FailureException */ function connect(string $connectionString, float $timeout = 0) : \Generator { if (!($connection = @\pg_connect($connectionString, \PGSQL_CONNECT_ASYNC | \PGSQL_CONNECT_FORCE_NEW))) { throw new FailureException('Failed to create connection resource'); } if (\pg_connection_status($connection) === \PGSQL_CONNECTION_BAD) { throw new FailureException(\pg_last_error($connection)); } if (!($socket = \pg_socket($connection))) { throw new FailureException('Failed to access connection socket'); } $delayed = new Delayed(); $callback = function ($resource, bool $expired) use(&$poll, &$await, $connection, $delayed, $timeout) { try { if ($expired) { throw new TimeoutException('Connection attempt timed out.'); } switch (\pg_connect_poll($connection)) { case \PGSQL_POLLING_READING: return; // Connection not ready, poll again. // Connection not ready, poll again. case \PGSQL_POLLING_WRITING: $await->listen($timeout); return; // Still writing... // Still writing... case \PGSQL_POLLING_FAILED: throw new FailureException('Could not connect to PostgreSQL server'); case \PGSQL_POLLING_OK: $poll->free(); $await->free(); $delayed->resolve(new BasicConnection($connection, $resource)); return; } } catch (\Throwable $exception) { $poll->free(); $await->free(); \pg_close($connection); $delayed->reject($exception); } }; $poll = Loop\poll($socket, $callback, true); $await = Loop\await($socket, $callback); $poll->listen($timeout); $await->listen($timeout); return (yield $delayed); }
/** * {@inheritdoc} */ public function connect(string $ip, int $port = null, array $options = []) : \Generator { $protocol = (string) ($options['protocol'] ?? (null === $port ? 'unix' : 'tcp')); $autoClose = (bool) ($options['auto_close'] ?? true); $allowSelfSigned = (bool) ($options['allow_self_signed'] ?? self::DEFAULT_ALLOW_SELF_SIGNED); $timeout = (double) ($options['timeout'] ?? self::DEFAULT_CONNECT_TIMEOUT); $verifyDepth = (int) ($options['verify_depth'] ?? self::DEFAULT_VERIFY_DEPTH); $cafile = (string) ($options['cafile'] ?? ''); $name = (string) ($options['name'] ?? ''); $cn = (string) ($options['cn'] ?? $name); $context = []; $context['socket'] = ['connect' => Socket\makeName($ip, $port)]; $context['ssl'] = ['capture_peer_cert' => true, 'capture_peer_chain' => true, 'capture_peer_cert_chain' => true, 'verify_peer' => true, 'verify_peer_name' => true, 'allow_self_signed' => $allowSelfSigned, 'verify_depth' => $verifyDepth, 'CN_match' => $cn, 'SNI_enabled' => true, 'SNI_server_name' => $name, 'peer_name' => $name, 'disable_compression' => true, 'honor_cipher_order' => true]; if ('' !== $cafile) { if (!file_exists($cafile)) { throw new InvalidArgumentError('No file exists at path given for cafile.'); } $context['ssl']['cafile'] = $cafile; } $context = stream_context_create($context); $uri = Socket\makeUri($protocol, $ip, $port); // Error reporting suppressed since stream_socket_client() emits an E_WARNING on failure (checked below). $socket = @stream_socket_client($uri, $errno, $errstr, null, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT, $context); if (!$socket || $errno) { throw new FailureException(sprintf('Could not connect to %s; Errno: %d; %s', $uri, $errno, $errstr)); } $delayed = new Delayed(); $await = Loop\await($socket, function ($resource, bool $expired, Io $await) use($delayed, $autoClose) { $await->free(); if ($expired) { $delayed->reject(new TimeoutException('Connection attempt timed out.')); return; } $delayed->resolve(new NetworkSocket($resource, $autoClose)); }); $await->listen($timeout); try { return (yield $delayed); } finally { $await->free(); } }