mirror of
https://github.com/guzzle/guzzle.git
synced 2025-02-24 18:13:00 +01:00
Adding an abstraction over curl context and providing the context in after_send and error events from the curl adapter.
This commit is contained in:
parent
b8712cbed2
commit
a6bb34646a
93
src/Guzzle/Http/Adapter/Curl/BatchContext.php
Normal file
93
src/Guzzle/Http/Adapter/Curl/BatchContext.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Guzzle\Http\Adapter\Curl;
|
||||
|
||||
use Guzzle\Http\Adapter\TransactionInterface;
|
||||
|
||||
/**
|
||||
* Provides an abstraction over curl_multi handles
|
||||
*/
|
||||
class BatchContext
|
||||
{
|
||||
/** @var resource */
|
||||
private $multi;
|
||||
|
||||
/** @var array Array of {@see TransactionInterface} */
|
||||
private $transactions = [];
|
||||
|
||||
/** @var \SplObjectStorage */
|
||||
private $handles;
|
||||
|
||||
/**
|
||||
* @param resource $mutliHandle Initialized curl_multi resource
|
||||
*/
|
||||
public function __construct($mutliHandle)
|
||||
{
|
||||
$this->multi = $mutliHandle;
|
||||
$this->handles = new \SplObjectStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the transactions in the context
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTransactions()
|
||||
{
|
||||
return iterator_to_array($this->handles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the curl_multi handle
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getMultiHandle()
|
||||
{
|
||||
return $this->multi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a curl easy handle for a specific transaction
|
||||
*
|
||||
* @param TransactionInterface $transaction Transaction associated with the handle
|
||||
*
|
||||
* @return resource|null Returns the handle if found or null if not found
|
||||
*/
|
||||
public function getHandle(TransactionInterface $transaction)
|
||||
{
|
||||
return isset($this->handles[$transaction]) ? $this->handles[$transaction] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a transaction to the multi handle
|
||||
*
|
||||
* @param TransactionInterface $transaction Transaction to add
|
||||
* @param resource $handle Resource to associated with the handle
|
||||
*
|
||||
* @throws \RuntimeException If the handle is already registered
|
||||
*/
|
||||
public function addTransaction(TransactionInterface $transaction, $handle)
|
||||
{
|
||||
if (isset($this->handles[$transaction])) {
|
||||
throw new \RuntimeException('Transaction already registered');
|
||||
}
|
||||
|
||||
CurlAdapter::checkCurlMultiResult(curl_multi_add_handle($this->multi, $handle));
|
||||
$this->handles[$transaction] = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a transaction and associated handle from the context
|
||||
*
|
||||
* @param TransactionInterface $transaction Transaction to remove
|
||||
*/
|
||||
public function removeTransaction(TransactionInterface $transaction)
|
||||
{
|
||||
if (isset($this->handles[$transaction])) {
|
||||
CurlAdapter::checkCurlMultiResult(curl_multi_remove_handle($this->multi, $this->handles[$transaction]));
|
||||
curl_close($this->handles[$transaction]);
|
||||
unset($this->handles[$transaction]);
|
||||
}
|
||||
}
|
||||
}
|
@ -52,6 +52,20 @@ class CurlAdapter implements AdapterInterface, BatchAdapterInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception for a cURL multi response if needed
|
||||
*
|
||||
* @param int $code Curl response code
|
||||
* @throws AdapterException
|
||||
*/
|
||||
public static function checkCurlMultiResult($code)
|
||||
{
|
||||
if ($code != CURLM_OK && $code != CURLM_CALL_MULTI_PERFORM) {
|
||||
$buffer = function_exists('curl_multi_strerror') ? curl_multi_strerror($code) : self::ERROR_STR;
|
||||
throw new AdapterException(sprintf('cURL error %s: %s', $code, $buffer));
|
||||
}
|
||||
}
|
||||
|
||||
public function send(TransactionInterface $transaction)
|
||||
{
|
||||
$this->batch([$transaction]);
|
||||
@ -61,49 +75,40 @@ class CurlAdapter implements AdapterInterface, BatchAdapterInterface
|
||||
|
||||
public function batch(array $transactions)
|
||||
{
|
||||
$context = [
|
||||
'transactions' => $transactions,
|
||||
'handles' => new \SplObjectStorage(),
|
||||
'multi' => $this->checkoutMultiHandle()
|
||||
];
|
||||
$context = new BatchContext($this->checkoutMultiHandle());
|
||||
|
||||
foreach ($transactions as $transaction) {
|
||||
try {
|
||||
$this->prepare($transaction, $context);
|
||||
$context->addTransaction(
|
||||
$transaction,
|
||||
$this->factory->createHandle($transaction, $this->messageFactory)
|
||||
);
|
||||
} catch (RequestException $e) {
|
||||
$stats = isset($context['handles'][$transaction]) && is_resource($context['handles'][$transaction])
|
||||
? curl_getinfo($context['handles'][$transaction])
|
||||
: [];
|
||||
$this->onError($transaction, $e, $context, $stats);
|
||||
$this->onError($transaction, $e, $context, ['curl_context' => $context]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->perform($context);
|
||||
$this->releaseMultiHandle($context['multi']);
|
||||
}
|
||||
|
||||
private function prepare(TransactionInterface $transaction, array $context)
|
||||
{
|
||||
$handle = $this->factory->createHandle($transaction, $this->messageFactory);
|
||||
$this->checkCurlResult(curl_multi_add_handle($context['multi'], $handle));
|
||||
$context['handles'][$transaction] = $handle;
|
||||
$this->releaseMultiHandle($context->getMultiHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute and select curl handles
|
||||
*
|
||||
* @param array $context TransactionInterface context
|
||||
* @param BatchContext $context
|
||||
*/
|
||||
private function perform(array $context)
|
||||
private function perform(BatchContext $context)
|
||||
{
|
||||
// The first curl_multi_select often times out no matter what, but is usually required for fast transfers
|
||||
$selectTimeout = 0.001;
|
||||
$active = false;
|
||||
$multi = $context->getMultiHandle();
|
||||
|
||||
do {
|
||||
while (($mrc = curl_multi_exec($context['multi'], $active)) == CURLM_CALL_MULTI_PERFORM);
|
||||
$this->checkCurlResult($mrc);
|
||||
while (($mrc = curl_multi_exec($multi, $active)) == CURLM_CALL_MULTI_PERFORM);
|
||||
self::checkCurlMultiResult($mrc);
|
||||
$this->processMessages($context);
|
||||
if ($active && curl_multi_select($context['multi'], $selectTimeout) === -1) {
|
||||
if ($active && curl_multi_select($multi, $selectTimeout) === -1) {
|
||||
// Perform a usleep if a select returns -1: https://bugs.php.net/bug.php?id=61141
|
||||
usleep(150);
|
||||
}
|
||||
@ -113,19 +118,14 @@ class CurlAdapter implements AdapterInterface, BatchAdapterInterface
|
||||
|
||||
/**
|
||||
* Check for errors and fix headers of a request based on a curl response
|
||||
*
|
||||
* @param TransactionInterface $transaction Transaction to process
|
||||
* @param array $curl Curl data
|
||||
* @param array $context Array of context information of the transfer
|
||||
*
|
||||
* @throws RequestException on error
|
||||
*/
|
||||
private function processResponse(TransactionInterface $transaction, array $curl, array $context)
|
||||
private function processResponse(TransactionInterface $transaction, array $curl, BatchContext $context)
|
||||
{
|
||||
$stats = curl_getinfo($context['handles'][$transaction]);
|
||||
curl_multi_remove_handle($context['multi'], $context['handles'][$transaction]);
|
||||
curl_close($context['handles'][$transaction]);
|
||||
unset($context['handles'][$transaction]);
|
||||
$handle = $context->getHandle($transaction);
|
||||
$stats = curl_getinfo($handle);
|
||||
$stats['curl_context'] = $context;
|
||||
$context->removeTransaction($transaction);
|
||||
$request = $transaction->getRequest();
|
||||
|
||||
try {
|
||||
@ -140,21 +140,6 @@ class CurlAdapter implements AdapterInterface, BatchAdapterInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process any received curl multi messages
|
||||
*/
|
||||
private function processMessages(array $context)
|
||||
{
|
||||
while ($done = curl_multi_info_read($context['multi'])) {
|
||||
foreach ($context['handles'] as $transaction) {
|
||||
if ($context['handles'][$transaction] === $done['handle']) {
|
||||
$this->processResponse($transaction, $done, $context);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a cURL transfer resulted in what should be an exception
|
||||
*
|
||||
@ -181,16 +166,18 @@ class CurlAdapter implements AdapterInterface, BatchAdapterInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception for a cURL multi response if needed
|
||||
*
|
||||
* @param int $code Curl response code
|
||||
* @throws AdapterException
|
||||
* Process any received curl multi messages
|
||||
*/
|
||||
private function checkCurlResult($code)
|
||||
private function processMessages(BatchContext $context)
|
||||
{
|
||||
if ($code != CURLM_OK && $code != CURLM_CALL_MULTI_PERFORM) {
|
||||
$buffer = function_exists('curl_multi_strerror') ? curl_multi_strerror($code) : self::ERROR_STR;
|
||||
throw new AdapterException(sprintf('cURL error %s: %s', $code, $buffer));
|
||||
$multi = $context->getMultiHandle();
|
||||
while ($done = curl_multi_info_read($multi)) {
|
||||
foreach ($context->getTransactions() as $transaction) {
|
||||
if ($context->getHandle($transaction) === $done['handle']) {
|
||||
$this->processResponse($transaction, $done, $context);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,18 +221,15 @@ class CurlAdapter implements AdapterInterface, BatchAdapterInterface
|
||||
/**
|
||||
* Handle an error
|
||||
*/
|
||||
private function onError(TransactionInterface $transaction, \Exception $e, array $context, array $stats)
|
||||
private function onError(TransactionInterface $transaction, \Exception $e, BatchContext $context, array $stats)
|
||||
{
|
||||
if (!$transaction->getRequest()->getEventDispatcher()->dispatch(
|
||||
RequestEvents::ERROR,
|
||||
new RequestErrorEvent($transaction, $e, $stats)
|
||||
)->isPropagationStopped()) {
|
||||
// Clean up multi handles and context
|
||||
foreach ($context['handles'] as $transaction) {
|
||||
curl_multi_remove_handle($context['multi'], $context['handles'][$transaction]);
|
||||
curl_close($context['handles'][$transaction]);
|
||||
}
|
||||
$this->releaseMultiHandle($context['multi']);
|
||||
$context->removeTransaction($transaction);
|
||||
$this->releaseMultiHandle($context->getMultiHandle());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
43
tests/Guzzle/Tests/Http/Adapter/Curl/BatchContextTest.php
Normal file
43
tests/Guzzle/Tests/Http/Adapter/Curl/BatchContextTest.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Guzzle\Tests\Http\Adapter\Curl;
|
||||
|
||||
use Guzzle\Http\Adapter\Curl\BatchContext;
|
||||
use Guzzle\Http\Adapter\Transaction;
|
||||
use Guzzle\Http\Client;
|
||||
use Guzzle\Http\Message\Request;
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Http\Adapter\Curl\BatchContext
|
||||
*/
|
||||
class BatchContextTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testValidatesTransactionsAreNotAddedTwice()
|
||||
{
|
||||
$m = curl_multi_init();
|
||||
$b = new BatchContext($m);
|
||||
$h = curl_init();
|
||||
$t = new Transaction(new Client(), new Request('GET', '/'));
|
||||
$b->addTransaction($t, $h);
|
||||
try {
|
||||
$b->addTransaction($t, $h);
|
||||
$this->fail('Did not throw');
|
||||
} catch (\RuntimeException $e) {
|
||||
curl_close($h);
|
||||
curl_multi_close($m);
|
||||
}
|
||||
}
|
||||
|
||||
public function testManagesHandles()
|
||||
{
|
||||
$m = curl_multi_init();
|
||||
$b = new BatchContext($m);
|
||||
$h = curl_init();
|
||||
$t = new Transaction(new Client(), new Request('GET', '/'));
|
||||
$b->addTransaction($t, $h);
|
||||
$this->assertEquals([$t], $b->getTransactions());
|
||||
$b->removeTransaction($t);
|
||||
$this->assertEquals([], $b->getTransactions());
|
||||
curl_multi_close($m);
|
||||
}
|
||||
}
|
@ -92,7 +92,7 @@ class CurlAdapterTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertInstanceOf('Guzzle\Http\Event\RequestErrorEvent', $ev);
|
||||
$this->assertSame($r, $ev->getRequest());
|
||||
$this->assertInstanceOf('Guzzle\Http\Exception\RequestException', $ev->getException());
|
||||
$this->assertEquals([], $ev->getTransferInfo());
|
||||
$this->assertEquals(['curl_context'], array_keys($ev->getTransferInfo()));
|
||||
}
|
||||
|
||||
public function testDispatchesAfterSendEvent()
|
||||
@ -102,12 +102,15 @@ class CurlAdapterTest extends \PHPUnit_Framework_TestCase
|
||||
$r = new Request('GET', self::$server->getUrl());
|
||||
$t = new Transaction(new Client(), $r);
|
||||
$a = new CurlAdapter(new MessageFactory());
|
||||
$r->getEventDispatcher()->addListener(RequestEvents::AFTER_SEND, function (RequestAfterSendEvent $e) {
|
||||
$ev = null;
|
||||
$r->getEventDispatcher()->addListener(RequestEvents::AFTER_SEND, function (RequestAfterSendEvent $e) use (&$ev) {
|
||||
$ev = $e;
|
||||
$e->intercept(new Response(200, ['Foo' => 'bar']));
|
||||
});
|
||||
$response = $a->send($t);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('bar', $response->getHeader('Foo'));
|
||||
$this->assertArrayHasKey('curl_context', $ev->getTransferInfo());
|
||||
}
|
||||
|
||||
public function testDispatchesErrorEventAndRecovers()
|
||||
@ -136,10 +139,7 @@ class CurlAdapterTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testChecksCurlMultiResult()
|
||||
{
|
||||
$a = new CurlAdapter(new MessageFactory());
|
||||
$r = new \ReflectionMethod($a, 'checkCurlResult');
|
||||
$r->setAccessible(true);
|
||||
$r->invoke($a, -2);
|
||||
CurlAdapter::checkCurlMultiResult(-2);
|
||||
}
|
||||
|
||||
public function testChecksForCurlException()
|
||||
|
Loading…
x
Reference in New Issue
Block a user