Skip to content

Commit 630a47f

Browse files
committed
introduced connection caching
1 parent 4ab872b commit 630a47f

16 files changed

+281
-131
lines changed

src/Authentication/BasicAuth.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,9 @@ public function authenticateBolt(Bolt $bolt, UriInterface $uri, string $userAgen
4646
{
4747
$bolt->init($userAgent, $this->username, $this->password);
4848
}
49+
50+
public function extractFromUri(UriInterface $uri): AuthenticateInterface
51+
{
52+
return $this;
53+
}
4954
}

src/Authentication/KerberosAuth.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,9 @@ public function authenticateBolt(Bolt $bolt, UriInterface $uri, string $userAgen
3838
$bolt->setScheme('kerberos');
3939
$bolt->init($userAgent, $this->token, $this->token);
4040
}
41+
42+
public function extractFromUri(UriInterface $uri): AuthenticateInterface
43+
{
44+
return $this;
45+
}
4146
}

src/Authentication/NoAuth.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,9 @@ public function authenticateBolt(Bolt $bolt, UriInterface $uri, string $userAgen
3030
$bolt->setScheme('none');
3131
$bolt->init($userAgent, '', '');
3232
}
33+
34+
public function extractFromUri(UriInterface $uri): AuthenticateInterface
35+
{
36+
return $this;
37+
}
3338
}

src/Authentication/UrlAuth.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,17 @@ public function authenticateHttp(RequestInterface $request, UriInterface $uri, s
3535
}
3636

3737
public function authenticateBolt(Bolt $bolt, UriInterface $uri, string $userAgent): void
38+
{
39+
$this->extractFromUri($uri)->authenticateBolt($bolt, $uri, $userAgent);
40+
}
41+
42+
public function extractFromUri(UriInterface $uri): AuthenticateInterface
3843
{
3944
if (substr_count($uri->getUserInfo(), ':') === 1) {
4045
[$user, $pass] = explode(':', $uri->getUserInfo());
41-
Authenticate::basic($user, $pass)->authenticateBolt($bolt, $uri, $userAgent);
42-
} else {
43-
Authenticate::disabled()->authenticateBolt($bolt, $uri, $userAgent);
46+
return Authenticate::basic($user, $pass);
4447
}
48+
49+
return Authenticate::disabled();
4550
}
4651
}

src/Bolt/BoltConnectionPool.php

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,31 @@
1313

1414
namespace Laudis\Neo4j\Bolt;
1515

16+
use function array_flip;
1617
use Bolt\Bolt;
1718
use Bolt\connection\StreamSocket;
1819
use Exception;
1920
use function explode;
20-
use Laudis\Neo4j\Common\TransactionHelper;
21+
use const FILTER_VALIDATE_IP;
22+
use function filter_var;
23+
use Laudis\Neo4j\Common\BoltConnection;
2124
use Laudis\Neo4j\Contracts\AuthenticateInterface;
2225
use Laudis\Neo4j\Contracts\ConnectionInterface;
2326
use Laudis\Neo4j\Contracts\ConnectionPoolInterface;
27+
use Laudis\Neo4j\Databags\DatabaseInfo;
2428
use Laudis\Neo4j\Databags\SessionConfiguration;
29+
use Laudis\Neo4j\Enum\ConnectionProtocol;
30+
use Laudis\Neo4j\Neo4j\RoutingTable;
2531
use Psr\Http\Message\UriInterface;
26-
use function str_starts_with;
2732

