Skip to content

Commit d303100

Browse files
committed
Added elastic-api-version when serverless
1 parent a5251e6 commit d303100

12 files changed

+158
-119
lines changed

phpstan.neon

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ parameters:
33
paths:
44
- src
55
ignoreErrors:
6-
- '#Offset ''body'' on array\{\}\|array\{#'
6+
- '#Offset ''body'' on array\{\}\|array\{#'
7+
- '#Access to an undefined property Elastic\\Elasticsearch\\Client::\$client.#'

src/Client.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@
2323
use Elastic\Transport\Transport;
2424
use Http\Promise\Promise;
2525
use Psr\Http\Message\RequestInterface;
26+
use Psr\Http\Message\ResponseInterface;
2627
use Psr\Log\LoggerInterface;
2728

2829
final class Client implements ClientInterface
2930
{
3031
const CLIENT_NAME = 'es';
3132
const VERSION = '9.0.0';
3233
const API_COMPATIBILITY_HEADER = '%s/vnd.elasticsearch+%s; compatible-with=9';
34+
const API_VERSION_HEADER = 'elastic-api-version';
35+
const API_VERSION = '2023-10-31';
3336

3437
const SEARCH_ENDPOINTS = [
3538
'search',
@@ -50,6 +53,7 @@ final class Client implements ClientInterface
5053

5154
protected Transport $transport;
5255
protected LoggerInterface $logger;
56+
protected bool $serverless = false;
5357

5458
/**
5559
* Specify is the request is asyncronous
@@ -156,6 +160,23 @@ public function getResponseException(): bool
156160
return $this->responseException;
157161
}
158162

163+
/**
164+
* @inheritdoc
165+
*/
166+
public function setServerless(bool $value): self
167+
{
168+
$this->serverless = $value;
169+
return $this;
170+
}
171+
172+
/**
173+
* @inheritdoc
174+
*/
175+
public function getServerless(): bool
176+
{
177+
return $this->serverless;
178+
}
179+
159180
/**
160181
* @inheritdoc
161182
*/
@@ -168,8 +189,8 @@ public function sendRequest(RequestInterface $request)
168189
}
169190
$this->transport->setAsyncOnSuccess(
170191
$request->getMethod() === 'HEAD'
171-
? new AsyncOnSuccessNoException
172-
: ($this->getResponseException() ? new AsyncOnSuccess : new AsyncOnSuccessNoException)
192+
? new AsyncOnSuccessNoException($this)
193+
: ($this->getResponseException() ? new AsyncOnSuccess($this) : new AsyncOnSuccessNoException($this))
173194
);
174195
return $this->transport->sendAsyncRequest($request);
175196
}
@@ -183,6 +204,7 @@ public function sendRequest(RequestInterface $request)
183204

184205
$result = new Elasticsearch;
185206
$result->setResponse($response, $request->getMethod() === 'HEAD' ? false : $this->getResponseException());
207+
$this->serverless = $result->isServerless();
186208
return $result;
187209
}
188210
}

src/ClientBuilder.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,9 @@ public function build(): Client
400400
// Enable or disable the x-elastic-client-meta header
401401
$client->setElasticMetaHeader($this->elasticMetaHeader);
402402

403+
if ($this->isServerless($this->hosts)) {
404+
$client->setServerless(true);
405+
}
403406
return $client;
404407
}
405408

src/ClientInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ public function setResponseException(bool $active): self;
6262
*/
6363
public function getResponseException(): bool;
6464

