Exemple #1
0
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);
    }
}
Exemple #2
0
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));
}