Skip to content

Commit 58b5b72

Browse files
- Add support for API key management.
- Add support for nearest search node. - Rename timeout_seconds to connection_timeout_seconds. - Move `last_health_check_ts` as a property of node.
1 parent cbc6a01 commit 58b5b72

File tree

9 files changed

+374
-108
lines changed

9 files changed

+374
-108
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
}
2020
},
2121
"require": {
22-
"guzzlehttp/guzzle": "^6.0"
22+
"guzzlehttp/guzzle": "^6.0",
23+
"ext-json": "*"
2324
}
2425
}

examples/alias_operations.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
'protocol' => 'http',
1616
],
1717
],
18-
'timeout_seconds' => 2,
18+
'connection_timeout_seconds' => 2,
1919
]
2020
);
2121
echo '<pre>';

examples/collection_operations.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
'protocol' => 'http',
1616
],
1717
],
18-
'timeout_seconds' => 2,
18+
'connection_timeout_seconds' => 2,
1919
]
2020
);
2121
echo '<pre>';

examples/curation_operations.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
'protocol' => 'http',
1616
],
1717
],
18-
'timeout_seconds' => 2,
18+
'connection_timeout_seconds' => 2,
1919
]
2020
);
2121
echo '<pre>';

src/ApiCall.php

Lines changed: 89 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Devloops\Typesence;
44

5+
use Devloops\Typesence\Lib\Node;
56
use GuzzleHttp\Exception\GuzzleException;
67
use Devloops\Typesence\Lib\Configuration;
78
use GuzzleHttp\Exception\ClientException;
@@ -44,6 +45,11 @@ class ApiCall
4445
*/
4546
private static $nodes;
4647

48+
/**
49+
* @var \Devloops\Typesence\Lib\Node
50+
*/
51+
private static $nearestNode;
52+
4753
/**
4854
* @var int
4955
*/
@@ -57,78 +63,79 @@ class ApiCall
5763
/**
5864
* ApiCall constructor.
5965
*
60-
* @param \Devloops\Typesence\Lib\Configuration $config
66+
* @param \Devloops\Typesence\Lib\Configuration $config
6167
*/
6268
public function __construct(Configuration $config)
6369
{
6470
$this->config = $config;
6571
$this->client = new \GuzzleHttp\Client();
6672
self::$nodes = $this->config->getNodes();
73+
self::$nearestNode = $this->config->getNearestNode();
6774
$this->nodeIndex = 0;
6875
$this->lastHealthCheckTs = time();
76+
$this->initializeNodes();
6977
}
7078

7179
/**
72-
* @param string $endPoint
73-
* @param array $params
74-
* @param bool $asJson
80+
* Initialize Nodes
81+
*/
82+
private function initializeNodes(): void
83+
{
84+
if (!empty(self::$nearestNode)) {
85+
$this->setNodeHealthcheck(self::$nearestNode, true);
86+
}
87+
88+
foreach (self::$nodes as &$node) {
89+
$this->setNodeHealthcheck($node, true);
90+
}
91+
}
92+
93+
/**
94+
* @param string $endPoint
95+
* @param array $params
96+
* @param bool $asJson
7597
*
7698
* @return string|array
7799
* @throws \Devloops\Typesence\Exceptions\TypesenseClientError
78100
* @throws \Exception
79101
*/
80102
public function get(string $endPoint, array $params, bool $asJson = true)
81103
{
82-
return $this->makeRequest(
83-
'get',
84-
$endPoint,
85-
$asJson,
86-
[
87-
'data' => $params ?? [],
88-
]
89-
);
104+
return $this->makeRequest('get', $endPoint, $asJson, [
105+
'data' => $params ?? [],
106+
]);
90107
}
91108

92109
/**
93-
* @param string $endPoint
94-
* @param mixed $body
110+
* @param string $endPoint
111+
* @param mixed $body
95112
*
96113
* @return array
97114
* @throws \Devloops\Typesence\Exceptions\TypesenseClientError
98115
*/
99116
public function post(string $endPoint, $body): array
100117
{
101-
return $this->makeRequest(
102-
'post',
103-
$endPoint,
104-
true,
105-
[
106-
'data' => $body ?? [],
107-
]
108-
);
118+
return $this->makeRequest('post', $endPoint, true, [
119+
'data' => $body ?? [],
120+
]);
109121
}
110122

111123
/**
112-
* @param string $endPoint
113-
* @param array $body
124+
* @param string $endPoint
125+
* @param array $body
114126
*
115127
* @return array
116128
* @throws \Devloops\Typesence\Exceptions\TypesenseClientError
117129
*/
118130
public function put(string $endPoint, array $body): array
119131
{
120-
return $this->makeRequest(
121-
'put',
122-
$endPoint,
123-
true,
124-
[
125-
'data' => $body ?? [],
126-
]
127-
);
132+
return $this->makeRequest('put', $endPoint, true, [
133+
'data' => $body ?? [],
134+
]);
128135
}
129136

