diff options
author | Ewan Dennis <ewandennis@users.noreply.github.com> | 2017-07-10 16:13:28 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-07-10 16:13:28 +0100 |
commit | 342ecf62947cfe3b31f2253fb427b1b4a613907b (patch) | |
tree | 0a7ddf7a0dee31d895479ce8af589477b93d8bd4 | |
parent | 4de9c54c7a3554192e50fee9734fa96bf3e7bec3 (diff) | |
parent | 7beccdecb04d24cd055c91cf9b652ccc58eeaf60 (diff) | |
download | php-sparkpost-342ecf62947cfe3b31f2253fb427b1b4a613907b.zip php-sparkpost-342ecf62947cfe3b31f2253fb427b1b4a613907b.tar.gz php-sparkpost-342ecf62947cfe3b31f2253fb427b1b4a613907b.tar.bz2 |
Merge pull request #169 from SparkPost/issue-168
Issue-168: Optional automatic retry on 5xx
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | examples/message-events/get_message_events_with_retry_logic.php | 33 | ||||
-rw-r--r-- | lib/SparkPost/SparkPost.php | 41 | ||||
-rw-r--r-- | test/unit/SparkPostTest.php | 76 |
4 files changed, 151 insertions, 4 deletions
@@ -102,6 +102,11 @@ $sparky = new SparkPost($httpClient, ['key'=>'YOUR_API_KEY']); * Type: `Boolean` * Default: `true` * `async` defines if the `request` function sends an asynchronous or synchronous request. If your client does not support async requests set this to `false` +* `options.retries` + * Required: No + * Type: `Number` + * Default: `0` + * `retries` controls how many API call attempts the client makes after receiving a 5xx response * `options.debug` * Required: No * Type: `Boolean` diff --git a/examples/message-events/get_message_events_with_retry_logic.php b/examples/message-events/get_message_events_with_retry_logic.php new file mode 100644 index 0000000..68225cb --- /dev/null +++ b/examples/message-events/get_message_events_with_retry_logic.php @@ -0,0 +1,33 @@ +<?php + +namespace Examples\Templates; + +require dirname(__FILE__).'/../bootstrap.php'; + +use SparkPost\SparkPost; +use GuzzleHttp\Client; +use Http\Adapter\Guzzle6\Client as GuzzleAdapter; + +$httpClient = new GuzzleAdapter(new Client()); + +$sparky = new SparkPost($httpClient, ["key" => "YOUR_API_KEY", "retries" => 3]); + +$promise = $sparky->request('GET', 'message-events', [ + 'campaign_ids' => 'CAMPAIGN_ID', +]); + +/** + * If this fails with a 5xx it will have failed 4 times + */ +try { + $response = $promise->wait(); + echo $response->getStatusCode()."\n"; + print_r($response->getBody())."\n"; +} catch (\Exception $e) { + echo $e->getCode()."\n"; + echo $e->getMessage()."\n"; + + if ($e->getCode() >= 500 && $e->getCode() <= 599) { + echo "Wow, this failed epically"; + } +} diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index 05be7f4..b3a9e36 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -41,6 +41,7 @@ class SparkPost 'version' => 'v1', 'async' => true, 'debug' => false, + 'retries' => 0 ]; /** @@ -97,13 +98,29 @@ class SparkPost $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); $request = call_user_func_array(array($this, 'buildRequestInstance'), $requestValues); + $retries = $this->options['retries']; try { - return new SparkPostResponse($this->httpClient->sendRequest($request), $this->ifDebug($requestValues)); + if ($retries > 0) { + $resp = $this->syncReqWithRetry($request, $retries); + } else { + $resp = $this->httpClient->sendRequest($request); + } + return new SparkPostResponse($resp, $this->ifDebug($requestValues)); } catch (\Exception $exception) { throw new SparkPostException($exception, $this->ifDebug($requestValues)); } } + private function syncReqWithRetry($request, $retries) + { + $resp = $this->httpClient->sendRequest($request); + $status = $resp->getStatusCode(); + if ($status >= 500 && $status <= 599 && $retries > 0) { + return $this->syncReqWithRetry($request, $retries-1); + } + return $resp; + } + /** * Sends async request to SparkPost API. * @@ -120,12 +137,28 @@ class SparkPost $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); $request = call_user_func_array(array($this, 'buildRequestInstance'), $requestValues); - return new SparkPostPromise($this->httpClient->sendAsyncRequest($request), $this->ifDebug($requestValues)); + $retries = $this->options['retries']; + if ($retries > 0) { + return new SparkPostPromise($this->asyncReqWithRetry($request, $retries), $this->ifDebug($requestValues)); + } else { + return new SparkPostPromise($this->httpClient->sendAsyncRequest($request), $this->ifDebug($requestValues)); + } } else { throw new \Exception('Your http client does not support asynchronous requests. Please use a different client or use synchronous requests.'); } } + private function asyncReqWithRetry($request, $retries) + { + return $this->httpClient->sendAsyncRequest($request)->then(function($response) use ($request, $retries) { + $status = $response->getStatusCode(); + if ($status >= 500 && $status <= 599 && $retries > 0) { + return $this->asyncReqWithRetry($request, $retries-1); + } + return $response; + }); + } + /** * Builds request values from given params. * @@ -252,7 +285,7 @@ class SparkPost } $this->httpClient = $httpClient; - + return $this; } @@ -283,7 +316,7 @@ class SparkPost $this->options[$option] = $value; } } - + return $this; } diff --git a/test/unit/SparkPostTest.php b/test/unit/SparkPostTest.php index cb139f6..89e8e3a 100644 --- a/test/unit/SparkPostTest.php +++ b/test/unit/SparkPostTest.php @@ -60,6 +60,13 @@ class SparkPostTest extends \PHPUnit_Framework_TestCase $this->responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($this->responseBody)); + $errorBodyMock = Mockery::mock(); + $this->badResponseBody = ['errors' => []]; + $this->badResponseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); + $this->badResponseMock->shouldReceive('getStatusCode')->andReturn(503); + $this->badResponseMock->shouldReceive('getBody')->andReturn($errorBodyMock); + $errorBodyMock->shouldReceive('__toString')->andReturn(json_encode($this->badResponseBody)); + // exception mock up $exceptionResponseMock = Mockery::mock(); $this->exceptionBody = ['results' => 'failed']; @@ -159,6 +166,35 @@ class SparkPostTest extends \PHPUnit_Framework_TestCase } } + public function testSuccessfulSyncRequestWithRetries() + { + $this->clientMock->shouldReceive('sendRequest')-> + with(Mockery::type('GuzzleHttp\Psr7\Request'))-> + andReturn($this->badResponseMock, $this->badResponseMock, $this->responseMock); + + $this->resource->setOptions(['retries' => 2]); + $response = $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); + + $this->assertEquals($this->responseBody, $response->getBody()); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testUnsuccessfulSyncRequestWithRetries() + { + $this->clientMock->shouldReceive('sendRequest')-> + once()-> + with(Mockery::type('GuzzleHttp\Psr7\Request'))-> + andThrow($this->exceptionMock); + + $this->resource->setOptions(['retries' => 2]); + try { + $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); + } catch (\Exception $e) { + $this->assertEquals($this->exceptionBody, $e->getBody()); + $this->assertEquals(500, $e->getCode()); + } + } + public function testSuccessfulAsyncRequestWithWait() { $this->promiseMock->shouldReceive('wait')->andReturn($this->responseMock); @@ -212,6 +248,46 @@ class SparkPostTest extends \PHPUnit_Framework_TestCase })->wait(); } + public function testSuccessfulAsyncRequestWithRetries() + { + $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); + $clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); + $clientMock->shouldReceive('sendAsyncRequest')-> + with(Mockery::type('GuzzleHttp\Psr7\Request'))-> + andReturn( + new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->badResponseMock), $testReq), + new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->badResponseMock), $testReq), + new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->responseMock), $testReq) + ); + + $resource = new SparkPost($clientMock, ['key' => 'SPARKPOST_API_KEY']); + + $resource->setOptions(['async' => true, 'retries' => 2]); + $promise = $resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); + $promise->then(function($resp) { + $this->assertEquals(200, $resp->getStatusCode()); + })->wait(); + } + + public function testUnsuccessfulAsyncRequestWithRetries() + { + $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); + $rejectedPromise = new GuzzleRejectedPromise($this->exceptionMock); + $clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); + $clientMock->shouldReceive('sendAsyncRequest')-> + with(Mockery::type('GuzzleHttp\Psr7\Request'))-> + andReturn(new GuzzleAdapterPromise($rejectedPromise, $testReq)); + + $resource = new SparkPost($clientMock, ['key' => 'SPARKPOST_API_KEY']); + + $resource->setOptions(['async' => true, 'retries' => 2]); + $promise = $resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); + $promise->then(null, function($exception) { + $this->assertEquals(500, $exception->getCode()); + $this->assertEquals($this->exceptionBody, $exception->getBody()); + })->wait(); + } + public function testPromise() { $promise = $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); |