Skip to content

Commit

Permalink
Merge pull request #100 from e-zannelli/feat-guzzle-failure-detector
Browse files Browse the repository at this point in the history
Add guzzle failure detector
  • Loading branch information
ackintosh authored Jan 5, 2023
2 parents 68792cb + 889434d commit 9579888
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 4 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 15 additions & 2 deletions src/Ganesha/GuzzleMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}

/**
Expand All @@ -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) {
Expand Down
13 changes: 13 additions & 0 deletions src/Ganesha/GuzzleMiddleware/AlwaysSuccessFailureDetector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Ackintosh\Ganesha\GuzzleMiddleware;

use Psr\Http\Message\ResponseInterface;

class AlwaysSuccessFailureDetector implements FailureDetectorInterface
{
public function isFailureResponse(ResponseInterface $response): bool
{
return false;
}
}
10 changes: 10 additions & 0 deletions src/Ganesha/GuzzleMiddleware/FailureDetectorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Ackintosh\Ganesha\GuzzleMiddleware;

use Psr\Http\Message\ResponseInterface;

interface FailureDetectorInterface
{
public function isFailureResponse(ResponseInterface $response): bool;
}
28 changes: 26 additions & 2 deletions tests/Ackintosh/Ganesha/GuzzleMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace Ackintosh\Ganesha;

use Ackintosh\Ganesha\Exception\RejectedException;
use Ackintosh\Ganesha\GuzzleMiddleware\FailureDetectorInterface;
use Ackintosh\Ganesha\Storage\Adapter\Memcached;
use Ackintosh\Ganesha\Storage\Adapter\Redis;
use GuzzleHttp\Client;
Expand Down Expand Up @@ -94,6 +95,28 @@ public function recordsSuccessOn500()
);
}

/**
* @test
* @vcr guzzle_responses.yml
*/
public function failureDetectorControlsIfResponseIsFailure()
{
$failureDetector = $this->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
*/
Expand Down Expand Up @@ -162,7 +185,7 @@ public function reject()
/**
* @return Client
*/
private function buildClient()
private function buildClient(?FailureDetectorInterface $failureDetector = null)
{
$ganesha = Builder::withRateStrategy()
->timeWindow(30)
Expand All @@ -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);

Expand Down

0 comments on commit 9579888

Please sign in to comment.