-
-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathSearchingExecutor.php
More file actions
132 lines (117 loc) · 3.75 KB
/
SearchingExecutor.php
File metadata and controls
132 lines (117 loc) · 3.75 KB
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<?php
namespace React\Dns\Query;
use React\Dns\Model\Message;
use React\Promise\PromiseInterface;
use function React\Promise\resolve;
/**
* In sequence attempt to find Fully Qualified Domain Name (FQDN) when not
* explicitly queried for, and when the amount of dots in the name is equal
* or greater than the passed ndots parameter.
*
* Wraps an existing `ExecutorInterface` to do the actual querying and only
* concerns itself with working through the list of search domains until a
* matching answer comes back from the resolver.
*
* This might cause a delay for domains that are never meant to be search
* such as public domains like exampled.com, as such always query with a
* FQDN when searching isn't required.
*
* This is useful in situations like Kubernetes where you might only know
* a services name and namespace but don't know the rest of the clusters
* LOOK UP svc.cluster.local and want to rely on the resolve.conf injected
* into pods.
*
* This is a high level executor, and it should be placed between the
* RetryExecutor and the CoopExecutor. So transient networking issues
* are handled through the RetryExecutor and other low level executors.
* And no duplicated queries are sent out with CoopExecutor.
*
* ```php
* $executor = new CoopExecutor(
* new SearchingExecutor(
* new RetryExecutor(
* new TimeoutExecutor(
* new UdpTransportExecutor($nameserver),
* 3.0
* )
* ),
* 5,
* 'svc',
* 'svc.cluster',
* 'svc.cluster.local'
* )
* );
* ```
*/
final class SearchingExecutor implements ExecutorInterface
{
/**
* @var ExecutorInterface
*/
private $executor;
/**
* @var int
*/
private $ndots;
/**
* @var array<string>
*/
private $domains;
public function __construct(ExecutorInterface $base, int $ndots, string $firstDomain, string ...$domains)
{
$this->executor = $base;
$this->ndots = $ndots;
array_unshift($domains, $firstDomain);
$this->domains = $domains;
}
public function query(Query $query)
{
if (substr($query->name, -1) === '.') {
return $this->executor->query(new Query(
substr($query->name, 0, -1),
$query->type,
$query->class
));
}
$startWithAbsolute = substr_count($query->name, '.') >= $this->ndots;
$domains = [];
if ($startWithAbsolute === true) {
$domains[] = $query->name;
}
foreach ($this->domains as $domain) {
$domains[] = $query->name . '.' . $domain;
}
if ($startWithAbsolute === false) {
$domains[] = $query->name;
}
$firstDomain = array_shift($domains);
$seeker = function (Message $message) use ($query, &$seeker, &$domains): PromiseInterface {
if ($this->hasRecords($message, $query)) {
return resolve($message);
}
$promise = $this->executor->query(new Query(
array_shift($domains),
$query->type,
$query->class
));
if (count($domains) > 0) {
$promise = $promise->then($seeker);
}
return $promise;
};
return $this->executor->query(new Query(
$firstDomain,
$query->type,
$query->class
))->then($seeker);
}
private function hasRecords(Message $message, Query $query): bool
{
foreach ($message->answers as $record) {
if ($record->type === $query->type && $record->class === $query->class) {
return true;
}
}
return false;
}
}