From 889434d5c0aa7354661d6f2b4ab2a74fd9a53331 Mon Sep 17 00:00:00 2001 From: Etienne Zannelli Date: Wed, 2 Nov 2022 10:15:34 +0100 Subject: [PATCH] Add guzzle failure detector --- README.md | 29 +++++++++++++++++++ src/Ganesha/GuzzleMiddleware.php | 17 +++++++++-- .../AlwaysSuccessFailureDetector.php | 13 +++++++++ .../FailureDetectorInterface.php | 10 +++++++ .../Ganesha/GuzzleMiddlewareTest.php | 28 ++++++++++++++++-- 5 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 src/Ganesha/GuzzleMiddleware/AlwaysSuccessFailureDetector.php create mode 100644 src/Ganesha/GuzzleMiddleware/FailureDetectorInterface.php diff --git a/README.md b/README.md index 1149063..ff59f6e 100644 --- a/README.md +++ b/README.md @@ -418,6 +418,35 @@ $middleware = new GuzzleMiddleware( ); ``` +### How does Guzzle Middleware determine the failure? + +By default, if the next handler promise is fulfilled ganesha will consider it a success, and a failure if it is rejected. + +You can implement your own rules on fulfilled response by passing an implementation of `FailureDetectorInterface` to the middleware. + +```php +use Ackintosh\Ganesha\GuzzleMiddleware\FailureDetectorInterface; +use Psr\Http\Message\ResponseInterface; + +class HttpStatusFailureDetector implements FailureDetectorInterface +{ + public function isFailureResponse(ResponseInterface $response) : bool + { + return in_array($response->getStatusCode(), [503, 504], true); + } +} + +// --- +$ganesha = Builder::withRateStrategy() + // ... + ->build(); +$middleware = new GuzzleMiddleware( + $ganesha, + // Pass the failure detector to the GuzzleMiddleware constructor. + failureDetector: new HttpStatusFailureDetector() +); +``` + ## [Ganesha :heart: OpenAPI Generator](#table-of-contents) PHP client generated by [OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator) is using Guzzle as HTTP client and as we mentioned as [Ganesha :heart: Guzzle](https://github.com/ackintosh/ganesha#ganesha-heart-guzzle), Guzzle Middleware powered by Ganesha is ready. So it is easily possible to integrate Ganesha and the PHP client generated by OpenAPI Generator in a smart way as below. diff --git a/src/Ganesha/GuzzleMiddleware.php b/src/Ganesha/GuzzleMiddleware.php index d012495..ede16f4 100644 --- a/src/Ganesha/GuzzleMiddleware.php +++ b/src/Ganesha/GuzzleMiddleware.php @@ -3,6 +3,8 @@ use Ackintosh\Ganesha; use Ackintosh\Ganesha\Exception\RejectedException; +use Ackintosh\Ganesha\GuzzleMiddleware\AlwaysSuccessFailureDetector; +use Ackintosh\Ganesha\GuzzleMiddleware\FailureDetectorInterface; use Ackintosh\Ganesha\GuzzleMiddleware\ServiceNameExtractor; use Ackintosh\Ganesha\GuzzleMiddleware\ServiceNameExtractorInterface; use Psr\Http\Message\RequestInterface; @@ -19,12 +21,19 @@ class GuzzleMiddleware */ private $serviceNameExtractor; + /** + * @var FailureDetectorInterface + */ + private $failureDetector; + public function __construct( Ganesha $ganesha, - ServiceNameExtractorInterface $serviceNameExtractor = null + ServiceNameExtractorInterface $serviceNameExtractor = null, + FailureDetectorInterface $failureDetector = null ) { $this->ganesha = $ganesha; $this->serviceNameExtractor = $serviceNameExtractor ?: new ServiceNameExtractor(); + $this->failureDetector = $failureDetector ?: new AlwaysSuccessFailureDetector(); } /** @@ -48,7 +57,11 @@ public function __invoke(callable $handler): \Closure return $promise->then( function ($value) use ($serviceName) { - $this->ganesha->success($serviceName); + if ($this->failureDetector->isFailureResponse($value)) { + $this->ganesha->failure($serviceName); + } else { + $this->ganesha->success($serviceName); + } return \GuzzleHttp\Promise\promise_for($value); }, function ($reason) use ($serviceName) { diff --git a/src/Ganesha/GuzzleMiddleware/AlwaysSuccessFailureDetector.php b/src/Ganesha/GuzzleMiddleware/AlwaysSuccessFailureDetector.php new file mode 100644 index 0000000..4201f90 --- /dev/null +++ b/src/Ganesha/GuzzleMiddleware/AlwaysSuccessFailureDetector.php @@ -0,0 +1,13 @@ +createMock(FailureDetectorInterface::class); + $failureDetector->expects($this->once())->method('isFailureResponse')->willReturn(true); + $client = $this->buildClient($failureDetector); + $response = $client->get('http://api.example.com/awesome_resource/200'); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame( + 1, + $this->adapter->load(Storage\StorageKeys::KEY_PREFIX . 'api.example.com' . Storage\StorageKeys::KEY_SUFFIX_FAILURE) + ); + $this->assertSame( + 0, + $this->adapter->load(Storage\StorageKeys::KEY_PREFIX . 'api.example.com' . Storage\StorageKeys::KEY_SUFFIX_SUCCESS) + ); + } + /** * @test */ @@ -162,7 +185,7 @@ public function reject() /** * @return Client */ - private function buildClient() + private function buildClient(?FailureDetectorInterface $failureDetector = null) { $ganesha = Builder::withRateStrategy() ->timeWindow(30) @@ -172,7 +195,8 @@ private function buildClient() ->adapter($this->adapter) ->build(); - $middleware = new GuzzleMiddleware($ganesha); + + $middleware = new GuzzleMiddleware($ganesha, null, $failureDetector); $handlers = HandlerStack::create(); $handlers->push($middleware);