Skip to content

Commit ba64c54

Browse files
committed
Add Symfony HTTP client adapter and remove Accept-Encoding header (gzip) for Symfony (#1243)
* Remove the gzip Accept-Encoding header for Symfony HTTP client * Add the support of symfony http-client v5 * Added Symfony options adapter + use Guzzle as default HTTP client * Reverted default HTTP client + fixed setOptions
1 parent 6c6124e commit ba64c54

File tree

12 files changed

+381
-25
lines changed

12 files changed

+381
-25
lines changed

composer.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"phpunit/phpunit": "^9.5",
2626
"symfony/finder": "~4.0",
2727
"nyholm/psr7": "^1.5",
28-
"php-http/mock-client": "^1.5"
28+
"php-http/mock-client": "^1.5",
29+
"symfony/http-client": "^5.0|^6.0"
2930
},
3031
"autoload": {
3132
"psr-4": {
@@ -45,6 +46,9 @@
4546
"integration-test" : [
4647
"vendor/bin/phpunit --testdox -c phpunit-integration-tests.xml"
4748
],
49+
"cloud-test" : [
50+
"vendor/bin/phpunit --testdox -c phpunit-integration-cloud-tests.xml"
51+
],
4852
"phpstan": [
4953
"phpstan analyse src --level 2 --no-progress"
5054
]

phpunit-integration-cloud-tests.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" failOnRisky="true" verbose="true" beStrictAboutChangesToGlobalState="true" beStrictAboutOutputDuringTests="true" beStrictAboutTestsThatDoNotTestAnything="false">
3+
<coverage>
4+
<include>
5+
<directory suffix=".php">src</directory>
6+
</include>
7+
</coverage>
8+
<php>
9+
<!-- Disable E_USER_DEPRECATED setting E_ALL & ~E_USER_DEPRECATED-->
10+
<ini name="error_reporting" value="16383"/>
11+
<ini name="memory_limit" value="-1"/>
12+
</php>
13+
<testsuites>
14+
<testsuite name="Elastic Cloud tests">
15+
<directory>tests/Integration</directory>
16+
</testsuite>
17+
</testsuites>
18+
<groups>
19+
<include>
20+
<group>cloud</group>
21+
</include>
22+
</groups>
23+
<logging>
24+
<junit outputFile="tests/yaml-test-junit.xml"/>
25+
</logging>
26+
</phpunit>

phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<group>integration</group>
1616
<group>free</group>
1717
<group>platinum</group>
18+
<group>cloud</group>
1819
</exclude>
1920
</groups>
2021
</phpunit>

src/ClientBuilder.php

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
use Elastic\Elasticsearch\Transport\Adapter\AdapterOptions;
2323
use Elastic\Elasticsearch\Transport\RequestOptions;
2424
use Elastic\Transport\NodePool\NodePoolInterface;
25+
use Elastic\Transport\Transport;
2526
use Elastic\Transport\TransportBuilder;
27+
use GuzzleHttp\Client as GuzzleHttpClient;
2628
use Http\Client\HttpAsyncClient;
2729
use Psr\Http\Client\ClientInterface;
2830
use Psr\Log\LoggerInterface;
@@ -344,14 +346,11 @@ public function build(): Client
344346
// Http client
345347
if (!empty($this->httpClient)) {
346348
$builder->setClient($this->httpClient);
347-
} else {
348-
// Set HTTP client options
349-
if (!empty($this->getConfig()) || !empty($this->httpClientOptions)) {
350-
$builder->setClient(
351-
$this->setOptions($builder->getClient(), $this->getConfig(), $this->httpClientOptions)
352-
);
353-
}
354349
}
350+
// Set HTTP client options
351+
$builder->setClient(
352+
$this->setOptions($builder->getClient(), $this->getConfig(), $this->httpClientOptions)
353+
);
355354

356355
// Cloud id
357356
if (!empty($this->cloudId)) {
@@ -389,8 +388,11 @@ public function build(): Client
389388
$transport->setHeader('Authorization', sprintf("ApiKey %s", $this->apiKey));
390389
}
391390

392-
// Elastic cloud optimized with gzip
393-
if (!empty($this->cloudId)) {
391+
/**
392+
* Elastic cloud optimized with gzip
393+
* @see https://github.com/elastic/elasticsearch-php/issues/1241 omit for Symfony HTTP Client
394+
*/
395+
if (!empty($this->cloudId) && !$this->isSymfonyHttpClient($transport)) {
394396
$transport->setHeader('Accept-Encoding', 'gzip');
395397
}
396398

@@ -401,6 +403,20 @@ public function build(): Client
401403
return $client;
402404
}
403405

406+
/**
407+
* Returns true if the transport HTTP client is Symfony
408+
*/
409+
protected function isSymfonyHttpClient(Transport $transport): bool
410+
{
411+
if (false !== strpos(get_class($transport->getClient()), 'Symfony\Component\HttpClient')) {
412+
return true;
413+
}
414+
if (false !== strpos(get_class($transport->getAsyncClient()), 'Symfony\Component\HttpClient')) {
415+
return true;
416+
}
417+
return false;
418+
}
419+
404420
/**
405421
* Returns the configuration to be used in the HTTP client
406422
*/

src/Transport/Adapter/AdapterOptions.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
final class AdapterOptions
2121
{
2222
const HTTP_ADAPTERS = [
23-
"GuzzleHttp\\Client" => "Elastic\\Elasticsearch\\Transport\\Adapter\\Guzzle"
23+
"GuzzleHttp\\Client" => "Elastic\\Elasticsearch\\Transport\\Adapter\\Guzzle",
24+
"Symfony\\Component\\HttpClient\\HttplugClient" => "Elastic\\Elasticsearch\\Transport\\Adapter\\Symfony",
25+
"Symfony\\Component\\HttpClient\\Psr18Client" => "Elastic\\Elasticsearch\\Transport\\Adapter\\Symfony"
2426
];
2527
}

src/Transport/Adapter/Guzzle.php

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@
1414

1515
namespace Elastic\Elasticsearch\Transport\Adapter;
1616

17-
use Elastic\Elasticsearch\Exception\HttpClientException;
18-
use Elastic\Elasticsearch\Transport\RequestOptions as Options;
19-
use GuzzleHttp\Client;
20-
use GuzzleHttp\RequestOptions;
17+
use Elastic\Elasticsearch\Transport\RequestOptions;
18+
use GuzzleHttp\RequestOptions As GuzzleOptions;
2119
use Psr\Http\Client\ClientInterface;
2220

2321
class Guzzle implements AdapterInterface
@@ -27,17 +25,17 @@ public function setConfig(ClientInterface $client, array $config, array $clientO
2725
$guzzleConfig = [];
2826
foreach ($config as $key => $value) {
2927
switch ($key) {
30-
case Options::SSL_CERT:
31-
$guzzleConfig[RequestOptions::CERT] = $value;
28+
case RequestOptions::SSL_CERT:
29+
$guzzleConfig[GuzzleOptions::CERT] = $value;
3230
break;
33-
case Options::SSL_KEY:
34-
$guzzleConfig[RequestOptions::SSL_KEY] = $value;
31+
case RequestOptions::SSL_KEY:
32+
$guzzleConfig[GuzzleOptions::SSL_KEY] = $value;
3533
break;
36-
case Options::SSL_VERIFY:
37-
$guzzleConfig[RequestOptions::VERIFY] = $value;
34+
case RequestOptions::SSL_VERIFY:
35+
$guzzleConfig[GuzzleOptions::VERIFY] = $value;
3836
break;
39-
case Options::SSL_CA:
40-
$guzzleConfig[RequestOptions::VERIFY] = $value;
37+
case RequestOptions::SSL_CA:
38+
$guzzleConfig[GuzzleOptions::VERIFY] = $value;
4139
}
4240
}
4341
$class = get_class($client);

src/Transport/Adapter/Symfony.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
/**
3+
* Elasticsearch PHP Client
4+
*
5+
* @link https://github.com/elastic/elasticsearch-php
6+
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
7+
* @license https://opensource.org/licenses/MIT MIT License
8+
*
9+
* Licensed to Elasticsearch B.V under one or more agreements.
10+
* Elasticsearch B.V licenses this file to you under the MIT License.
11+
* See the LICENSE file in the project root for more information.
12+
*/
13+
declare(strict_types = 1);
14+
15+
namespace Elastic\Elasticsearch\Transport\Adapter;
16+
17+
use Elastic\Elasticsearch\Transport\RequestOptions;
18+
use Psr\Http\Client\ClientInterface;
19+
use Symfony\Component\HttpClient\HttpClient;
20+
21+
class Symfony implements AdapterInterface
22+
{
23+
public function setConfig(ClientInterface $client, array $config, array $clientOptions): ClientInterface
24+
{
25+
$symfonyConfig = [];
26+
foreach ($config as $key => $value) {
27+
switch ($key) {
28+
case RequestOptions::SSL_CERT:
29+
$symfonyConfig['local_cert'] = $value;
30+
break;
31+
case RequestOptions::SSL_KEY:
32+
$symfonyConfig['local_pk'] = $value;
33+
break;
34+
case RequestOptions::SSL_VERIFY:
35+
$symfonyConfig['verify_host'] = $value;
36+
$symfonyConfig['verify_peer'] = $value;
37+
break;
38+
case RequestOptions::SSL_CA:
39+
$symfonyConfig['cafile'] = $value;
40+
}
41+
}
42+
$class = get_class($client);
43+
$httpClient = HttpClient::create(array_merge($clientOptions, $symfonyConfig));
44+
return new $class($httpClient);
45+
}
46+
}

tests/ClientBuilderTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
use PHPUnit\Framework\TestCase;
2626
use Psr\Http\Client\ClientInterface;
2727
use Psr\Log\LoggerInterface;
28+
use Symfony\Component\HttpClient\Psr18Client;
29+
use Symfony\Component\HttpClient\HttplugClient;
2830

2931
class ClientBuilderTest extends TestCase
3032
{
@@ -331,4 +333,37 @@ public function testSetHttpClientOptions()
331333
$result = $this->builder->setHttpClientOptions([]);
332334
$this->assertEquals($this->builder, $result);
333335
}
336+
337+
public function testClientWithSymfonyPsr18Client()
338+
{
339+
$symfonyClient = new Psr18Client();
340+
$client = ClientBuilder::create()
341+
->setHttpClient($symfonyClient)
342+
->build();
343+
344+
$this->assertInstanceOf(Client::class, $client);
345+
$this->assertEquals($symfonyClient, $client->getTransport()->getClient());
346+
}
347+
348+
public function testClientWithSymfonyHttplugClient()
349+
{
350+
$symfonyClient = new HttplugClient();
351+
$client = ClientBuilder::create()
352+
->setHttpClient($symfonyClient)
353+
->build();
354+
355+
$this->assertInstanceOf(Client::class, $client);
356+
$this->assertEquals($symfonyClient, $client->getTransport()->getClient());
357+
}
358+
359+
public function testAsyncClientWithSymfonyHttplugClient()
360+
{
361+
$symfonyClient = new HttplugClient();
362+
$client = ClientBuilder::create()
363+
->setAsyncHttpClient($symfonyClient)
364+
->build();
365+
366+
$this->assertInstanceOf(Client::class, $client);
367+
$this->assertEquals($symfonyClient, $client->getTransport()->getAsyncClient());
368+
}
334369
}

tests/Integration/BasicTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414

1515
namespace Elastic\Elasticsearch\Tests\Integration;
1616

17-
use Elastic\Elasticsearch\Exception\ClientResponseException;
1817
use Elastic\Elasticsearch\Tests\Utility;
19-
use Elastic\Transport\Exception\NoNodeAvailableException;
2018
use PHPUnit\Framework\TestCase;
2119

2220
/**
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
/**
3+
* Elasticsearch PHP Client
4+
*
5+
* @link https://github.com/elastic/elasticsearch-php
6+
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
7+
* @license https://opensource.org/licenses/MIT MIT License
8+
*
9+
* Licensed to Elasticsearch B.V under one or more agreements.
10+
* Elasticsearch B.V licenses this file to you under the MIT License.
11+
* See the LICENSE file in the project root for more information.
12+
*/
13+
declare(strict_types = 1);
14+
15+
namespace Elastic\Elasticsearch\Tests\Integration;
16+
17+
use Elastic\Elasticsearch\ClientBuilder;
18+
use Http\Promise\Promise;
19+
use PHPUnit\Framework\TestCase;
20+
use Symfony\Component\HttpClient\Psr18Client;
21+
use Symfony\Component\HttpClient\HttplugClient;
22+
23+
/**
24+
* @group cloud
25+
*/
26+
class ElasticCloudTest extends TestCase
27+
{
28+
const CLOUD_ID = 'ELASTIC_CLOUD_ID';
29+
const API_KEY = 'ELASTIC_API_KEY';
30+
31+
protected ClientBuilder $clientBuilder;
32+
33+
public function setUp(): void
34+
{
35+
if (!getenv(self::CLOUD_ID) && !getenv(self::API_KEY)) {
36+
$this->markTestSkipped(sprintf(
37+
"I cannot execute the Elastic Cloud test without the env variables %s and %s",
38+
self::CLOUD_ID,
39+
self::API_KEY
40+
));
41+
}
42+
$this->clientBuilder = ClientBuilder::create()
43+
->setElasticCloudId(getenv(self::CLOUD_ID))
44+
->setApiKey(getenv(self::API_KEY));
45+
}
46+
47+
public function testInfoWithAsyncSymfonyHttplugClient()
48+
{
49+
$symfonyClient = new HttplugClient();
50+
51+
$client = $this->clientBuilder->setAsyncHttpClient($symfonyClient)
52+
->build();
53+
54+
$client->setAsync(true);
55+
56+
$promise = $client->info();
57+
$this->assertInstanceOf(Promise::class, $promise);
58+
$response = $promise->wait();
59+
60+
$this->assertEquals(200, $response->getStatusCode());
61+
$this->assertNotEmpty($response['name']);
62+
$this->assertNotEmpty($response['version']['number']);
63+
}
64+
65+
public function testInfoWithSymfonyHttpPsr18Client()
66+
{
67+
$symfonyClient = new Psr18Client();
68+
69+
$client = $this->clientBuilder->setHttpClient($symfonyClient)
70+
->build();
71+
72+
$response = $client->info();
73+
$this->assertEquals(200, $response->getStatusCode());
74+
$this->assertNotEmpty($response['name']);
75+
$this->assertNotEmpty($response['version']['number']);
76+
}
77+
}

0 commit comments

Comments
 (0)