diff --git a/composer.json b/composer.json index 9ef06fb55..8e8dbdee1 100644 --- a/composer.json +++ b/composer.json @@ -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" diff --git a/src/Compressors/Compressor.php b/src/Compressors/Compressor.php new file mode 100644 index 000000000..785d9cdc5 --- /dev/null +++ b/src/Compressors/Compressor.php @@ -0,0 +1,21 @@ +addHeader('Content-Encoding', 'gzip'); + $requestOptions->addHeader('Content-Length', strlen($compressedBody)); + + return $compressedBody; + } +} diff --git a/src/Compressors/NullCompressor.php b/src/Compressors/NullCompressor.php new file mode 100644 index 000000000..7f9293e83 --- /dev/null +++ b/src/Compressors/NullCompressor.php @@ -0,0 +1,16 @@ + $this->defaultWriteTimeout, 'connectTimeout' => $this->defaultConnectTimeout, 'defaultHeaders' => array(), + 'compressionType' => self::COMPRESSION_TYPE_NONE, ); } @@ -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; + } } diff --git a/src/Config/SearchConfig.php b/src/Config/SearchConfig.php index 516cec589..e4cbb2583 100644 --- a/src/Config/SearchConfig.php +++ b/src/Config/SearchConfig.php @@ -29,6 +29,7 @@ public function getDefaultConfig() 'defaultHeaders' => array(), 'defaultForwardToReplicas' => null, 'batchSize' => 1000, + 'compressionType' => self::COMPRESSION_TYPE_GZIP, ); } diff --git a/src/RetryStrategy/ApiWrapper.php b/src/RetryStrategy/ApiWrapper.php index 269129485..ce1eb49f1 100644 --- a/src/RetryStrategy/ApiWrapper.php +++ b/src/RetryStrategy/ApiWrapper.php @@ -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; @@ -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, @@ -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()) @@ -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, @@ -125,6 +139,7 @@ private function request($method, $path, RequestOptions $requestOptions, $hosts, ); $retry = 1; + foreach ($hosts as $host) { $uri = $uri->withHost($host); $request = null; @@ -132,12 +147,7 @@ private function request($method, $path, RequestOptions $requestOptions, $hosts, $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); @@ -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' - ) { + /** + * @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 { @@ -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; } } diff --git a/tests/Integration/IndexingTest.php b/tests/Integration/IndexingTest.php index d623f6071..2f0dd0075 100644 --- a/tests/Integration/IndexingTest.php +++ b/tests/Integration/IndexingTest.php @@ -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]; diff --git a/tests/Unit/CopyResourcesTest.php b/tests/Unit/CopyResourcesTest.php index f6018a444..27f75eb42 100644 --- a/tests/Unit/CopyResourcesTest.php +++ b/tests/Unit/CopyResourcesTest.php @@ -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'), @@ -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'), @@ -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'), diff --git a/tests/Unit/RequestTestCase.php b/tests/Unit/RequestTestCase.php index 0c48ed4fb..cac259543 100644 --- a/tests/Unit/RequestTestCase.php +++ b/tests/Unit/RequestTestCase.php @@ -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); @@ -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(); diff --git a/tests/Unit/SearchTest.php b/tests/Unit/SearchTest.php index 743e73895..b10dae260 100644 --- a/tests/Unit/SearchTest.php +++ b/tests/Unit/SearchTest.php @@ -15,7 +15,10 @@ public function testQueryAsNullValue() try { $client->searchUserIds(null); } catch (RequestException $e) { - $this->assertBodySubset(array('query' => ''), $e->getRequest()); + $this->assertHeaderIsSet('Content-Encoding', $e->getRequest()); + $this->assertHeaderIsSet('Content-Length', $e->getRequest()); + $this->assertEncodedBodySubset(array('query' => ''), + $e->getRequest()); } $index = $client->initIndex('foo'); @@ -23,25 +26,37 @@ public function testQueryAsNullValue() try { $index->search(null); } catch (RequestException $e) { - $this->assertBodySubset(array('query' => ''), $e->getRequest()); + $this->assertHeaderIsSet('Content-Encoding', $e->getRequest()); + $this->assertHeaderIsSet('Content-Length', $e->getRequest()); + $this->assertEncodedBodySubset(array('query' => ''), + $e->getRequest()); } try { $index->searchSynonyms(null); } catch (RequestException $e) { - $this->assertBodySubset(array('query' => ''), $e->getRequest()); + $this->assertHeaderIsSet('Content-Encoding', $e->getRequest()); + $this->assertHeaderIsSet('Content-Length', $e->getRequest()); + $this->assertEncodedBodySubset(array('query' => ''), + $e->getRequest()); } try { $index->searchRules(null); } catch (RequestException $e) { - $this->assertBodySubset(array('query' => ''), $e->getRequest()); + $this->assertHeaderIsSet('Content-Encoding', $e->getRequest()); + $this->assertHeaderIsSet('Content-Length', $e->getRequest()); + $this->assertEncodedBodySubset(array('query' => ''), + $e->getRequest()); } try { $index->searchRules(null); } catch (RequestException $e) { - $this->assertBodySubset(array('query' => ''), $e->getRequest()); + $this->assertHeaderIsSet('Content-Encoding', $e->getRequest()); + $this->assertHeaderIsSet('Content-Length', $e->getRequest()); + $this->assertEncodedBodySubset(array('query' => ''), + $e->getRequest()); } $client = PlacesClient::create('id', 'key'); @@ -49,6 +64,8 @@ public function testQueryAsNullValue() try { $client->search(null); } catch (RequestException $e) { + $this->assertHeaderIsNotSet('Content-Encoding', $e->getRequest()); + $this->assertHeaderIsNotSet('Content-Length', $e->getRequest()); $this->assertBodySubset(array('query' => ''), $e->getRequest()); } }