function __doConnect($uri, array $options) { $contextOptions = []; if (\stripos($uri, "unix://") === 0 || \stripos($uri, "udg://") === 0) { list($scheme, $path) = explode("://", $uri, 2); $isUnixSock = true; $resolvedUri = "{$scheme}:///" . \ltrim($path, "/"); } else { $isUnixSock = false; // TCP/UDP host names are always case-insensitive if (!($uriParts = @\parse_url(strtolower($uri)))) { throw new \DomainException("Invalid URI: {$uri}"); } // $scheme, $host, $port, $path \extract($uriParts); $scheme = empty($scheme) ? "tcp" : $scheme; if (!($scheme === "tcp" || $scheme === "udp")) { throw new \DomainException("Invalid URI scheme ({$scheme}); tcp, udp, unix or udg scheme expected"); } if (empty($host) || empty($port)) { throw new \DomainException("Invalid URI ({$uri}); host and port components required"); } if (PHP_VERSION_ID < 50600 && $scheme === "tcp") { // Prior to PHP 5.6 the SNI_server_name only registers if assigned to the stream // context at the time the socket is first connected (NOT with stream_socket_enable_crypto()). // So we always add the necessary ctx option here along with our own custom SNI_nb_hack // key to communicate our intent to the CryptoBroker if it's subsequently used $contextOptions = ["ssl" => ["SNI_server_name" => $host, "SNI_nb_hack" => true]]; } if ($inAddr = @\inet_pton($host)) { $isIpv6 = isset($inAddr[15]); } else { $records = (yield \Amp\Dns\resolve($host)); list($host, $mode) = $records[0]; $isIpv6 = $mode === \Amp\Dns\Record::AAAA; } $resolvedUri = $isIpv6 ? "[{$host}]:{$port}" : "{$host}:{$port}"; } $flags = \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT; $timeout = 42; // <--- timeout not applicable for async connects $bindTo = empty($options["bind_to"]) ? "" : (string) $options["bind_to"]; if (!$isUnixSock && $bindTo) { $contextOptions["socket"]["bindto"] = $bindTo; } $ctx = \stream_context_create($contextOptions); if (!($socket = @\stream_socket_client($resolvedUri, $errno, $errstr, $timeout, $flags, $ctx))) { throw new ConnectException(\sprintf("Connection to %s failed: [Error #%d] %s", $uri, $errno, $errstr)); } \stream_set_blocking($socket, false); $promisor = new \Amp\Deferred(); $promise = $promisor->promise(); $watcherId = \Amp\onWritable($socket, [$promisor, "succeed"]); $timeout = empty($options["timeout"]) ? 30000 : $options["timeout"]; try { (yield $timeout > 0 ? \Amp\timeout($promise, $timeout) : $promise); \Amp\cancel($watcherId); (yield new \Amp\CoroutineResult($socket)); } catch (\Amp\TimeoutException $e) { \Amp\cancel($watcherId); throw new ConnectException("Connection to {$uri} failed: timeout exceeded ({$timeout} ms)", 0, $e); } }
function __doResolve($name, array $types, $options) { static $state; $state = $state ?: (yield \Amp\resolve(__init())); if (empty($types)) { (yield new CoroutineResult([])); return; } assert(array_reduce($types, function ($result, $val) { return $result && \is_int($val); }, true), 'The $types passed to DNS functions must all be integers (from \\Amp\\Dns\\Record class)'); $name = \strtolower($name); $result = []; // Check for cache hits if (!isset($options["cache"]) || $options["cache"]) { foreach ($types as $k => $type) { $cacheKey = "{$name}#{$type}"; $cacheValue = (yield $state->arrayCache->get($cacheKey)); if ($cacheValue !== null) { $result[$type] = $cacheValue; unset($types[$k]); } } if (empty($types)) { (yield new CoroutineResult($result)); return; } } $timeout = empty($options["timeout"]) ? $state->config["timeout"] : (int) $options["timeout"]; if (empty($options["server"])) { if (empty($state->config["nameservers"])) { throw new ResolutionException("No nameserver specified in system config"); } $uri = "udp://" . $state->config["nameservers"][0]; } else { $uri = __parseCustomServerUri($options["server"]); } foreach ($types as $type) { $promises[] = __doRequest($state, $uri, $name, $type); } try { list(, $resultArr) = (yield \Amp\timeout(\Amp\some($promises), $timeout)); foreach ($resultArr as $value) { $result += $value; } } catch (\Amp\TimeoutException $e) { if (substr($uri, 0, 6) == "tcp://") { throw new TimeoutException("Name resolution timed out for {$name}"); } else { $options["server"] = \preg_replace("#[a-z.]+://#", "tcp://", $uri); (yield new CoroutineResult(\Amp\resolve(__doResolve($name, $types, $options)))); } } catch (ResolutionException $e) { if (empty($result)) { // if we have no cached results throw $e; } } catch (\RuntimeException $e) { // if all promises in Amp\some fail if (empty($result)) { // if we have no cached results throw new ResolutionException("All name resolution requests failed", 0, $e); } } (yield new CoroutineResult($result)); }