2833
/**
2934
* @implements ConnectionPoolInterface<Bolt>
3035
*/
3136
final class BoltConnectionPool implements ConnectionPoolInterface
3237
{
38+
/** @var array<string, list<ConnectionInterface<Bolt>>> */
39+
private static array $connectionCache = [];
40+
3341
/**
3442
* @throws Exception
3543
*/
@@ -38,24 +46,92 @@ public function acquire(
3846
AuthenticateInterface $authenticate,
3947
float $socketTimeout,
4048
string $userAgent,
41-
SessionConfiguration $config
49+
SessionConfiguration $config,
50+
?RoutingTable $table = null,
51+
?UriInterface $server = null
4252
): ConnectionInterface {
43-
$host = $uri->getHost();
44-
$socket = new StreamSocket($host, $uri->getPort() ?? 7687, $socketTimeout);
53+
$connectingTo = $server ?? $uri;
54+
$key = $connectingTo->getHost().':'.$connectingTo->getPort();
55+
if (!isset($this->connectionCache[$key])) {
56+
self::$connectionCache[$key] = [];
57+
}
58+
59+
foreach (self::$connectionCache[$key] as $connection) {
60+
if ($connection->isOpen()) {
61+
return $connection;
62+
}
63+
}
64+
65+
$socket = new StreamSocket($connectingTo->getHost(), $connectingTo->getPort() ?? 7687, $socketTimeout);
66+
67+
$this->configureSsl($uri, $connectingTo, $socket, $table);
68+
69+
$bolt = new Bolt($socket);
70+
$authenticate->authenticateBolt($bolt, $connectingTo, $userAgent);
71+
72+
/**
73+
* @var array{'name': 0, 'version': 1, 'edition': 2}
74+
* @psalm-suppress all
75+
*/
76+
$fields = array_flip($bolt->run(<<<'CYPHER'
77+
CALL dbms.components()
78+
YIELD name, versions, edition
79+
UNWIND versions AS version
80+
RETURN name, version, edition
81+
CYPHER)['fields']);
4582

46-
$this->configureSsl($uri, $host, $socket);
83+
/** @var array{0: array{0: string, 1: string, 2: string}} $results */
84+
$results = $bolt->pullAll();
4785

48-
return TransactionHelper::connectionFromSocket($socket, $uri, $userAgent, $authenticate, $config);
86+
$connection = new BoltConnection(
87+
$bolt,
88+
$socket,
89+
$results[0][$fields['name']].'-'.$results[0][$fields['edition']].'/'.$results[0][$fields['version']],
90+
$connectingTo,
91+
$results[0][$fields['version']],
92+
ConnectionProtocol::determineBoltVersion($bolt),
93+
$config->getAccessMode(),
94+
new DatabaseInfo($config->getDatabase())
95+
);
96+
97+
self::$connectionCache[$key][] = $connection;
98+
99+
return $connection;
49100
}
50101

51-
private function configureSsl(UriInterface $uri, string $host, StreamSocket $socket): void
102+
private function configureSsl(UriInterface $uri, UriInterface $server, StreamSocket $socket, ?RoutingTable $table): void
52103
{
53104
$scheme = $uri->getScheme();
54105
$explosion = explode('+', $scheme, 2);
55106
$sslConfig = $explosion[1] ?? '';
56107

57108
if (str_starts_with('s', $sslConfig)) {
58-
TransactionHelper::enableSsl($host, $sslConfig, $socket);
109+
// We have to pass a different host when working with ssl on aura.
110+
// There is a strange behaviour where if we pass the uri host on a single
111+
// instance aura deployment, we need to pass the original uri for the
112+
// ssl configuration to be valid.
113+
if ($table && $table->getWithRole()->count() > 1) {
114+
$this->enableSsl($server->getHost(), $sslConfig, $socket);
115+
} else {
116+
$this->enableSsl($uri->getHost(), $sslConfig, $socket);
117+
}
118+
}
119+
}
120+
121+
private function enableSsl(string $host, string $sslConfig, StreamSocket $sock): void
122+
{
123+
$options = [
124+
'verify_peer' => true,
125+
'peer_name' => $host,
126+
];
127+
if (!filter_var($host, FILTER_VALIDATE_IP)) {
128+
$options['SNI_enabled'] = true;
129+
}
130+
if ($sslConfig === 's') {
131+
$sock->setSslContextOptions($options);
132+
} elseif ($sslConfig === 'ssc') {
133+
$options['allow_self_signed'] = true;
134+
$sock->setSslContextOptions($options);
59135
}
60136
}
61137
}

