1
0
mirror of https://github.com/guzzle/guzzle.git synced 2025-01-17 21:38:16 +01:00

Added the ability to throw exceptions immediately.

Closes #760
This commit is contained in:
Michael Dowling 2014-08-17 16:21:32 -07:00
parent 90bd509464
commit e5f0b6b84d
12 changed files with 182 additions and 14 deletions

View File

@ -5,9 +5,7 @@ CHANGELOG
------------------
* Now merging in default options using a case-insensitive comparison.
* Updating guzzlehttp/streams dependency to ~2.1
* No longer utilizing the now deprecated namespaced methods from the stream
package.
Closes https://github.com/guzzle/guzzle/issues/767
* Added the ability to automatically decode `Content-Encoding` response bodies
using the `decode_content` request option. This is set to `true` by default
to decode the response body if it comes over the wire with a
@ -16,6 +14,12 @@ CHANGELOG
header and turn on automatic response decoding. This feature now allows you
to pass an `Accept-Encoding` header in the headers of a request but still
disable automatic response decoding.
Closes https://github.com/guzzle/guzzle/issues/764
* Added the ability to throw an exception immediately when transferring
requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760
* Updating guzzlehttp/streams dependency to ~2.1
* No longer utilizing the now deprecated namespaced methods from the stream
package.
4.1.8 (2014-08-14)
------------------

View File

@ -285,6 +285,37 @@ failed request to an array that we can use to process errors later.
// Handle the error...
}
Throwing Errors Immediately
~~~~~~~~~~~~~~~~~~~~~~~~~~~
It sometimes is useful to throw exceptions immediately when the occur. The
following example shows how to use an event listener to throw exceptions
immediately and prevent subsequent requests from being sent.
.. code-block:: php
use GuzzleHttp\Event\ErrorEvent;
$client->sendAll($requests, [
'error' => function (ErrorEvent $event) {
$event->throwImmediately(true);
}
]);
Calling the ``ErrorEvent::throwImmediately()`` instructs the
``ParallelAdapterInterface`` sending the request to stop sending subsequent
requests, clean up any opened resources, and throw the exception associated
with the event as soon as possible. If the error event was not sent by a
``ParallelAdapterInterface``, then calling ``throwImmediately()`` has no
effect.
.. note::
Subsequent listeners of the "error" event can inspect and undo the
``throwImmediately()`` call of a previous listener if they know how to
handle the associated exception or if the error is intercepted with a
response.
.. _batch-requests:
Batching Requests

View File

@ -1,5 +1,4 @@
<?php
namespace GuzzleHttp\Adapter\Curl;
use GuzzleHttp\Adapter\TransactionInterface;
@ -40,6 +39,19 @@ class BatchContext
$this->pending = $pending;
}
/**
* Closes all of the requests associated with the underlying multi handle.
*/
public function removeAll()
{
foreach ($this->handles as $transaction) {
$ch = $this->handles[$transaction];
curl_multi_remove_handle($this->multi, $ch);
curl_close($ch);
unset($this->handles[$transaction]);
}
}
/**
* Find a transaction for a given curl handle
*

View File

@ -178,7 +178,7 @@ class MultiAdapter implements AdapterInterface, ParallelAdapterInterface
) {
RequestEvents::emitComplete($transaction, $info);
}
} catch (RequestException $e) {
} catch (\Exception $e) {
$this->throwException($e, $context);
}
}
@ -233,16 +233,19 @@ class MultiAdapter implements AdapterInterface, ParallelAdapterInterface
),
$stats
);
} catch (RequestException $e) {
} catch (\Exception $e) {
$this->throwException($e, $context);
}
return true;
}
private function throwException(RequestException $e, BatchContext $context)
private function throwException(\Exception $e, BatchContext $context)
{
if ($context->throwsExceptions()) {
if ($context->throwsExceptions()
|| ($e instanceof RequestException && $e->getThrowImmediately())
) {
$context->removeAll();
$this->releaseMultiHandle($context->getMultiHandle());
throw $e;
}

View File

@ -1,5 +1,4 @@
<?php
namespace GuzzleHttp\Adapter;
use GuzzleHttp\Exception\RequestException;
@ -27,7 +26,9 @@ class FakeParallelAdapter implements ParallelAdapterInterface
try {
$this->adapter->send($transaction);
} catch (RequestException $e) {
// no op for batch transaction
if ($e->getThrowImmediately()) {
throw $e;
}
}
}
}

View File

@ -40,6 +40,7 @@ class ErrorEvent extends AbstractTransferEvent
{
$this->stopPropagation();
$this->getTransaction()->setResponse($response);
$this->exception->setThrowImmediately(false);
RequestEvents::emitComplete($this->getTransaction());
}
@ -62,4 +63,18 @@ class ErrorEvent extends AbstractTransferEvent
{
return $this->getException()->getResponse();
}
/**
* Request that a ParallelAdapterInterface throw the associated exception
* if the exception is unhandled.
*
* If the error event was not emitted from a ParallelAdapterInterface, then
* the effect of this method is nil.
*
* @param bool $throwImmediately Whether or not to throw immediately
*/
public function throwImmediately($throwImmediately)
{
$this->exception->setThrowImmediately($throwImmediately);
}
}

View File