65+
/**
66+
* Set Elastic Serverless to true or false
67+
*/
68+
public function setServerless(bool $value): self;
69+
70+
/**
71+
* Returns true if the client is set (or connected) to Serverless
72+
*/
73+
public function getServerless(): bool;
74+
6575
/**
6676
* Send the HTTP request using the Elastic Transport.
6777
* It manages syncronous and asyncronus requests using Client::getAsync()

src/Response/Elasticsearch.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
use ArrayAccess;
1818
use DateTime;
19+
use Elastic\Elasticsearch\Client;
1920
use Elastic\Elasticsearch\Exception\ArrayAccessException;
2021
use Elastic\Elasticsearch\Exception\ClientResponseException;
2122
use Elastic\Elasticsearch\Exception\ServerResponseException;
@@ -44,6 +45,7 @@ class Elasticsearch implements ElasticsearchInterface, ResponseInterface, ArrayA
4445
protected array $asArray;
4546
protected object $asObject;
4647
protected string $asString;
48+
protected bool $serverless = false;
4749

4850
/**
4951
* The PSR-7 response
@@ -62,6 +64,8 @@ class Elasticsearch implements ElasticsearchInterface, ResponseInterface, ArrayA
6264
public function setResponse(ResponseInterface $response, bool $throwException = true): void
6365
{
6466
$this->productCheck($response);
67+
// Check for Serverless response
68+
$this->serverless = $this->isServerlessResponse($response);
6569
$this->response = $response;
6670
$status = $response->getStatusCode();
6771
if ($throwException && $status > 399 && $status < 500) {
@@ -79,6 +83,22 @@ public function setResponse(ResponseInterface $response, bool $throwException =
7983
}
8084
}
8185

86+
/**
87+
* Check whether the response is from Serverless
88+
*/
89+
private function isServerlessResponse(ResponseInterface $response): bool
90+
{
91+
return !empty($response->getHeader(Client::API_VERSION_HEADER));
92+
}
93+
94+
/**
95+
* Return true if the response is from Serverless
96+
*/
97+
public function isServerless(): bool
98+
{
99+
return $this->serverless;
100+
}
101+
82102
/**
83103
* Return true if status code is 2xx
84104
*/

src/Traits/EndpointTrait.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@
1515
namespace Elastic\Elasticsearch\Traits;
1616

1717
use Elastic\Elasticsearch\Client;
18+
use Elastic\Elasticsearch\ClientInterface;
1819
use Elastic\Elasticsearch\Exception\ContentTypeException;
1920
use Elastic\Elasticsearch\Exception\MissingParameterException;
20-
use Elastic\Elasticsearch\Utility;
2121
use Elastic\Transport\OpenTelemetry;
2222
use Elastic\Transport\Serializer\JsonSerializer;
2323
use Elastic\Transport\Serializer\NDJsonSerializer;
2424
use Http\Discovery\Psr17FactoryDiscovery;
2525
use Psr\Http\Message\ServerRequestInterface;
2626

2727
use function http_build_query;
28+
use function rawurlencode;
2829
use function strpos;
2930
use function sprintf;
3031

@@ -81,7 +82,7 @@ protected function convertValue($value): string
8182
*/
8283
protected function encode($value): string
8384
{
84-
return Utility::urlencode($this->convertValue($value));
85+
return rawurlencode($this->convertValue($value));
8586
}
8687

