Skip to content

Commit 9313eea

Browse files
committed
merged connection resolver into main
2 parents cb11050 + 9210a00 commit 9313eea

File tree

5 files changed

+166
-6
lines changed

5 files changed

+166
-6
lines changed

src/Common/DNSAddressResolver.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Neo4j PHP Client and Driver package.
5+
*
6+
* (c) Nagels <https://nagels.tech>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Laudis\Neo4j\Common;
13+
14+
use function array_filter;
15+
use function array_map;
16+
use function array_unique;
17+
use function array_values;
18+
use const DNS_A;
19+
use const DNS_AAAA;
20+
use function dns_get_record;
21+
use Laudis\Neo4j\Contracts\AddressResolverInterface;
22+
use Throwable;
23+
24+
class DNSAddressResolver implements AddressResolverInterface
25+
{
26+
/**
27+
* @return iterable<string>
28+
*/
29+
public function getAddresses(string $host): iterable
30+
{
31+
// By using the generator pattern we make sure to call the heavy DNS IO operations
32+
// as late as possible
33+
yield $host;
34+
35+
try {
36+
/** @var list<array{ip?: string|null}> $records */
37+
$records = dns_get_record($host, DNS_A | DNS_AAAA);
38+
} catch (Throwable $e) {
39+
$records = []; // Failed DNS queries should not halt execution
40+
}
41+
42+
if (count($records) === 0) {
43+
yield from $this->tryReverseLookup($host);
44+
} else {
45+
$records = array_map(static fn (array $x) => $x['ip'] ?? '', $records);
46+
$records = array_filter($records, static fn (string $x) => $x !== '');
47+
yield from array_values(array_unique($records));
48+
}
49+
}
50+
51+
/**
52+
* @return iterable<string>
53+
*/
54+
private function tryReverseLookup(string $host): iterable
55+
{
56+
try {
57+
/** @var list<array{target?: string|null}> $records */
58+
$records = dns_get_record($host.'.in-addr.arpa');
59+
} catch (Throwable $e) {
60+
$records = []; // Failed DNS queries should not halt execution
61+
}
62+
63+
if (count($records) !== 0) {
64+
$records = array_map(static fn (array $x) => $x['target'] ?? '', $records);
65+
$records = array_filter($records, static fn (string $x) => $x !== '');
66+
yield from array_values(array_unique($records));
67+
}
68+
}
69+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Neo4j PHP Client and Driver package.
5+
*
6+
* (c) Nagels <https://nagels.tech>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Laudis\Neo4j\Contracts;
13+
14+
interface AddressResolverInterface
15+
{
16+
/**
17+
* Returns the addresses.
18+
*
19+
* @return iterable<string>
20+
*/
21+
public function getAddresses(string $host): iterable;
22+
}

src/Neo4j/Neo4jConnectionPool.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace Laudis\Neo4j\Neo4j;
1515

16+
use Laudis\Neo4j\Contracts\AddressResolverInterface;
17+
use RuntimeException;
1618
use function array_slice;
1719
use function array_unique;
1820
use Bolt\error\MessageException;
@@ -41,6 +43,8 @@
4143
use Laudis\Neo4j\Databags\SessionConfiguration;
4244
use Laudis\Neo4j\Enum\AccessMode;
4345
use Laudis\Neo4j\Enum\RoutingRoles;
46+
use function implode;
47+
use function sprintf;
4448
use const PHP_INT_MAX;
4549
use Psr\Http\Message\UriInterface;
4650
use Psr\SimpleCache\CacheInterface;
@@ -58,6 +62,9 @@
5862
*/
5963
final class Neo4jConnectionPool implements ConnectionPoolInterface
6064
{
65+
/** @psalm-readonly */
66+
private BoltConnectionPool $pool;
67+
private AddressResolverInterface $resolver;
6168
/** @var array<string, ConnectionPool> */
6269
private static array $pools = [];
6370
private SemaphoreInterface $semaphore;
@@ -119,13 +126,25 @@ public function acquire(SessionConfiguration $config): Generator
119126

120127
/** @var RoutingTable|null */
121128
$table = $this->cache->get($key, null);
129+
$triedAddresses = [];
130+
122131
if ($table == null) {
123-
$pool = $this->createOrGetPool($this->data->getUri());
124-
/** @var BoltConnection $connection */
125-
$connection = GeneratorHelper::getReturnFromGenerator($pool->acquire($config));
126-
$table = $this->routingTable($connection, $config);
127-
$this->cache->set($key, $table, $table->getTtl());
128-
$pool->release($connection);
132+
$addresses = $this->resolver->getAddresses($this->data->getUri());
133+
foreach ($addresses as $address) {
134+
$triedAddresses[] = $address;
135+
$pool = $this->createOrGetPool($address);
136+
if ($this->pool->canConnect($this->data->getUri()->withHost($address), $this->data->getAuth())) {
137+
/** @var BoltConnection $connection */
138+
$connection = GeneratorHelper::getReturnFromGenerator($pool->acquire($config));
139+
$table = $this->routingTable($connection, $config);
140+
$this->cache->set($key, $table, $table->getTtl());
141+
$pool->release($connection);
142+
}
143+
}
144+
}
145+
146+
if ($table === null) {
147+
throw new RuntimeException(sprintf('Cannot connect to host: "%s". Hosts tried: "%s"', $this->data->getUri()->getHost(), implode('", "', $triedAddresses)));
129148
}
130149

131150
$server = $this->getNextServer($table, $config->getAccessMode()) ?? $this->data->getUri();

src/Neo4j/Neo4jDriver.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Laudis\Neo4j\Authentication\Authenticate;
1919
use Laudis\Neo4j\Bolt\Session;
2020
use Laudis\Neo4j\Common\GeneratorHelper;
21+
use Laudis\Neo4j\Common\DNSAddressResolver;
2122
use Laudis\Neo4j\Common\Uri;
2223
use Laudis\Neo4j\Contracts\AuthenticateInterface;
2324
use Laudis\Neo4j\Contracts\DriverInterface;

tests/Unit/DNSAddressResolverTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Neo4j PHP Client and Driver package.
5+
*
6+
* (c) Nagels <https://nagels.tech>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Laudis\Neo4j\Tests\Unit;
13+
14+
use Laudis\Neo4j\Common\DNSAddressResolver;
15+
use PHPUnit\Framework\TestCase;
16+
17+
class DNSAddressResolverTest extends TestCase
18+
{
19+
private DNSAddressResolver $resolver;
20+
21+
protected function setUp(): void
22+
{
23+
parent::setUp();
24+
$this->resolver = new DNSAddressResolver();
25+
}
26+
27+
public function testResolverGhlenDotCom(): void
28+
{
29+
$records = [...$this->resolver->getAddresses('test.ghlen.com')];
30+
31+
$this->assertEqualsCanonicalizing(['test.ghlen.com', '123.123.123.123', '123.123.123.124'], $records);
32+
$this->assertNotEmpty($records);
33+
$this->assertEquals('test.ghlen.com', $records[0]);
34+
}
35+
36+
public function testResolverGoogleDotComReverse(): void
37+
{
38+
$records = [...$this->resolver->getAddresses('8.8.8.8')];
39+
40+
$this->assertEqualsCanonicalizing(['8.8.8.8', 'dns.google'], $records);
41+
$this->assertNotEmpty($records);
42+
$this->assertEquals('8.8.8.8', $records[0]);
43+
}
44+
45+
public function testBogus(): void
46+
{
47+
$this->assertEquals(['bogus'], [...$this->resolver->getAddresses('bogus')]);
48+
}
49+
}

0 commit comments

Comments
 (0)