@ -19,6 +19,9 @@ class RequestException extends TransferException
/** @var ResponseInterface */
private $response;
/** @var bool */
private $throwImmediately = false;
public function __construct(
$message = '',
RequestInterface $request,
@ -115,10 +118,33 @@ class RequestException extends TransferException
if ($value === null) {
return $this->emittedErrorEvent;
} elseif ($value === true) {
return $this->emittedErrorEvent = true;
$this->emittedErrorEvent = true;
} else {
throw new \InvalidArgumentException('You cannot set the emitted '
. 'error value to false.');
}
}
/**
* Sets whether or not parallel adapters SHOULD throw the exception
* immediately rather than handling errors through asynchronous error
* handling.
*
* @param bool $throwImmediately
*
*/
public function setThrowImmediately($throwImmediately)
{
$this->throwImmediately = $throwImmediately;
}
/**
* Gets the setting specified by setThrowImmediately().
*
* @return bool
*/
public function getThrowImmediately()
{
return $this->throwImmediately;
}
}

View File

@ -92,4 +92,20 @@ class BatchContextTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($b->hasPending());
$this->assertNull($b->nextPending());
}
public function testCanCloseAll()
{
$m = curl_multi_init();
$b = new BatchContext($m, true);
$h = curl_init();
$t = new Transaction(
new Client(),
new Request('GET', 'http://httbin.org')
);
$b->addTransaction($t, $h);
$b->removeAll();
$this->assertFalse($b->isActive());
$this->assertEquals(0, count($this->readAttribute($b, 'handles')));
curl_multi_close($m);
}
}

View File

@ -64,6 +64,7 @@ class MultiAdapterTest extends AbstractCurl
public function testChecksForCurlException()
{
$mh = curl_multi_init();
$request = new Request('GET', 'http://httbin.org');
$transaction = $this->getMockBuilder('GuzzleHttp\Adapter\Transaction')
->setMethods(['getRequest'])
@ -74,7 +75,7 @@ class MultiAdapterTest extends AbstractCurl
->will($this->returnValue($request));
$context = $this->getMockBuilder('GuzzleHttp\Adapter\Curl\BatchContext')
->setMethods(['throwsExceptions'])
->disableOriginalConstructor()
->setConstructorArgs([$mh, true])
->getMock();
$context->expects($this->once())
->method('throwsExceptions')
@ -84,8 +85,10 @@ class MultiAdapterTest extends AbstractCurl
$r->setAccessible(true);
try {
$r->invoke($a, $transaction, ['result' => -10], $context, []);
curl_multi_close($mh);
$this->fail('Did not throw');
} catch (RequestException $e) {
curl_multi_close($mh);
$this->assertSame($request, $e->getRequest());
$this->assertContains('[curl] (#-10) ', $e->getMessage());
$this->assertContains($request->getUrl(), $e->getMessage());
@ -319,4 +322,23 @@ class MultiAdapterTest extends AbstractCurl
201
);
}
public function testThrowsImmediatelyWhenInstructed()
{
Server::flush();
Server::enqueue(["HTTP/1.1 501\r\nContent-Length: 0\r\n\r\n"]);
$c = new Client(['base_url' => Server::$url]);
$request = $c->createRequest('GET', '/');
$request->getEmitter()->on('error', function (ErrorEvent $e) {
$e->throwImmediately(true);
});
$transactions = [new Transaction($c, $request)];
$a = new MultiAdapter(new MessageFactory());
try {
$a->sendAll(new \ArrayIterator($transactions), 1);
$this->fail('Did not throw');
} catch (RequestException $e) {
$this->assertSame($request, $e->getRequest());
}
}
}

View File

@ -1,10 +1,11 @@
<?php
namespace GuzzleHttp\Tests\Adapter;
use GuzzleHttp\Adapter\FakeParallelAdapter;
use GuzzleHttp\Adapter\MockAdapter;
use GuzzleHttp\Client;
use GuzzleHttp\Event\ErrorEvent;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Adapter\TransactionIterator;
@ -32,4 +33,27 @@ class FakeParallelAdapterTest extends \PHPUnit_Framework_TestCase
$this->assertContains('GET', $sent);
$this->assertContains('HEAD', $sent);
}
public function testThrowsImmediatelyIfInstructed()
{
$client = new Client();
$request = $client->createRequest('GET', 'http://httbin.org');
$request->getEmitter()->on('error', function (ErrorEvent $e) {
$e->throwImmediately(true);
});
$sent = [];
$f = new FakeParallelAdapter(
new MockAdapter(function ($trans) use (&$sent) {
$sent[] = $trans->getRequest()->getMethod();
return new Response(404);
})
);
$tIter = new TransactionIterator([$request], $client, []);
try {
$f->sendAll($tIter, 1);
$this->fail('Did not throw');
} catch (RequestException $e) {
$this->assertSame($request, $e->getRequest());
}
}
}

View File

@ -1,5 +1,4 @@
<?php
namespace GuzzleHttp\Tests\Event;
use GuzzleHttp\Adapter\Transaction;
@ -23,6 +22,11 @@ class ErrorEventTest extends \PHPUnit_Framework_TestCase
$except = new RequestException('foo', $request, $response);
$event = new ErrorEvent($transaction, $except);
$event->throwImmediately(true);
$this->assertTrue($except->getThrowImmediately());
$event->throwImmediately(false);
$this->assertFalse($except->getThrowImmediately());
$this->assertSame($except, $event->getException());
$this->assertSame($response, $event->getResponse());
$this->assertSame($request, $event->getRequest());

View File

@ -81,4 +81,14 @@ class RequestExceptionTest extends \PHPUnit_Framework_TestCase
$e = RequestException::create(new Request('GET', '/'), new Response(442));
$this->assertEquals(442, $e->getCode());
}
public function testHasThrowState() {
$e = RequestException::create(
new Request('GET', '/'),
new Response(442)
);
$this->assertFalse($e->getThrowImmediately());
$e->setThrowImmediately(true);
$this->assertTrue($e->getThrowImmediately());
}
}