1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
<?php
namespace React\Socket;
use React\Dns\Resolver\ResolverInterface;
use React\Promise;
use React\Promise\CancellablePromiseInterface;
final class DnsConnector implements ConnectorInterface
{
private $connector;
private $resolver;
public function __construct(ConnectorInterface $connector, ResolverInterface $resolver)
{
$this->connector = $connector;
$this->resolver = $resolver;
}
public function connect($uri)
{
$original = $uri;
if (\strpos($uri, '://') === false) {
$uri = 'tcp://' . $uri;
$parts = \parse_url($uri);
if (isset($parts['scheme'])) {
unset($parts['scheme']);
}
} else {
$parts = \parse_url($uri);
}
if (!$parts || !isset($parts['host'])) {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $original . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
));
}
$host = \trim($parts['host'], '[]');
$connector = $this->connector;
// skip DNS lookup / URI manipulation if this URI already contains an IP
if (@\inet_pton($host) !== false) {
return $connector->connect($original);
}
$promise = $this->resolver->resolve($host);
$resolved = null;
return new Promise\Promise(
function ($resolve, $reject) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
// resolve/reject with result of DNS lookup
$promise->then(function ($ip) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
$resolved = $ip;
return $promise = $connector->connect(
Connector::uri($parts, $host, $ip)
)->then(null, function (\Exception $e) use ($uri) {
if ($e instanceof \RuntimeException) {
$message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage());
$e = new \RuntimeException(
'Connection to ' . $uri . ' failed: ' . $message,
$e->getCode(),
$e
);
// avoid garbage references by replacing all closures in call stack.
// what a lovely piece of code!
$r = new \ReflectionProperty('Exception', 'trace');
$r->setAccessible(true);
$trace = $r->getValue($e);
// Exception trace arguments are not available on some PHP 7.4 installs
// @codeCoverageIgnoreStart
foreach ($trace as &$one) {
if (isset($one['args'])) {
foreach ($one['args'] as &$arg) {
if ($arg instanceof \Closure) {
$arg = 'Object(' . \get_class($arg) . ')';
}
}
}
}
// @codeCoverageIgnoreEnd
$r->setValue($e, $trace);
}
throw $e;
});
}, function ($e) use ($uri, $reject) {
$reject(new \RuntimeException('Connection to ' . $uri .' failed during DNS lookup: ' . $e->getMessage(), 0, $e));
})->then($resolve, $reject);
},
function ($_, $reject) use (&$promise, &$resolved, $uri) {
// cancellation should reject connection attempt
// reject DNS resolution with custom reason, otherwise rely on connection cancellation below
if ($resolved === null) {
$reject(new \RuntimeException(
'Connection to ' . $uri . ' cancelled during DNS lookup (ECONNABORTED)',
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
));
}
// (try to) cancel pending DNS lookup / connection attempt
if ($promise instanceof CancellablePromiseInterface) {
// overwrite callback arguments for PHP7+ only, so they do not show
// up in the Exception trace and do not cause a possible cyclic reference.
$_ = $reject = null;
$promise->cancel();
$promise = null;
}
}
);
}
}
|