Skip to content
This repository has been archived by the owner on Aug 19, 2023. It is now read-only.

Commit

Permalink
Added response handler to retry 429. bunq#109
Browse files Browse the repository at this point in the history
  • Loading branch information
OGKevin committed Mar 16, 2019
1 parent 2780425 commit 506ffdd
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 1 deletion.
32 changes: 31 additions & 1 deletion 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,33 @@ 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();
}

$body = $request->getBody()->getContents();

if (!$this->isBinary || !empty($body)) {
$body = json_decode($body, true);
}

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

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

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

return $handlerStack;
}
Expand Down
48 changes: 48 additions & 0 deletions src/Http/Handler/ResponseHandlerRateLimit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php declare(strict_types=1);


namespace bunq\Http\Handler;


use bunq\Http\ApiClient;
use phpDocumentor\Reflection\Types\This;
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 RequestRetryert
*/
private $retryer;

/**
* ResponseHandlerRateLimit constructor.
* @param ApiClient $client
*/
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) {
usleep(1500);

This comment has been minimized.

Copy link
@holtkamp

holtkamp Mar 17, 2019

Does the Exception / response not indicate a time to "wait"?

It not: the current way of "static" backing of seems not right. Usually it is dynamic: 1 second, 2 seconds, 4 seconds, 16 seconds, etc.

This comment has been minimized.

Copy link
@OGKevin

OGKevin Mar 17, 2019

Author Owner

This is an old version, Ive already updated the code. See

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;
}

This comment has been minimized.

Copy link
@OGKevin

OGKevin Mar 17, 2019

Author Owner

and to answer your question, no, bunq does not say how long you should wait or any of that. They only return a plain and simple 429 with an inaccurate estimate of what the rate limit is. E.g. 1 call per 3 sec etc.

return $this->retryer->retryRequest($request);
}

return $response;
}
}
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;
}
25 changes: 25 additions & 0 deletions tests/Http/RequestRertyerForTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?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
{
/**
* @param RequestInterface $request
* @return ResponseInterface
*/
public function retryRequest(RequestInterface $request): ResponseInterface
{
return new Response();
}
}
33 changes: 33 additions & 0 deletions tests/Http/ResponseHandlerRateLimitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types=1);

namespace bunq\test\Http;

use bunq\Http\Handler\ResponseHandlerRateLimit;
use bunq\test\BunqSdkTestBase;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;

/**
* Class ResponseHandlerRateLimitTest
* @package bunq\test\Http
*/
class ResponseHandlerRateLimitTest extends BunqSdkTestBase
{
/**
*/
public function testExecute()
{
$sut = new ResponseHandlerRateLimit(new RequestRertyerForTest());

$response = $sut->execute(new Response(429), new Request('GET', 'https://whatthecommit.com/index.txt'));

static::assertEquals(200, $response->getStatusCode());
}

/**
*/
public function testActualRequest()
{
static::markTestSkipped('how do we want to test this');
}
}

0 comments on commit 506ffdd

Please sign in to comment.