Skip to content

Commit

Permalink
Merge pull request #31 from php-http/feature/safety-catch
Browse files Browse the repository at this point in the history
Add a safety catch for max restarts
  • Loading branch information
joelwurtz committed Dec 25, 2015
2 parents 2f5651c + c930d99 commit 57762fb
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 1 deletion.
16 changes: 16 additions & 0 deletions spec/PluginClientSpec.php
Expand Up @@ -4,11 +4,20 @@

use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Client\Plugin\Plugin;
use Http\Promise\Promise;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use PhpSpec\ObjectBehavior;

class DefectuousPlugin implements Plugin
{
public function handleRequest(RequestInterface $request, callable $next, callable $first)
{
return $first($request);
}
}

class PluginClientSpec extends ObjectBehavior
{
function let(HttpClient $client)
Expand Down Expand Up @@ -45,4 +54,11 @@ function it_sends_async_request_with_underlying_client(HttpAsyncClient $asyncCli
$this->beConstructedWith($asyncClient);
$this->sendAsyncRequest($request)->shouldReturn($promise);
}

function it_throws_loop_exception(HttpClient $client, RequestInterface $request)
{
$this->beConstructedWith($client, [new DefectuousPlugin()]);

$this->shouldThrow('Http\Client\Plugin\Exception\LoopException')->duringSendRequest($request);
}
}
9 changes: 9 additions & 0 deletions src/Exception/LoopException.php
@@ -0,0 +1,9 @@
<?php

namespace Http\Client\Plugin\Exception;

use Http\Client\Exception\RequestException;

class LoopException extends RequestException
{
}
43 changes: 42 additions & 1 deletion src/PluginClient.php
Expand Up @@ -4,7 +4,9 @@

use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Client\Plugin\Exception\LoopException;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
* The client managing plugins and providing a decorator around HTTP Clients.
Expand All @@ -27,13 +29,21 @@ class PluginClient implements HttpClient, HttpAsyncClient
*/
protected $plugins;

/**
* A list of options.
*
* @var array
*/
protected $options;

/**
* @param HttpClient|HttpAsyncClient $client
* @param Plugin[] $plugins
* @param array $options
*
* @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient
*/
public function __construct($client, array $plugins = [])
public function __construct($client, array $plugins = [], array $options = [])
{
if ($client instanceof HttpAsyncClient) {
$this->client = $client;
Expand All @@ -44,6 +54,7 @@ public function __construct($client, array $plugins = [])
}

$this->plugins = $plugins;
$this->options = $this->configure($options);
}

/**
Expand All @@ -66,6 +77,23 @@ public function sendAsyncRequest(RequestInterface $request)
return $pluginChain($request);
}

/**
* Configure the plugin client.
*
* @param array $options
*
* @return array
*/
protected function configure(array $options = [])
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
'max_restarts' => 10,
]);

return $resolver->resolve($options);
}

/**
* @param Plugin[] $pluginList
*
Expand All @@ -74,6 +102,8 @@ public function sendAsyncRequest(RequestInterface $request)
private function createPluginChain($pluginList)
{
$client = $this->client;
$options = $this->options;

$lastCallable = function (RequestInterface $request) use ($client) {
return $client->sendAsyncRequest($request);
};
Expand All @@ -87,6 +117,17 @@ private function createPluginChain($pluginList)
$firstCallable = $lastCallable;
}

$firstCalls = 0;
$firstCallable = function (RequestInterface $request) use ($options, $lastCallable, &$firstCalls) {
if ($firstCalls > $options['max_restarts']) {
throw new LoopException('Too many restarts in plugin client', $request);
}

++$firstCalls;

return $lastCallable($request);
};

return $firstCallable;
}
}

0 comments on commit 57762fb

Please sign in to comment.