src/Bolt/BoltUnmanagedTransaction.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,9 @@ private function getBolt(): Bolt
146146
{
147147
return $this->connection->getImplementation();
148148
}
149+
150+
public function __destruct()
151+
{
152+
$this->connection->close();
153+
}
149154
}

src/Bolt/Session.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,10 @@ public function beginTransaction(?iterable $statements = null, ?TransactionConfi
133133
*/
134134
private function beginInstantTransaction(SessionConfiguration $config): TransactionInterface
135135
{
136-
return new BoltUnmanagedTransaction(
137-
$this->config->getDatabase(),
138-
$this->formatter,
139-
$this->acquireConnection(TransactionConfiguration::default(), $config)
140-
);
136+
$connection = $this->acquireConnection(TransactionConfiguration::default(), $config);
137+
$connection->open();
138+
139+
return new BoltUnmanagedTransaction($this->config->getDatabase(), $this->formatter, $connection);
141140
}
142141

143142
/**

src/Common/BoltConnection.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Laudis Neo4j package.
7+
*
8+
* (c) Laudis technologies <http://laudis.tech>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Laudis\Neo4j\Common;
15+
16+
use Bolt\Bolt;
17+
use Bolt\connection\IConnection;
18+
use Laudis\Neo4j\Contracts\ConnectionInterface;
19+
use Laudis\Neo4j\Databags\DatabaseInfo;
20+
use Laudis\Neo4j\Enum\AccessMode;
21+
use Laudis\Neo4j\Enum\ConnectionProtocol;
22+
use Psr\Http\Message\UriInterface;
23+
24+
/**
25+
* @implements ConnectionInterface<Bolt>
26+
*/
27+
final class BoltConnection implements ConnectionInterface
28+
{
29+
private Bolt $bolt;
30+
private string $serverAgent;
31+
private UriInterface $serverAddress;
32+
private string $serverVersion;
33+
private ConnectionProtocol $protocol;
34+
private AccessMode $accessMode;
35+
private DatabaseInfo $databaseInfo;
36+
private IConnection $socket;
37+
38+
private bool $isOpen = true;
39+
40+
public function __construct(
41+
Bolt $bolt,
42+
IConnection $socket,
43+
string $serverAgent,
44+
UriInterface $serverAddress,
45+
string $serverVersion,
46+
ConnectionProtocol $protocol,
47+
AccessMode $accessMode,
48+
DatabaseInfo $databaseInfo
49+
) {
50+
$this->bolt = $bolt;
51+
$this->serverAgent = $serverAgent;
52+
$this->serverAddress = $serverAddress;
53+
$this->serverVersion = $serverVersion;
54+
$this->protocol = $protocol;
55+
$this->accessMode = $accessMode;
56+
$this->databaseInfo = $databaseInfo;
57+
$this->socket = $socket;
58+
}
59+
60+
public function getImplementation()
61+
{
62+
return $this->bolt;
63+
}
64+
65+
public function getServerAgent(): string
66+
{
67+
return $this->serverAgent;
68+
}
69+
70+
public function getServerAddress(): UriInterface
71+
{
72+
return $this->serverAddress;
73+
}
74+
75+
public function getServerVersion(): string
76+
{
77+
return $this->serverVersion;
78+
}
79+
80+
public function getProtocol(): ConnectionProtocol
81+
{
82+
return $this->protocol;
83+
}
84+
85+
public function getAccessMode(): AccessMode
86+
{
87+
return $this->accessMode;
88+
}
89+
90+
public function getDatabaseInfo(): DatabaseInfo
91+
{
92+
return $this->databaseInfo;
93+
}
94+
95+
public function isOpen(): bool
96+
{
97+
return $this->isOpen;
98+
}
99+
100+
public function open(): void
101+
{
102+
if (!$this->isOpen) {
103+
$this->isOpen = true;
104+
}
105+
}
106+
107+
public function close(): void
108+
{
109+
if ($this->isOpen) {
110+
$this->isOpen = false;
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)