8788
/**
@@ -146,7 +147,13 @@ protected function createRequest(string $method, string $url, array $headers, $b
146147
$content = is_string($body) ? $body : $this->bodySerialize($body, $headers['Content-Type']);
147148
$request = $request->withBody($streamFactory->createStream($content));
148149
}
149-
$headers = $this->buildCompatibilityHeaders($headers);
150+
151+
$client = $this->client ?? $this;
152+
if ($client instanceof ClientInterface && $client->getServerless()) {
153+
$headers[Client::API_VERSION_HEADER] = Client::API_VERSION;
154+
} else {
155+
$headers = $this->buildCompatibilityHeaders($headers);
156+
}
150157

151158
// Headers
152159
foreach ($headers as $name => $value) {

src/Transport/AsyncOnSuccess.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,24 @@
1414

1515
namespace Elastic\Elasticsearch\Transport;
1616

17+
use Elastic\Elasticsearch\ClientInterface;
1718
use Elastic\Elasticsearch\Response\Elasticsearch;
1819
use Elastic\Transport\Async\OnSuccessInterface;
1920
use Psr\Http\Message\ResponseInterface;
2021

2122
class AsyncOnSuccess implements OnSuccessInterface
2223
{
24+
public function __construct(protected ?ClientInterface $client = null)
25+
{
26+
}
27+
2328
public function success(ResponseInterface $response, int $count): Elasticsearch
2429
{
2530
$result = new Elasticsearch;
2631
$result->setResponse($response, true);
32+
if (isset($this->client)) {
33+
$this->client->setServerless($result->isServerless());
34+
}
2735
return $result;
2836
}
2937
}

src/Transport/AsyncOnSuccessNoException.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,24 @@
1414

1515
namespace Elastic\Elasticsearch\Transport;
1616

17+
use Elastic\Elasticsearch\ClientInterface;
1718
use Elastic\Elasticsearch\Response\Elasticsearch;
1819
use Elastic\Transport\Async\OnSuccessInterface;
1920
use Psr\Http\Message\ResponseInterface;
2021

2122
class AsyncOnSuccessNoException implements OnSuccessInterface
2223
{
24+
public function __construct(protected ?ClientInterface $client = null)
25+
{
26+
}
27+
2328
public function success(ResponseInterface $response, int $count): Elasticsearch
2429
{
2530
$result = new Elasticsearch;
2631
$result->setResponse($response, false);
32+
if (isset($this->client)) {
33+
$this->client->setServerless($result->isServerless());
34+
}
2735
return $result;
2836
}
2937
}

src/Utility.php

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
class Utility
1818
{
19-
const ENV_URL_PLUS_AS_SPACE = 'ELASTIC_CLIENT_URL_PLUS_AS_SPACE';
20-
2119
/**
2220
* Get the ENV variable with a thread safe fallback criteria
2321
* @see https://github.com/elastic/elasticsearch-php/issues/1237
@@ -29,23 +27,6 @@ public static function getEnv(string $env)
2927
return $_SERVER[$env] ?? $_ENV[$env] ?? getenv($env);
3028
}
3129

32-
/**
33-
* Encode a string in URL using urlencode() or rawurlencode()
34-
* according to ENV_URL_PLUS_AS_SPACE.
35-
* If ENV_URL_PLUS_AS_SPACE is not defined or true use urlencode(),
36-
* otherwise use rawurlencode()
37-
*
38-
* @see https://github.com/elastic/elasticsearch-php/issues/1278
39-
* @deprecated will be replaced by PHP function rawurlencode()
40-
*/
41-
public static function urlencode(string $url): string
42-
{
43-
$plusAsSpace = self::getEnv(self::ENV_URL_PLUS_AS_SPACE);
44-
return $plusAsSpace === false || $plusAsSpace === 'true'
45-
? urlencode($url)
46-
: rawurlencode($url);
47-
}
48-
4930
/**
5031
* Remove all the characters not valid for a PHP variable name
5132
* The valid characters are: a-z, A-Z, 0-9 and _ (underscore)

tests/ClientTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,57 @@ public function testSendRequestWithAsyncWillReturnElasticsearch()
193193
$this->assertInstanceOf(Promise::class, $result);
194194
$this->assertInstanceOf(Elasticsearch::class, $result->wait());
195195
}
196+
197+
public function testSetServerless()
198+
{
199+
$this->client->setServerless(true);
200+
$this->assertTrue($this->client->getServerless());
201+
}
202+
203+
public function testServerlessIsDefaultFalse()
204+
{
205+
$this->assertFalse($this->client->getServerless());
206+
}
207+
208+
public function testIsServerlessTrueWhenReponseFromServerless()
209+
{
210+
$request = $this->psr17Factory->createRequest('GET', 'localhost:9200');
211+
$response = $this->psr17Factory->createResponse(200)
212+
->withHeader('X-Elastic-Product', 'Elasticsearch')
213+
->withHeader(Client::API_VERSION_HEADER, Client::API_VERSION);
214+
$this->httpClient->addResponse($response);
215+
216+
$result = $this->client->sendRequest($request);
217+
$this->assertTrue($this->client->getServerless());
218+
}
219+
220+
public function testIsServerlessTrueWhenReponseFromServerlessWithAsyncOnSuccess()
221+
{
222+
$request = $this->psr17Factory->createRequest('GET', 'localhost:9200');
223+
$response = $this->psr17Factory->createResponse(200)
224+
->withHeader('X-Elastic-Product', 'Elasticsearch')
225+
->withHeader(Client::API_VERSION_HEADER, Client::API_VERSION);
226+
$this->httpClient->addResponse($response);
227+
228+
$this->client->setAsync(true);
229+
$result = $this->client->sendRequest($request);
230+
$this->assertInstanceOf(Promise::class, $result);
231+
$result->wait();
232+
$this->assertTrue($this->client->getServerless());
233+
}
234+
235+
public function testIsServerlessTrueWhenReponseFromServerlessWithAsyncOnSuccessNoException()
236+
{
237+
$request = $this->psr17Factory->createRequest('HEAD', 'localhost:9200');
238+
$response = $this->psr17Factory->createResponse(200)
239+
->withHeader('X-Elastic-Product', 'Elasticsearch')
240+
->withHeader(Client::API_VERSION_HEADER, Client::API_VERSION);
241+
$this->httpClient->addResponse($response);
242+
243+
$this->client->setAsync(true);
244+
$result = $this->client->sendRequest($request);
245+
$this->assertInstanceOf(Promise::class, $result);
246+
$result->wait();
247+
$this->assertTrue($this->client->getServerless());
248+
}
196249
}

0 commit comments

Comments
 (0)