Commit 5d3d9f71 authored by chadicus's avatar chadicus

Add Guzzle3 Client Adapter

parent 70383b14
...@@ -19,7 +19,8 @@ ...@@ -19,7 +19,8 @@
"phpunit/phpunit": "~3.7", "phpunit/phpunit": "~3.7",
"squizlabs/php_codesniffer": "~1.4", "squizlabs/php_codesniffer": "~1.4",
"zendframework/zendframework1": "~1.12", "zendframework/zendframework1": "~1.12",
"satooshi/php-coveralls": "~1.0" "satooshi/php-coveralls": "~1.0",
"guzzlehttp/guzzle": "^3.8"
}, },
"suggest": { "suggest": {
"minimalcode/search": "Query builder compatible with Solarium, allows simplified solr-query handling" "minimalcode/search": "Query builder compatible with Solarium, allows simplified solr-query handling"
......
<?php
/**
* Copyright 2011 Bas de Nooijer. All rights reserved.
* * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this listof conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of the copyright holder.
*
* @copyright Copyright 2011 Bas de Nooijer <solarium@raspberry.nl>
* @license http://github.com/basdenooijer/solarium/raw/master/COPYING
*
* @link http://www.solarium-project.org/
*/
/**
* @namespace
*/
namespace Solarium\Core\Client\Adapter;
use Guzzle\Http\Client as GuzzleClient;
use Solarium\Core\Configurable;
use Solarium\Core\Client\Adapter\AdapterInterface;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Client\Endpoint;
use Solarium\Exception\HttpException;
/**
* Guzzle3 HTTP adapter.
*/
class Guzzle3 extends Configurable implements AdapterInterface
{
/**
* The Guzzle HTTP client instance.
*
* @var GuzzleClient
*/
private $guzzleClient;
/**
* Execute a Solr request using the cURL Http.
*
* @param Request $request
* @param Endpoint $endpoint
*
* @return Response
*/
public function execute($request, $endpoint)
{
$guzzleRequest = $this->getGuzzleClient()->createRequest(
$request->getMethod(),
$endpoint->getBaseUri() . $request->getUri(),
$this->getRequestHeaders($request),
$this->getRequestBody($request),
[
'timeout' => $endpoint->getTimeout(),
'connecttimeout' => $endpoint->getTimeout(),
]
);
// Try endpoint authentication first, fallback to request for backwards compatibility
$authData = $endpoint->getAuthentication();
if (empty($authData['username'])) {
$authData = $request->getAuthentication();
}
if (!empty($authData['username']) && !empty($authData['password'])) {
$guzzleRequest->setAuth($authData['username'], $authData['password']);
}
try {
$this->getGuzzleClient()->send($guzzleRequest);
$guzzleResponse = $guzzleRequest->getResponse();
$responseHeaders = array_merge(
["HTTP/1.1 {$guzzleResponse->getStatusCode()} {$guzzleResponse->getReasonPhrase()}"],
$guzzleResponse->getHeaderLines()
);
return new Response($guzzleResponse->getBody(true), $responseHeaders);
} catch (\Guzzle\Http\Exception\RequestException $e) {
$error = $e->getMessage();
if ($e instanceof \Guzzle\Http\Exception\CurlException) {
$error = $e->getError();
}
throw new HttpException("HTTP request failed, {$error}");
}
}
/**
* Gets the Guzzle HTTP client instance.
*
* @return GuzzleClient
*/
public function getGuzzleClient()
{
if ($this->guzzleClient === null) {
$this->guzzleClient = new GuzzleClient(null, $this->options);
}
return $this->guzzleClient;
}
/**
* Helper method to create a request body suitable for a guzzle 3 request.
*
* @param Request $request The incoming solarium request.
*
* @return null|resource|string
*/
private function getRequestBody(Request $request)
{
if ($request->getMethod() !== Request::METHOD_POST) {
return null;
}
if ($request->getFileUpload()) {
return fopen($request->getFileUpload(), 'r');
}
return $request->getRawData();
}
/**
* Helper method to extract headers from the incoming solarium request and put them in a format
* suitable for a guzzle 3 request.
*
* @param Request $request The incoming solarium request.
*
* @return array
*/
private function getRequestHeaders(Request $request)
{
$headers = [];
foreach ($request->getHeaders() as $headerLine) {
list($header, $value) = explode(':', $headerLine);
if ($header = trim($header)) {
$headers[$header] = trim($value);
}
}
if (!isset($headers['Content-Type'])) {
if ($request->getMethod() == Request::METHOD_GET) {
$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
} else {
$headers['Content-Type'] = 'application/xml; charset=utf-8';
}
}
return $headers;
}
}
<?php
/**
* Copyright 2011 Bas de Nooijer. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this listof conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS 'AS IS'
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of the copyright holder.
*/
namespace Solarium\Tests\Core\Client\Adapter;
use Guzzle\Plugin\Mock\MockPlugin;
use Guzzle\Http\Message\Response;
use Guzzle\Http\Client as GuzzleClient;
use Solarium\Core\Client\Adapter\Guzzle3 as GuzzleAdapter;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Exception;
/**
* @coversDefaultClass \Solarium\Core\Client\Adapter\Guzzle3
* @covers ::<private>
* @covers ::getGuzzleClient
*/
final class Guzzle3Test extends \PHPUnit_Framework_TestCase
{
/**
* @var Guzzle3Adapter
*/
private $adapter;
/**
* Prepare each test
*
* @return void
*/
public function setUp()
{
$this->adapter = new GuzzleAdapter();
}
/**
* Verify basic behavior of execute()
*
* @test
* @covers ::execute
*
* @return void
*/
public function executeGet()
{
$guzzleResponse = $this->getValidResponse();
$plugin = new MockPlugin();
$plugin->addResponse($guzzleResponse);
$this->adapter->getGuzzleClient()->addSubscriber($plugin);
$request = new Request();
$request->setMethod(Request::METHOD_GET);
$request->addHeader('X-PHPUnit: request value');
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $this->adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame('200', $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame($guzzleResponse->getBody(true), $response->getBody());
$receivedRequests = $plugin->getReceivedRequests();
$this->assertSame(1, count($receivedRequests));
$this->assertSame('GET', $receivedRequests[0]->getMethod());
$this->assertSame(
'request value',
(string)$receivedRequests[0]->getHeader('X-PHPUnit')
);
}
/**
* Verify execute() with request containing file
*
* @test
* @covers ::execute
*
* @return void
*/
public function executePostWithFile()
{
$guzzleResponse = $this->getValidResponse();
$plugin = new MockPlugin();
$plugin->addResponse($guzzleResponse);
$this->adapter->getGuzzleClient()->addSubscriber($plugin);
$request = new Request();
$request->setMethod(Request::METHOD_POST);
$request->addHeader('X-PHPUnit: request value');
$request->setFileUpload(__FILE__);
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $this->adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame('200', $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame($guzzleResponse->getBody(true), $response->getBody());
$receivedRequests = $plugin->getReceivedRequests();
$this->assertSame(1, count($receivedRequests));
$this->assertSame('POST', $receivedRequests[0]->getMethod());
$this->assertSame(file_get_contents(__FILE__), (string)$receivedRequests[0]->getBody());
$this->assertSame(
'request value',
(string)$receivedRequests[0]->getHeader('X-PHPUnit')
);
}
/**
* Verify execute() with request containing raw body
*
* @test
* @covers ::execute
*
* @return void
*/
public function executePostWithRawBody()
{
$guzzleResponse = $this->getValidResponse();
$plugin = new MockPlugin();
$plugin->addResponse($guzzleResponse);
$this->adapter->getGuzzleClient()->addSubscriber($plugin);
$request = new Request();
$request->setMethod(Request::METHOD_POST);
$request->addHeader('X-PHPUnit: request value');
$xml = '<root><parent><child>some data</child></parent></root>';
$request->setRawData($xml);
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $this->adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame('200', $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame($guzzleResponse->getBody(true), $response->getBody());
$receivedRequests = $plugin->getReceivedRequests();
$this->assertSame(1, count($receivedRequests));
$this->assertSame('POST', $receivedRequests[0]->getMethod());
$this->assertSame($xml, (string)$receivedRequests[0]->getBody());
$this->assertSame(
'request value',
(string)$receivedRequests[0]->getHeader('X-PHPUnit')
);
$this->assertSame(
'application/xml; charset=utf-8',
(string)$receivedRequests[0]->getHeader('Content-Type')
);
}
/**
* Verify execute() with GET request containing Authentication.
*
* @test
* @covers ::execute
*
* @return void
*/
public function executeGetWithAuthentication()
{
$guzzleResponse = $this->getValidResponse();
$plugin = new MockPlugin();
$plugin->addResponse($guzzleResponse);
$this->adapter->getGuzzleClient()->addSubscriber($plugin);
$request = new Request();
$request->setMethod(Request::METHOD_GET);
$request->addHeader('X-PHPUnit: request value');
$request->setAuthentication('username', 's3cr3t');
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $this->adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame('200', $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame($guzzleResponse->getBody(true), $response->getBody());
$receivedRequests = $plugin->getReceivedRequests();
$this->assertSame(1, count($receivedRequests));
$this->assertSame('GET', $receivedRequests[0]->getMethod());
$this->assertSame(
'request value',
(string)$receivedRequests[0]->getHeader('X-PHPUnit')
);
$this->assertSame(
'Basic ' . base64_encode('username:s3cr3t'),
(string)$receivedRequests[0]->getHeader('Authorization')
);
}
/**
* Verify execute() with GET when guzzle throws an exception.
*
* @test
* @covers ::execute
* @expectedException \Solarium\Exception\HttpException
* @expectedExceptionMessage HTTP request failed
*
* @return void
*/
public function executeRequestException()
{
$request = new Request();
$request->setMethod(Request::METHOD_GET);
$endpoint = new Endpoint(
[
'scheme' => 'silly', //invalid protocol
]
);
$this->adapter->execute($request, $endpoint);
}
/**
* Helper method to create a valid Guzzle response.
*
* @return Response
*/
private function getValidResponse()
{
$body = json_encode(
[
'response' => [
'numFound' => 10,
'start' => 0,
'docs' => [
[
'id' => '58339e95d5200',
'author' => 'Gambardella, Matthew',
'title' => "XML Developer's Guide",
'genre' => 'Computer',
'price' => 44.95,
'published' => 970372800,
'description' => 'An in-depth look at creating applications with XML.',
],
],
],
]
);
$headers = ['Content-Type' => 'application/json', 'X-PHPUnit' => 'response value'];
return new Response(200, $headers, $body);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment