Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Add 429 response handler #174

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,4 @@ tmp/

# PhpCs
.php_cs*
.idea
20 changes: 20 additions & 0 deletions development/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM php:7.2-cli

RUN apt-get update -yqq \
&& apt-get install -yqq \
# install ping and netcat (for debugging xdebug connectivity)
iputils-ping netcat \
wget \
;
RUN pecl config-set php_ini /usr/local/etc/php/php.ini \
&& pecl install xdebug
COPY ./development/php.ini /usr/local/etc/php/php.ini

RUN wget https://phar.phpunit.de/phpunit-6.5.phar \
&& chmod +x phpunit-6.5.phar \
&& mv phpunit-6.5.phar /usr/local/bin/phpunit \
&& phpunit --version

EXPOSE 9000

CMD ["bin/sh"]
1 change: 1 addition & 0 deletions development/php.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20170718/xdebug.so
12 changes: 12 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: "3"

services:

php_dev:
# this service can be used to develop with docker image in phpstorm
# this way you do not need to install php on your system
build:
context: ./
dockerfile: ./development/Dockerfile
volumes:
- .:/var/www/app:rw
39 changes: 34 additions & 5 deletions src/Http/ApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@
use bunq\Http\Handler\RequestHandlerEncryption;
use bunq\Http\Handler\RequestHandlerSignature;
use bunq\Http\Handler\ResponseHandlerError;
use bunq\Http\Handler\ResponseHandlerRateLimit;
use bunq\Http\Handler\ResponseHandlerSignature;
use bunq\Util\BunqEnumApiEnvironmentType;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
*/
class ApiClient
class ApiClient implements RequestRetryer
{
/**
* Error constants.
Expand Down Expand Up @@ -235,6 +237,28 @@ private function request(
return $this->createBunqResponseRaw($response);
}

/**
* @param RequestInterface $request
* @return ResponseInterface
*/
public function retryRequest(RequestInterface $request): ResponseInterface
{
$this->initialize($request->getUri());

if ($request->getBody()->isSeekable()) {
$request->getBody()->rewind();
}

return $this->httpClient->request(
$request->getMethod(),
$request->getUri(),
$this->determineRequestOptions(
$request->getBody()->getContents(),
$request->getHeaders()
)
);
}

/**
*/
private function initialize(string $uri)
Expand Down Expand Up @@ -298,6 +322,7 @@ private function determineMiddleware(): HandlerStack
}

$handlerStack->push(HandlerUtil::applyResponseHandler(new ResponseHandlerError()));
$handlerStack->push(HandlerUtil::applyResponseHandler(new ResponseHandlerRateLimit($this)));

return $handlerStack;
}
Expand Down Expand Up @@ -398,13 +423,17 @@ protected function determineBodyString($body): string
{
if ($this->isBinary) {
return $body;
} elseif (empty($body)) {
}

if (empty($body)) {
return self::BODY_EMPTY;
} else {
$bodyString = json_encode($body);
}

return $bodyString;
if (is_array($body)) {
return json_encode($body);
}

return $body;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/Http/Handler/HandlerUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ public static function applyResponseHandler(ResponseHandlerBase $responseHandler
return function (callable $handler) use ($responseHandler) {
return function (RequestInterface $request, array $options) use ($handler, $responseHandler) {
return $handler($request, $options)->then(
function (ResponseInterface $response) use ($responseHandler) {
return $responseHandler->execute($response);
function (ResponseInterface $response) use ($responseHandler, $request) {
return $responseHandler->execute($response, $request);
}
);
};
Expand Down
4 changes: 3 additions & 1 deletion src/Http/Handler/ResponseHandlerBase.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace bunq\Http\Handler;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
Expand All @@ -9,8 +10,9 @@ abstract class ResponseHandlerBase extends HandlerBase
{
/**
* @param ResponseInterface $response
* @param RequestInterface $request
*
* @return ResponseInterface
*/
abstract public function execute(ResponseInterface $response): ResponseInterface;
abstract public function execute(ResponseInterface $response, RequestInterface $request): ResponseInterface;
}
5 changes: 4 additions & 1 deletion src/Http/Handler/ResponseHandlerError.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use bunq\Exception\ApiException;
use bunq\Exception\BunqException;
use bunq\Exception\ExceptionFactory;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
Expand Down Expand Up @@ -41,11 +42,13 @@ class ResponseHandlerError extends ResponseHandlerBase

/**
* @param ResponseInterface $response
* @param RequestInterface $request
*
* @return ResponseInterface
* @throws ApiException when something goes wrong on the API side.
* @throws BunqException
*/
public function execute(ResponseInterface $response): ResponseInterface
public function execute(ResponseInterface $response, RequestInterface $request): ResponseInterface
{
if ($response->getStatusCode() !== self::STATUS_CODE_OK) {
throw ExceptionFactory::createExceptionForResponse(
Expand Down
69 changes: 69 additions & 0 deletions src/Http/Handler/ResponseHandlerRateLimit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);

namespace bunq\Http\Handler;

use bunq\Http\RequestRetryer;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
* Class ResponseHandlerRateLimit
* @package bunq\Http\Handler
*/
class ResponseHandlerRateLimit extends ResponseHandlerBase
{
/**
* @var RequestRetryer
*/
private $retryer;

/**
* @var string[]
*/
private $retryMap;

/**
* ResponseHandlerRateLimit constructor.
* @param RequestRetryer $retryer
*/
public function __construct(RequestRetryer $retryer)
{
$this->retryer = $retryer;
}

/**
* @param ResponseInterface $response
* @param RequestInterface $request
*
* @return ResponseInterface
*/
public function execute(ResponseInterface $response, RequestInterface $request): ResponseInterface
{
if ($response->getStatusCode() === 429) {
$this->incRetryCounter($request->getUri()->__toString());
if ($this->retryMap[$request->getUri()->__toString()] > 2) {
// let the error handler further down the stack handle the 429 error
return $response;
}
usleep(1100 * $this->retryMap[$request->getUri()->__toString()]);
return $this->retryer->retryRequest($request);
}

$this->retryMap[$request->getUri()->__toString()] = 0;

return $response;
}

/**
* @param string $url
*/
private function incRetryCounter(string $url)
{
if (isset($this->retryMap[$url])) {
$this->retryMap[$url]++;
} else {
$this->retryMap[$url] = 1;
}
}
}
4 changes: 3 additions & 1 deletion src/Http/Handler/ResponseHandlerSignature.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use bunq\Exception\SecurityException;
use bunq\Security\PublicKey;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
Expand Down Expand Up @@ -61,11 +62,12 @@ public function __construct(PublicKey $publicKeyServer = null)

/**
* @param ResponseInterface $response
* @param RequestInterface $request
*
* @return ResponseInterface
* @throws SecurityException when the response verification fails.
*/
public function execute(ResponseInterface $response): ResponseInterface
public function execute(ResponseInterface $response, RequestInterface $request): ResponseInterface
{
if ($response->getStatusCode() === self::STATUS_CODE_OK) {
if (is_null($this->publicKeyServer)) {
Expand Down
19 changes: 19 additions & 0 deletions src/Http/RequestRetryer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php declare(strict_types=1);

namespace bunq\Http;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
* Interface RequestRertier
* @package bunq\Http
*/
interface RequestRetryer
{
/**
* @param RequestInterface $request
* @return ResponseInterface
*/
public function retryRequest(RequestInterface $request): ResponseInterface;
}
60 changes: 60 additions & 0 deletions tests/Http/RequestRertyerForTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);


namespace bunq\test\Http;

use bunq\Http\RequestRetryer;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
* Class RequesRertyerForTest
* @package bunq\test\Http
*/
class RequestRertyerForTest implements RequestRetryer
{
/**
* @var bool
*/
private $retry;

/**
* @var int
*/
private $calledCounter = 0;

/**
* RequestRertyerForTest constructor.
* @param bool $retry
*/
public function __construct(bool $retry = false)
{
$this->retry = $retry;
}

/**
* @param RequestInterface $request
* @return ResponseInterface
*/
public function retryRequest(RequestInterface $request): ResponseInterface
{
$this->calledCounter++;

if ($this->retry) {
$this->retry = false;
return $this->retryRequest($request);
}

return new Response();
}

/**
* @return int
*/
public function getCalledCounter(): int
{
return $this->calledCounter;
}
}
Loading