mirror of
https://github.com/guzzle/guzzle.git
synced 2025-02-24 10:03:27 +01:00
Adding a RingBridge for better separation
This commit is contained in:
parent
8fe3124bd9
commit
0337b7848a
@ -224,29 +224,30 @@ class Client implements ClientInterface
|
||||
$trans = new Transaction($this, $request);
|
||||
RequestEvents::emitBefore($trans);
|
||||
|
||||
// Return a response if one was set during the before event.
|
||||
if ($trans->response) {
|
||||
return $trans->response;
|
||||
}
|
||||
|
||||
// Send the request using the Guzzle ring handler
|
||||
// Send the request using the Guzzle Ring handler
|
||||
$adapter = $this->adapter;
|
||||
$response = $adapter(
|
||||
RequestEvents::createRingRequest($trans, $this->messageFactory)
|
||||
RingBridge::prepareRingRequest($trans, $this->messageFactory)
|
||||
);
|
||||
|
||||
if ($trans->response) {
|
||||
return $trans->response;
|
||||
} elseif ($response instanceof RingFutureInterface) {
|
||||
if ($response instanceof RingFutureInterface) {
|
||||
return $this->createFutureResponse($response, $trans);
|
||||
} elseif ($trans->response) {
|
||||
// A response can be set during the "then" ring callback.
|
||||
return $trans->response;
|
||||
} elseif (isset($response['error'])) {
|
||||
// Errors are handled outside of the "then" function for
|
||||
// synchronous errors.
|
||||
// Errors are thrown and handled outside of the "then" function
|
||||
// for synchronous errors.
|
||||
throw $response['error'];
|
||||
}
|
||||
|
||||
throw new \RuntimeException('No response was associated with the '
|
||||
. 'transaction! This means the ring adapter did something '
|
||||
. 'wrong and never completed the transaction.');
|
||||
// No response, future, or error, so throw an exception.
|
||||
throw $this->getNoRingResponseException($trans->request);
|
||||
|
||||
} catch (RequestException $e) {
|
||||
throw $e;
|
||||
@ -262,22 +263,27 @@ class Client implements ClientInterface
|
||||
) {
|
||||
// Create a future response that's hooked up to the ring future.
|
||||
return new FutureResponse(
|
||||
// Dereference function
|
||||
function () use ($response, $trans) {
|
||||
// Only deref if an event listener did not intercept.
|
||||
if (!$trans->response) {
|
||||
$result = $response->deref();
|
||||
// The response may have been set when dereferencing.
|
||||
if (!$trans->response) {
|
||||
if (isset($result['error'])) {
|
||||
throw $response['error'];
|
||||
} else {
|
||||
throw new \RuntimeException('Did not return a '
|
||||
. 'response or error.');
|
||||
}
|
||||
}
|
||||
// Event listeners may have intercepted the transaction during
|
||||
// the "then" event of the ring request transfer.
|
||||
if ($trans->response) {
|
||||
return $trans->response;
|
||||
}
|
||||
return $trans->response;
|
||||
// Dereference the underlying future and block until complete.
|
||||
$result = $response->deref();
|
||||
// The transaction response may have been set on the trans
|
||||
// while dereferencing the response (e.g., MockAdapter).
|
||||
if ($trans->response) {
|
||||
return $trans->response;
|
||||
}
|
||||
// Throw the appropriate exception, whether one was explicitly
|
||||
// set, or we need to inform the user of a misbehaving adapter.
|
||||
throw isset($result['error'])
|
||||
? $response['error']
|
||||
: $this->getNoRingResponseException($trans->request);
|
||||
},
|
||||
// Cancel function. Just proxy to the underlying future.
|
||||
function () use ($response) {
|
||||
return $response->cancel();
|
||||
}
|
||||
@ -384,9 +390,7 @@ class Client implements ClientInterface
|
||||
private function mergeDefaults(&$options)
|
||||
{
|
||||
// Merging optimization for when no headers are present
|
||||
if (!isset($options['headers'])
|
||||
|| !isset($this->defaults['headers'])
|
||||
) {
|
||||
if (!isset($options['headers']) || !isset($this->defaults['headers'])) {
|
||||
$options = array_replace_recursive($this->defaults, $options);
|
||||
return null;
|
||||
}
|
||||
@ -406,4 +410,14 @@ class Client implements ClientInterface
|
||||
{
|
||||
Pool::send($this, $requests, $options);
|
||||
}
|
||||
|
||||
private function getNoRingResponseException(RequestInterface $request)
|
||||
{
|
||||
return new RequestException(
|
||||
'Sending the request did not return a response, exception, or '
|
||||
. 'populate the transaction with a response. This is most likely '
|
||||
. 'due to an incorrectly implemented Guzzle Ring adapter.',
|
||||
$request
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Event;
|
||||
|
||||
use GuzzleHttp\Message\MessageFactoryInterface;
|
||||
use GuzzleHttp\Ring\FutureInterface;
|
||||
use GuzzleHttp\Transaction;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
@ -158,102 +157,4 @@ final class RequestEvents
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Guzzle Transaction object into a Guzzle Ring request array.
|
||||
*
|
||||
* @param Transaction $trans Transaction to convert.
|
||||
* @param MessageFactoryInterface $messageFactory Factory used to create
|
||||
* response objects.
|
||||
*
|
||||
* @return array Request hash to send via a ring handler.
|
||||
*/
|
||||
public static function createRingRequest(
|
||||
Transaction $trans,
|
||||
MessageFactoryInterface $messageFactory
|
||||
) {
|
||||
$request = $trans->request;
|
||||
$options = $request->getConfig()->toArray();
|
||||
$url = $request->getUrl();
|
||||
$r = [
|
||||
'scheme' => $request->getScheme(),
|
||||
'http_method' => $request->getMethod(),
|
||||
'url' => $url,
|
||||
'uri' => $request->getPath(),
|
||||
'headers' => $request->getHeaders(),
|
||||
'body' => $request->getBody(),
|
||||
'version' => $request->getProtocolVersion(),
|
||||
'client' => $options,
|
||||
'future' => isset($options['future']) ? $options['future'] : null,
|
||||
// No need to calculate the query string twice.
|
||||
'query_string' => ($pos = strpos($url, '?')) ? substr($url, $pos + 1) : null,
|
||||
'then' => function (array $response) use ($trans, $messageFactory) {
|
||||
self::completeRingResponse($trans, $response, $messageFactory);
|
||||
},
|
||||
];
|
||||
|
||||
// Emit progress events if any progress listeners are registered.
|
||||
if ($request->getEmitter()->hasListeners('progress')) {
|
||||
$emitter = $request->getEmitter();
|
||||
$r['client']['progress'] = function ($a, $b, $c, $d) use ($trans, $emitter) {
|
||||
$emitter->emit(
|
||||
'progress',
|
||||
new ProgressEvent($trans, $a, $b, $c, $d)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the process of processing a response received from a handler.
|
||||
*
|
||||
* @param Transaction $trans Owns request and response.
|
||||
* @param array $res Response array
|
||||
* @param MessageFactoryInterface $messageFactory Creates response objects.
|
||||
*/
|
||||
public static function completeRingResponse(
|
||||
Transaction $trans,
|
||||
array $res,
|
||||
MessageFactoryInterface $messageFactory
|
||||
) {
|
||||
if (!empty($res['status'])) {
|
||||
$options = [];
|
||||
|
||||
if (isset($res['version'])) {
|
||||
$options['protocol_version'] = $res['version'];
|
||||
}
|
||||
|
||||
if (isset($res['reason'])) {
|
||||
$options['reason_phrase'] = $res['reason'];
|
||||
}
|
||||
|
||||
$trans->response = $messageFactory->createResponse(
|
||||
$res['status'],
|
||||
$res['headers'],
|
||||
isset($res['body']) ? $res['body'] : null,
|
||||
$options
|
||||
);
|
||||
|
||||
if (isset($res['effective_url'])) {
|
||||
$trans->response->setEffectiveUrl($res['effective_url']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($res['error'])) {
|
||||
RequestEvents::emitComplete($trans);
|
||||
} else {
|
||||
RequestEvents::emitError(
|
||||
$trans,
|
||||
new RequestException(
|
||||
$res['error']->getMessage(),
|
||||
$trans->request,
|
||||
$trans->response,
|
||||
$res['error']
|
||||
),
|
||||
isset($res['transfer_info']) ? $res['transfer_info'] : []
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
152
src/RingBridge.php
Normal file
152
src/RingBridge.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Message\MessageFactoryInterface;
|
||||
use GuzzleHttp\Message\RequestInterface;
|
||||
use GuzzleHttp\Event\ProgressEvent;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Event\RequestEvents;
|
||||
|
||||
/**
|
||||
* Provides the bridge between Guzzle requests and responses and Guzzle Ring.
|
||||
*/
|
||||
class RingBridge
|
||||
{
|
||||
/**
|
||||
* Creates a Ring request from a request object.
|
||||
*
|
||||
* This function does not hook up the "then" and "progress" events that
|
||||
* would be required for actually sending a Guzzle request through a
|
||||
* ring adapter.
|
||||
*
|
||||
* @param RequestInterface $request Request to convert.
|
||||
*
|
||||
* @return array Converted Guzzle Ring request.
|
||||
*/
|
||||
public static function createRingRequest(RequestInterface $request)
|
||||
{
|
||||
$options = $request->getConfig()->toArray();
|
||||
$url = $request->getUrl();
|
||||
// No need to calculate the query string twice (in URL and query).
|
||||
$qs = ($pos = strpos($url, '?')) ? substr($url, $pos + 1) : null;
|
||||
|
||||
return [
|
||||
'scheme' => $request->getScheme(),
|
||||
'http_method' => $request->getMethod(),
|
||||
'url' => $url,
|
||||
'uri' => $request->getPath(),
|
||||
'headers' => $request->getHeaders(),
|
||||
'body' => $request->getBody(),
|
||||
'version' => $request->getProtocolVersion(),
|
||||
'client' => $options,
|
||||
'query_string' => $qs,
|
||||
'future' => isset($options['future']) ? $options['future'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a ring request array, this function adds the "then" and "progress"
|
||||
* event callbacks using a transaction and message factory.
|
||||
*
|
||||
* @param array $ringRequest Request to update.
|
||||
* @param Transaction $trans Transaction
|
||||
* @param MessageFactoryInterface $factory Creates responses.
|
||||
*
|
||||
* @return array Returns the new ring response array.
|
||||
*/
|
||||
public static function addRingRequestCallbacks(
|
||||
array $ringRequest,
|
||||
Transaction $trans,
|
||||
MessageFactoryInterface $factory
|
||||
) {
|
||||
$request = $trans->request;
|
||||
|
||||
$ringRequest['then'] = function (array $response) use ($trans, $factory) {
|
||||
self::completeRingResponse($trans, $response, $factory);
|
||||
};
|
||||
|
||||
// Emit progress events if any progress listeners are registered.
|
||||
if ($request->getEmitter()->hasListeners('progress')) {
|
||||
$emitter = $request->getEmitter();
|
||||
$ringRequest['client']['progress'] = function ($a, $b, $c, $d)
|
||||
use ($trans, $emitter)
|
||||
{
|
||||
$emitter->emit(
|
||||
'progress',
|
||||
new ProgressEvent($trans, $a, $b, $c, $d)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return $ringRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Ring request from a request object AND prepares the callbacks.
|
||||
*
|
||||
* @param Transaction $transaction Transaction to update.
|
||||
* @param MessageFactoryInterface $factory Creates responses.
|
||||
*
|
||||
* @return array Converted Guzzle Ring request.
|
||||
*/
|
||||
public static function prepareRingRequest(
|
||||
Transaction $transaction,
|
||||
MessageFactoryInterface $factory
|
||||
) {
|
||||
return self::addRingRequestCallbacks(
|
||||
self::createRingRequest($transaction->request),
|
||||
$transaction,
|
||||
$factory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the process of processing a response received from a ring
|
||||
* handler. The created response is added to the transaction, and any
|
||||
* necessary events are emitted based on the ring response.
|
||||
*
|
||||
* @param Transaction $trans Owns request and response.
|
||||
* @param array $response Ring response array
|
||||
* @param MessageFactoryInterface $messageFactory Creates response objects.
|
||||
*/
|
||||
public static function completeRingResponse(
|
||||
Transaction $trans,
|
||||
array $response,
|
||||
MessageFactoryInterface $messageFactory
|
||||
) {
|
||||
if (!empty($response['status'])) {
|
||||
$options = [];
|
||||
if (isset($response['version'])) {
|
||||
$options['protocol_version'] = $response['version'];
|
||||
}
|
||||
if (isset($response['reason'])) {
|
||||
$options['reason_phrase'] = $response['reason'];
|
||||
}
|
||||
$trans->response = $messageFactory->createResponse(
|
||||
$response['status'],
|
||||
$response['headers'],
|
||||
isset($response['body']) ? $response['body'] : null,
|
||||
$options
|
||||
);
|
||||
if (isset($response['effective_url'])) {
|
||||
$trans->response->setEffectiveUrl($response['effective_url']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($response['error'])) {
|
||||
RequestEvents::emitComplete($trans);
|
||||
} else {
|
||||
RequestEvents::emitError(
|
||||
$trans,
|
||||
new RequestException(
|
||||
$response['error']->getMessage(),
|
||||
$trans->request,
|
||||
$trans->response,
|
||||
$response['error']
|
||||
),
|
||||
isset($response['transfer_info'])
|
||||
? $response['transfer_info'] : []
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -329,7 +329,7 @@ class ClientTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
/**
|
||||
* @expectedException \GuzzleHttp\Exception\RequestException
|
||||
* @expectedExceptionMessage No response
|
||||
* @expectedExceptionMessage incorrectly implemented Guzzle Ring adapter
|
||||
*/
|
||||
public function testEnsuresResponseIsPresentAfterSending()
|
||||
{
|
||||
|
@ -2,12 +2,7 @@
|
||||
namespace GuzzleHttp\Tests\Event;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Event\ProgressEvent;
|
||||
use GuzzleHttp\Message\FutureResponse;
|
||||
use GuzzleHttp\Message\MessageFactory;
|
||||
use GuzzleHttp\Ring\Client\MockAdapter;
|
||||
use GuzzleHttp\Stream\Stream;
|
||||
use GuzzleHttp\Tests\Server;
|
||||
use GuzzleHttp\Transaction;
|
||||
use GuzzleHttp\Event\BeforeEvent;
|
||||
use GuzzleHttp\Event\ErrorEvent;
|
||||
@ -225,86 +220,4 @@ class RequestEventsTest extends \PHPUnit_Framework_TestCase
|
||||
$result = RequestEvents::convertEventArray($in, $events, $add);
|
||||
$this->assertEquals($out, $result);
|
||||
}
|
||||
|
||||
public function testCreatesRingRequests()
|
||||
{
|
||||
$stream = Stream::factory('test');
|
||||
$request = new Request('GET', 'http://httpbin.org/get?a=b', [
|
||||
'test' => 'hello'
|
||||
], $stream);
|
||||
$request->getConfig()->set('foo', 'bar');
|
||||
$trans = new Transaction(new Client(), $request);
|
||||
$factory = new MessageFactory();
|
||||
$r = RequestEvents::createRingRequest($trans, $factory);
|
||||
$this->assertEquals('http', $r['scheme']);
|
||||
$this->assertEquals('GET', $r['http_method']);
|
||||
$this->assertEquals('http://httpbin.org/get?a=b', $r['url']);
|
||||
$this->assertEquals('/get', $r['uri']);
|
||||
$this->assertEquals('a=b', $r['query_string']);
|
||||
$this->assertEquals([
|
||||
'Host' => ['httpbin.org'],
|
||||
'test' => ['hello']
|
||||
], $r['headers']);
|
||||
$this->assertSame($stream, $r['body']);
|
||||
$this->assertEquals(['foo' => 'bar'], $r['client']);
|
||||
$this->assertTrue(is_callable($r['then']));
|
||||
}
|
||||
|
||||
public function testCreatesRingRequestsWithNullQueryString()
|
||||
{
|
||||
$request = new Request('GET', 'http://httpbin.org');
|
||||
$trans = new Transaction(new Client(), $request);
|
||||
$factory = new MessageFactory();
|
||||
$r = RequestEvents::createRingRequest($trans, $factory);
|
||||
$this->assertNull($r['query_string']);
|
||||
$this->assertEquals('/', $r['uri']);
|
||||
$this->assertEquals(['Host' => ['httpbin.org']], $r['headers']);
|
||||
$this->assertNull($r['body']);
|
||||
$this->assertEquals([], $r['client']);
|
||||
}
|
||||
|
||||
public function testCallsThenAndAddsProgress()
|
||||
{
|
||||
Server::enqueue([new Response(200)]);
|
||||
$client = new Client(['base_url' => Server::$url]);
|
||||
$request = $client->createRequest('GET');
|
||||
$called = false;
|
||||
$request->getEmitter()->on(
|
||||
'progress',
|
||||
function (ProgressEvent $e) use (&$called) {
|
||||
$called = true;
|
||||
}
|
||||
);
|
||||
$this->assertEquals(200, $client->send($request)->getStatusCode());
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testEmitsErrorEventOnError()
|
||||
{
|
||||
$client = new Client(['base_url' => 'http://127.0.0.1:123']);
|
||||
$request = $client->createRequest('GET');
|
||||
$request->getConfig()['timeout'] = 0.001;
|
||||
$request->getConfig()['connect_timeout'] = 0.001;
|
||||
try {
|
||||
$client->send($request);
|
||||
$this->fail('did not throw');
|
||||
} catch (RequestException $e) {
|
||||
$this->assertSame($request, $e->getRequest());
|
||||
$this->assertContains('cURL error 7:', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetsResponseProtocolVersion()
|
||||
{
|
||||
$client = new Client([
|
||||
'adapter' => new MockAdapter([
|
||||
'status' => 200,
|
||||
'headers' => [],
|
||||
'version' => '1.0'
|
||||
])
|
||||
]);
|
||||
$request = $client->createRequest('GET', 'http://foo.com');
|
||||
$response = $client->send($request);
|
||||
$this->assertEquals('1.0', $response->getProtocolVersion());
|
||||
}
|
||||
}
|
||||
|
98
tests/RingBridgeTest.php
Normal file
98
tests/RingBridgeTest.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Tests;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Event\ProgressEvent;
|
||||
use GuzzleHttp\Message\MessageFactory;
|
||||
use GuzzleHttp\RingBridge;
|
||||
use GuzzleHttp\Stream\Stream;
|
||||
use GuzzleHttp\Transaction;
|
||||
use GuzzleHttp\Message\Request;
|
||||
use GuzzleHttp\Message\Response;
|
||||
use GuzzleHttp\Ring\Client\MockAdapter;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
|
||||
class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testCreatesRingRequests()
|
||||
{
|
||||
$stream = Stream::factory('test');
|
||||
$request = new Request('GET', 'http://httpbin.org/get?a=b', [
|
||||
'test' => 'hello'
|
||||
], $stream);
|
||||
$request->getConfig()->set('foo', 'bar');
|
||||
$trans = new Transaction(new Client(), $request);
|
||||
$factory = new MessageFactory();
|
||||
$r = RingBridge::prepareRingRequest($trans, $factory);
|
||||
$this->assertEquals('http', $r['scheme']);
|
||||
$this->assertEquals('GET', $r['http_method']);
|
||||
$this->assertEquals('http://httpbin.org/get?a=b', $r['url']);
|
||||
$this->assertEquals('/get', $r['uri']);
|
||||
$this->assertEquals('a=b', $r['query_string']);
|
||||
$this->assertEquals([
|
||||
'Host' => ['httpbin.org'],
|
||||
'test' => ['hello']
|
||||
], $r['headers']);
|
||||
$this->assertSame($stream, $r['body']);
|
||||
$this->assertEquals(['foo' => 'bar'], $r['client']);
|
||||
$this->assertTrue(is_callable($r['then']));
|
||||
}
|
||||
|
||||
public function testCreatesRingRequestsWithNullQueryString()
|
||||
{
|
||||
$request = new Request('GET', 'http://httpbin.org');
|
||||
$trans = new Transaction(new Client(), $request);
|
||||
$factory = new MessageFactory();
|
||||
$r = RingBridge::prepareRingRequest($trans, $factory);
|
||||
$this->assertNull($r['query_string']);
|
||||
$this->assertEquals('/', $r['uri']);
|
||||
$this->assertEquals(['Host' => ['httpbin.org']], $r['headers']);
|
||||
$this->assertNull($r['body']);
|
||||
$this->assertEquals([], $r['client']);
|
||||
}
|
||||
|
||||
public function testCallsThenAndAddsProgress()
|
||||
{
|
||||
Server::enqueue([new Response(200)]);
|
||||
$client = new Client(['base_url' => Server::$url]);
|
||||
$request = $client->createRequest('GET');
|
||||
$called = false;
|
||||
$request->getEmitter()->on(
|
||||
'progress',
|
||||
function (ProgressEvent $e) use (&$called) {
|
||||
$called = true;
|
||||
}
|
||||
);
|
||||
$this->assertEquals(200, $client->send($request)->getStatusCode());
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testGetsResponseProtocolVersion()
|
||||
{
|
||||
$client = new Client([
|
||||
'adapter' => new MockAdapter([
|
||||
'status' => 200,
|
||||
'headers' => [],
|
||||
'version' => '1.0'
|
||||
])
|
||||
]);
|
||||
$request = $client->createRequest('GET', 'http://foo.com');
|
||||
$response = $client->send($request);
|
||||
$this->assertEquals('1.0', $response->getProtocolVersion());
|
||||
}
|
||||
|
||||
public function testEmitsErrorEventOnError()
|
||||
{
|
||||
$client = new Client(['base_url' => 'http://127.0.0.1:123']);
|
||||
$request = $client->createRequest('GET');
|
||||
$request->getConfig()['timeout'] = 0.001;
|
||||
$request->getConfig()['connect_timeout'] = 0.001;
|
||||
try {
|
||||
$client->send($request);
|
||||
$this->fail('did not throw');
|
||||
} catch (RequestException $e) {
|
||||
$this->assertSame($request, $e->getRequest());
|
||||
$this->assertContains('cURL error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user