Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addition of gzip feature #568

Open
wants to merge 3 commits into
base: v3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-zlib": "*",
"psr/http-message": "^1.0",
"psr/log": "^1.0",
"psr/simple-cache": "^1.0"
Expand Down
21 changes: 21 additions & 0 deletions src/Compressors/Compressor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Algolia\AlgoliaSearch\Compressors;

use Algolia\AlgoliaSearch\RequestOptions\RequestOptions;

/**
* @internal
*/
interface Compressor
{
/**
* Mutates the given `$requestOptions` object and returns the encoded body.
*
* @param \Algolia\AlgoliaSearch\RequestOptions\RequestOptions $requestOptions
* @param string $body
*
* @return string
*/
public function compress(RequestOptions $requestOptions, $body);
}
29 changes: 29 additions & 0 deletions src/Compressors/CompressorFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Algolia\AlgoliaSearch\Compressors;

use Algolia\AlgoliaSearch\Config\AbstractConfig;
use Doctrine\Instantiator\Exception\InvalidArgumentException;

/**
* @internal
*/
final class CompressorFactory
{
/**
* @param string $type
*
* @return \Algolia\AlgoliaSearch\Compressors\Compressor
*/
public static function create($type)
{
switch ($type) {
case AbstractConfig::COMPRESSION_TYPE_NONE:
return new NullCompressor();
case AbstractConfig::COMPRESSION_TYPE_GZIP:
return new GzipCompressor();
default:
throw new InvalidArgumentException('Compression type not supported');
}
}
}
21 changes: 21 additions & 0 deletions src/Compressors/GzipCompressor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Algolia\AlgoliaSearch\Compressors;

use Algolia\AlgoliaSearch\RequestOptions\RequestOptions;

/**
* @internal
*/
final class GzipCompressor implements Compressor
{
public function compress(RequestOptions $requestOptions, $body)
{
$compressedBody = gzencode($body, 9);

$requestOptions->addHeader('Content-Encoding', 'gzip');
$requestOptions->addHeader('Content-Length', strlen($compressedBody));

return $compressedBody;
}
}
16 changes: 16 additions & 0 deletions src/Compressors/NullCompressor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Algolia\AlgoliaSearch\Compressors;

use Algolia\AlgoliaSearch\RequestOptions\RequestOptions;

/**
* @internal
*/
final class NullCompressor implements Compressor
{
public function compress(RequestOptions $requestOptions, $body)
{
return $body;
}
}
32 changes: 32 additions & 0 deletions src/Config/AbstractConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

abstract class AbstractConfig
{
const COMPRESSION_TYPE_NONE = 'none';
const COMPRESSION_TYPE_GZIP = 'gzip';

protected $config;

protected $defaultReadTimeout = 5;
Expand All @@ -29,6 +32,7 @@ public function getDefaultConfig()
'writeTimeout' => $this->defaultWriteTimeout,
'connectTimeout' => $this->defaultConnectTimeout,
'defaultHeaders' => array(),
'compressionType' => self::COMPRESSION_TYPE_NONE,
);
}

Expand Down Expand Up @@ -115,4 +119,32 @@ public function setDefaultHeaders(array $defaultHeaders)

return $this;
}

/**
* @return string
*/
public function getCompressionType()
{
return $this->config['compressionType'];
}

/**
* @param string $compressionType
*
* @return $this
*/
public function setCompressionType($compressionType)
{
if (!in_array(
$compressionType,
array(self::COMPRESSION_TYPE_GZIP, self::COMPRESSION_TYPE_NONE),
true
)) {
throw new \InvalidArgumentException('Compression type not supported');
}

$this->config['compressionType'] = $compressionType;

return $this;
}
}
1 change: 1 addition & 0 deletions src/Config/SearchConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function getDefaultConfig()
'defaultHeaders' => array(),
'defaultForwardToReplicas' => null,
'batchSize' => 1000,
'compressionType' => self::COMPRESSION_TYPE_GZIP,
);
}

Expand Down
62 changes: 35 additions & 27 deletions src/RetryStrategy/ApiWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Algolia\AlgoliaSearch\RetryStrategy;

use Algolia\AlgoliaSearch\Algolia;
use Algolia\AlgoliaSearch\Compressors\CompressorFactory;
use Algolia\AlgoliaSearch\Config\AbstractConfig;
use Algolia\AlgoliaSearch\Exceptions\AlgoliaException;
use Algolia\AlgoliaSearch\Exceptions\BadRequestException;
Expand Down Expand Up @@ -42,6 +43,11 @@ final class ApiWrapper implements ApiWrapperInterface
*/
private $requestOptionsFactory;

/**
* @var \Algolia\AlgoliaSearch\Compressors\Compressor
*/
private $compressor;

public function __construct(
HttpClientInterface $http,
AbstractConfig $config,
Expand All @@ -52,6 +58,8 @@ public function __construct(
$this->config = $config;
$this->clusterHosts = $clusterHosts;
$this->requestOptionsFactory = $RqstOptsFactory ?: new RequestOptionsFactory($config);

$this->compressor = CompressorFactory::create($this->config->getCompressionType());
}

public function read($method, $path, $requestOptions = array(), $defaultRequestOptions = array())
Expand Down Expand Up @@ -115,7 +123,13 @@ private function request($method, $path, RequestOptions $requestOptions, $hosts,
->withQuery($requestOptions->getBuiltQueryParameters())
->withScheme('https');

$body = array_merge($data, $requestOptions->getBody());
$body = $this->sanitizeBody(array_merge($data, $requestOptions->getBody()));

if ('POST' === strtoupper($method) || 'PUT' === strtoupper($method)) {
$compressedBody = $this->compressor->compress($requestOptions, $body);
} else {
$compressedBody = $body;
}

$logParams = array(
'body' => $body,
Expand All @@ -125,19 +139,15 @@ private function request($method, $path, RequestOptions $requestOptions, $hosts,
);

$retry = 1;

foreach ($hosts as $host) {
$uri = $uri->withHost($host);
$request = null;
$logParams['retryNumber'] = $retry;
$logParams['host'] = (string) $uri;

try {
$request = $this->createRequest(
$method,
$uri,
$requestOptions->getHeaders(),
$body
);
$request = new Request($method, $uri, $requestOptions->getHeaders(), $compressedBody, '1.1');

$this->log(LogLevel::DEBUG, 'Sending request.', $logParams);

Expand Down Expand Up @@ -215,16 +225,24 @@ private function createUri($uri)
throw new \InvalidArgumentException('URI must be a string or UriInterface');
}

private function createRequest(
$method,
$uri,
array $headers = array(),
$body = null,
$protocolVersion = '1.1'
) {
/**
chloelbn marked this conversation as resolved.
Show resolved Hide resolved
* @param string $level
* @param string $message
* @param array $context
*/
private function log($level, $message, array $context = array())
{
Algolia::getLogger()->log($level, 'Algolia API client: '.$message, $context);
}

/**
* @param string $body
*
* @return string
*/
private function sanitizeBody($body)
{
if (is_array($body)) {
// Send an empty body instead of "[]" in case there are
// no content/params to send
if (empty($body)) {
$body = '';
} else {
Expand All @@ -236,16 +254,6 @@ private function createRequest(
}
}

return new Request($method, $uri, $headers, $body, $protocolVersion);
}

/**
* @param string $level
* @param string $message
* @param array $context
*/
private function log($level, $message, array $context = array())
{
Algolia::getLogger()->log($level, 'Algolia API client: '.$message, $context);
return $body;
}
}
1 change: 0 additions & 1 deletion tests/Integration/IndexingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ public function testIndexing()
$multiResponse->wait();

/* Check 6 first records with getObject */

$objectID1 = $responses[0][0]['objectIDs'][0];
$objectID2 = $responses[1][0]['objectIDs'][0];
$objectID3 = $responses[2][0]['objectIDs'][0];
Expand Down
15 changes: 12 additions & 3 deletions tests/Unit/CopyResourcesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ public function testCopySettings()
static::$client->copySettings('src', 'dest');
} catch (RequestException $e) {
$this->assertEndpointEquals($e->getRequest(), '/1/indexes/src/operation');
$this->assertBodySubset(array(
$this->assertHeaderIsSet('Content-Encoding', $e->getRequest());
$this->assertHeaderIsSet('Content-Length', $e->getRequest());
$this->assertBodyEncoded($e->getRequest());
$this->assertEncodedBodySubset(array(
'operation' => 'copy',
'destination' => 'dest',
'scope' => array('settings'),
Expand All @@ -38,7 +41,10 @@ public function testCopySynonyms()
static::$client->copySynonyms('src', 'dest');
} catch (RequestException $e) {
$this->assertEndpointEquals($e->getRequest(), '/1/indexes/src/operation');
$this->assertBodySubset(array(
$this->assertHeaderIsSet('Content-Encoding', $e->getRequest());
$this->assertHeaderIsSet('Content-Length', $e->getRequest());
$this->assertBodyEncoded($e->getRequest());
$this->assertEncodedBodySubset(array(
'operation' => 'copy',
'destination' => 'dest',
'scope' => array('synonyms'),
Expand All @@ -54,7 +60,10 @@ public function testCopyRules()
static::$client->copyRules('src', 'dest');
} catch (RequestException $e) {
$this->assertEndpointEquals($e->getRequest(), '/1/indexes/src/operation');
$this->assertBodySubset(array(
$this->assertHeaderIsSet('Content-Encoding', $e->getRequest());
$this->assertHeaderIsSet('Content-Length', $e->getRequest());
$this->assertBodyEncoded($e->getRequest());
$this->assertEncodedBodySubset(array(
'operation' => 'copy',
'destination' => 'dest',
'scope' => array('rules'),
Expand Down
25 changes: 25 additions & 0 deletions tests/Unit/RequestTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ protected function assertBodySubset($subset, RequestInterface $request)
$this->assertArraySubset($subset, $body, true);
}

protected function assertEncodedBodySubset(
$subset,
RequestInterface $request
) {
$body = json_decode(gzdecode($request->getBody()), true);
$this->assertArraySubset($subset, $body, true);
}

protected function assertQueryParametersSubset(array $subset, RequestInterface $request)
{
$params = $this->requestQueryParametersToArray($request);
Expand All @@ -44,6 +52,23 @@ protected function assertQueryParametersNotHasKey($key, RequestInterface $reques
$this->assertArrayNotHasKey($key, $params);
}

protected function assertBodyEncoded(RequestInterface $request)
{
return gzdecode($request->getBody());
}

protected function assertHeaderIsSet($headerName, RequestInterface $request)
{
$this->assertArrayHasKey($headerName, $request->getHeaders());
}

protected function assertHeaderIsNotSet(
$headerName,
RequestInterface $request
) {
$this->assertArrayNotHasKey($headerName, $request->getHeaders());
}

private function requestQueryParametersToArray(RequestInterface $request)
{
$array = array();
Expand Down
Loading