Stats: Add support for chaining TimingMetric->start()
[mediawiki.git] / includes / libs / DnsSrvDiscoverer.php
blob28d71347bdaa118cbd1ac4164751dac00ebd1870
1 <?php
2 /**
3 * Service discovery using DNS SRV records
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * @file
23 /**
24 * @since 1.29
26 class DnsSrvDiscoverer {
27 /**
28 * @var string
30 private $service;
32 /**
33 * @var string
35 private $protocol;
37 /**
38 * @var string|null
40 private $domain;
42 /**
43 * @var callable
45 private $resolver;
47 /**
48 * Construct a new discoverer for the given domain, service, and protocol.
50 * @param string $service Name of the service to discover.
51 * @param string $protocol Service protocol. Defaults to 'tcp'
52 * @param ?string $domain The hostname/domain on which to perform discovery
53 * of the given service and protocol. Defaults to null which effectively
54 * performs a query relative to the host's configured search domain.
55 * @param ?callable $resolver Resolver function. Defaults to using
56 * dns_get_record. Primarily useful in testing.
58 public function __construct(
59 string $service,
60 string $protocol = 'tcp',
61 ?string $domain = null,
62 ?callable $resolver = null
63 ) {
64 $this->service = $service;
65 $this->protocol = $protocol;
66 $this->domain = $domain;
68 $this->resolver = $resolver ?? static function ( $srv ) {
69 return dns_get_record( $srv, DNS_SRV );
73 /**
74 * Queries the resolver for an SRV resource record matching the service,
75 * protocol, and domain and returns all target/port/priority/weight
76 * records.
78 * @return array
80 public function getRecords() {
81 $result = [];
83 $records = ( $this->resolver )( $this->getSrvName() );
85 // Respect RFC 2782 with regard to a single '.' entry denoting a valid
86 // empty response
87 if (
88 !$records
89 || ( count( $records ) === 1 && $records[0]['target'] === '.' )
90 ) {
91 return $result;
94 foreach ( $records as $record ) {
95 $result[] = [
96 'target' => $record['target'],
97 'port' => (int)$record['port'],
98 'pri' => (int)$record['pri'],
99 'weight' => (int)$record['weight'],
103 return $result;
107 * Performs discovery for the domain, service, and protocol, and returns a
108 * list of resolved server name/ip and port number pairs sorted by each
109 * record's priority, with servers of the same priority randomly shuffled.
111 * @return array
113 public function getServers() {
114 $records = $this->getRecords();
116 usort( $records, static function ( $a, $b ) {
117 if ( $a['pri'] === $b['pri'] ) {
118 return mt_rand( 0, 1 ) ? 1 : -1;
121 return $a['pri'] - $b['pri'];
122 } );
124 $serversAndPorts = [];
126 foreach ( $records as $record ) {
127 $serversAndPorts[] = [ $record['target'], $record['port'] ];
130 return $serversAndPorts;
134 * Returns the SRV resource record name.
136 public function getSrvName(): string {
137 $srv = "_{$this->service}._{$this->protocol}";
139 if ( $this->domain === null || $this->domain === '' ) {
140 return $srv;
143 return "$srv.{$this->domain}";