130137
/**
131-
* @param string $endPoint
138+
* @param string $endPoint
132139
*
133140
* @return array
134141
* @throws \Devloops\Typesence\Exceptions\TypesenseClientError
@@ -141,10 +148,10 @@ public function delete(string $endPoint): array
141148
/**
142149
* Makes the actual http request, along with retries
143150
*
144-
* @param string $method
145-
* @param string $endPoint
146-
* @param bool $asJson
147-
* @param array $options
151+
* @param string $method
152+
* @param string $endPoint
153+
* @param bool $asJson
154+
* @param array $options
148155
*
149156
* @return string|array
150157
* @throws \Devloops\Typesence\Exceptions\TypesenseClientError
@@ -162,7 +169,7 @@ private function makeRequest(
162169
$node->setHealthy(false);
163170

164171
try {
165-
$url = $node->url() . $endPoint;
172+
$url = $node->url().$endPoint;
166173
$reqOp = $this->getRequestOptions();
167174
if (isset($options['data'])) {
168175
if ($method === 'get') {
@@ -178,43 +185,41 @@ private function makeRequest(
178185

179186
$statusCode = $response->getStatusCode();
180187
if (0 < $statusCode && $statusCode < 500) {
181-
$node->setHealthy(true);
188+
$this->setNodeHealthcheck($node, true);
182189
}
183190

184-
if (($method !== 'post' && $statusCode !== 200)
185-
|| ($method === 'post'
186-
&& !($statusCode === 200
187-
|| $statusCode === 201))) {
188-
$errorMessage = json_decode(
189-
$response->getBody()->getContents(),
190-
true
191-
)['message'] ?? 'API error.';
192-
throw $this->getException($statusCode)->setMessage(
193-
$errorMessage
194-
);
191+
if (!(200 <= $statusCode && $statusCode < 300)) {
192+
$errorMessage =
193+
json_decode($response->getBody()->getContents(),
194+
true)['message'] ?? 'API error.';
195+
throw $this->getException($statusCode)
196+
->setMessage($errorMessage);
195197
}
196198

197-
return $asJson ? json_decode(
198-
$response->getBody()->getContents(),
199-
true
200-
) : $response->getBody()->getContents();
199+
return $asJson ? json_decode($response->getBody()
200+
->getContents(),
201+
true) : $response->getBody()->getContents();
201202
} catch (ClientException $exception) {
202203
if ($exception->getResponse()->getStatusCode() === 408) {
203204
continue;
204205
}
205-
throw $this->getException(
206-
$exception->getResponse()->getStatusCode()
207-
)->setMessage($exception->getMessage());
206+
$this->setNodeHealthcheck($node, false);
207+
throw $this->getException($exception->getResponse()
208+
->getStatusCode())
209+
->setMessage($exception->getMessage());
208210
} catch (RequestException $exception) {
209211
if ($exception->getResponse()->getStatusCode() === 408) {
210212
continue;
211213
}
212-
throw $this->getException(
213-
$exception->getResponse()->getStatusCode()
214-
)->setMessage($exception->getMessage());
214+
$this->setNodeHealthcheck($node, false);
215+
throw $this->getException($exception->getResponse()
216+
->getStatusCode())
217+
->setMessage($exception->getMessage());
215218
} catch (TypesenseClientError $exception) {
219+
$this->setNodeHealthcheck($node, false);
216220
throw $exception;
217221
} catch (\Exception $exception) {
222+
$this->setNodeHealthcheck($node, false);
218223
throw $exception;
219224
}
220225

@@ -230,20 +235,21 @@ private function makeRequest(
230235
private function getRequestOptions(): array
231236
{
232237
return [
233-
'headers' => [
238+
'headers' => [
234239
self::API_KEY_HEADER_NAME => $this->config->getApiKey(),
235-
],
236-
'connect_timeout' => $this->config->getTimeoutSeconds(),
240+
], 'connect_timeout' => $this->config->getConnectionTimeoutSeconds(),
237241
];
238242
}
239243

240244
/**
245+
* @param \Devloops\Typesence\Lib\Node $node
246+
*
241247
* @return bool
242248
*/
243-
private function checkFailedNode(): bool
249+
private function nodeDueForHealthCheck(Node $node): bool
244250
{
245251
$currentTimestamp = time();
246-
$checkNode = ($currentTimestamp - $this->lastHealthCheckTs)
252+
$checkNode = ($currentTimestamp - $node->getLastAccessTs())
247253
> self::CHECK_FAILED_NODE_INTERVAL_S;
248254
if ($checkNode) {
249255
$this->lastHealthCheckTs = $currentTimestamp;
@@ -252,6 +258,16 @@ private function checkFailedNode(): bool
252258
return $checkNode;
253259
}
254260

261+
/**
262+
* @param \Devloops\Typesence\Lib\Node $node
263+
* @param bool $isHealthy
264+
*/
265+
public function setNodeHealthcheck(Node $node, bool $isHealthy): void
266+
{
267+
$node->setHealthy($isHealthy);
268+
$node->setLastAccessTs(time());
269+
}
270+
255271
/**
256272
* Returns a healthy host from the pool in a round-robin fashion
257273
* Might return an unhealthy host periodically to check for recovery.
@@ -260,12 +276,18 @@ private function checkFailedNode(): bool
260276
*/
261277
public function getNode(): Lib\Node
262278
{
279+
if (self::$nearestNode) {
280+
if (self::$nearestNode->isHealthy()
281+
|| $this->nodeDueForHealthCheck(self::$nearestNode)) {
282+
return self::$nearestNode;
283+
}
284+
}
263285
$i = 0;
264286
while ($i < count(self::$nodes)) {
265287
$i++;
266288
$this->nodeIndex = ($this->nodeIndex + 1) % count(self::$nodes);
267289
if (self::$nodes[$this->nodeIndex]->isHealthy()
268-
|| $this->checkFailedNode()) {
290+
|| $this->nodeDueForHealthCheck(self::$nodes[$this->nodeIndex])) {
269291
return self::$nodes[$this->nodeIndex];
270292
}
271293
}
@@ -279,7 +301,7 @@ public function getNode(): Lib\Node
279301
}
280302

281303
/**
282-
* @param int $httpCode
304+
* @param int $httpCode
283305
*
284306
* @return \Devloops\Typesence\Exceptions\TypesenseClientError
285307
*/

0 commit comments

Comments
 (0)