Skip to content

Commit c1682b1

Browse files
Merge pull request #95 from misantron/issue94-patch
CreateCurlOptions method regression tests
2 parents 08fe96a + a952b54 commit c1682b1

File tree

5 files changed

+237
-139
lines changed

5 files changed

+237
-139
lines changed

lib/Client.php

+126-105
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
<?php
22

33
/**
4-
* HTTP Client library
5-
*
6-
* @author Matt Bernier <[email protected]>
7-
* @author Elmer Thomas <[email protected]>
8-
* @copyright 2018 SendGrid
9-
* @license https://opensource.org/licenses/MIT The MIT License
10-
* @version GIT: <git_id>
11-
* @link http://packagist.org/packages/sendgrid/php-http-client
12-
*/
4+
* HTTP Client library
5+
*
6+
* @author Matt Bernier <[email protected]>
7+
* @author Elmer Thomas <[email protected]>
8+
* @copyright 2018 SendGrid
9+
* @license https://opensource.org/licenses/MIT The MIT License
10+
* @version GIT: <git_id>
11+
* @link http://packagist.org/packages/sendgrid/php-http-client
12+
*/
1313

1414
namespace SendGrid;
1515

@@ -27,29 +27,54 @@
2727
*/
2828
class Client
2929
{
30-
/** @var string */
30+
const TOO_MANY_REQUESTS_HTTP_CODE = 429;
31+
32+
/**
33+
* @var string
34+
*/
3135
protected $host;
32-
/** @var array */
36+
37+
/**
38+
* @var array
39+
*/
3340
protected $headers;
34-
/** @var string */
41+
42+
/**
43+
* @var string
44+
*/
3545
protected $version;
36-
/** @var array */
46+
47+
/**
48+
* @var array
49+
*/
3750
protected $path;
38-
/** @var array */
51+
52+
/**
53+
* @var array
54+
*/
3955
protected $curlOptions;
40-
/** @var bool $isConcurrentRequest */
56+
57+
/**
58+
* @var bool
59+
*/
4160
protected $isConcurrentRequest;
42-
/** @var array $savedRequests */
61+
62+
/**
63+
* @var array
64+
*/
4365
protected $savedRequests;
44-
/** @var bool */
66+
67+
/**
68+
* @var bool
69+
*/
4570
protected $retryOnLimit;
4671

4772
/**
4873
* These are the supported HTTP verbs
4974
*
5075
* @var array
5176
*/
52-
private $methods = ['get', 'post', 'patch', 'put', 'delete'];
77+
private $methods = ['get', 'post', 'patch', 'put', 'delete'];
5378

5479
/**
5580
* Initialize the client
@@ -105,6 +130,14 @@ public function getPath()
105130
return $this->path;
106131
}
107132

133+
/**
134+
* @return array
135+
*/
136+
public function getCurlOptions()
137+
{
138+
return $this->curlOptions;
139+
}
140+
108141
/**
109142
* Set extra options to set during curl initialization
110143
*
@@ -134,7 +167,7 @@ public function setRetryOnLimit($retry)
134167
}
135168

136169
/**
137-
* set concurrent request flag
170+
* Set concurrent request flag
138171
*
139172
* @param bool $isConcurrent
140173
*
@@ -148,20 +181,12 @@ public function setIsConcurrentRequest($isConcurrent)
148181
}
149182

150183
/**
151-
* @return array
184+
* Build the final URL to be passed
185+
*
186+
* @param array $queryParams an array of all the query parameters
187+
*
188+
* @return string
152189
*/
153-
public function getCurlOptions()
154-
{
155-
return $this->curlOptions;
156-
}
157-
158-
/**
159-
* Build the final URL to be passed
160-
*
161-
* @param array $queryParams an array of all the query parameters
162-
*
163-
* @return string
164-
*/
165190
private function buildUrl($queryParams = null)
166191
{
167192
$path = '/' . implode('/', $this->path);
@@ -178,13 +203,14 @@ private function buildUrl($queryParams = null)
178203
* @param string $method
179204
* @param array $body
180205
* @param array $headers
206+
*
181207
* @return array
182208
*/
183209
private function createCurlOptions($method, $body = null, $headers = null)
184210
{
185211
$options = [
186212
CURLOPT_RETURNTRANSFER => true,
187-
CURLOPT_HEADER => 1,
213+
CURLOPT_HEADER => true,
188214
CURLOPT_CUSTOMREQUEST => strtoupper($method),
189215
CURLOPT_SSL_VERIFYPEER => true,
190216
CURLOPT_FAILONERROR => false
@@ -213,7 +239,7 @@ private function createCurlOptions($method, $body = null, $headers = null)
213239
*
214240
* @return array
215241
*/
216-
private function createSavedRequest($requestData, $retryOnLimit = false)
242+
private function createSavedRequest(array $requestData, $retryOnLimit = false)
217243
{
218244
return array_merge($requestData, ['retryOnLimit' => $retryOnLimit]);
219245
}
@@ -223,7 +249,7 @@ private function createSavedRequest($requestData, $retryOnLimit = false)
223249
*
224250
* @return array
225251
*/
226-
private function createCurlMultiHandle($requests)
252+
private function createCurlMultiHandle(array $requests)
227253
{
228254
$channels = [];
229255
$multiHandle = curl_multi_init();
@@ -241,68 +267,72 @@ private function createCurlMultiHandle($requests)
241267
/**
242268
* Prepare response object
243269
*
244-
* @param resource $curl the curl resource
270+
* @param resource $channel the curl resource
271+
* @param string $content
245272
*
246273
* @return Response object
247274
*/
248-
private function prepareResponse($curl)
275+
private function parseResponse($channel, $content)
249276
{
250-
$response = curl_exec($curl);
251-
$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
252-
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
253-
$responseBody = substr($response, $headerSize);
254-
$responseHeaders = substr($response, 0, $headerSize);
277+
$headerSize = curl_getinfo($channel, CURLINFO_HEADER_SIZE);
278+
$statusCode = curl_getinfo($channel, CURLINFO_HTTP_CODE);
279+
280+
$responseBody = substr($content, $headerSize);
281+
282+
$responseHeaders = substr($content, 0, $headerSize);
255283
$responseHeaders = explode("\n", $responseHeaders);
256284
$responseHeaders = array_map('trim', $responseHeaders);
257-
$response = new Response($statusCode, $responseBody, $responseHeaders);
258-
return $response;
285+
286+
return new Response($statusCode, $responseBody, $responseHeaders);
259287
}
260-
288+
261289
/**
262290
* Retry request
263291
*
264-
* @param array $responseHeaders headers from rate limited response
265-
* @param string $method the HTTP verb
266-
* @param string $url the final url to call
267-
* @param array $body request body
268-
* @param array $headers original headers
292+
* @param array $responseHeaders headers from rate limited response
293+
* @param string $method the HTTP verb
294+
* @param string $url the final url to call
295+
* @param array $body request body
296+
* @param array $headers original headers
269297
*
270298
* @return Response response object
271299
*/
272-
private function retryRequest($responseHeaders, $method, $url, $body, $headers)
300+
private function retryRequest(array $responseHeaders, $method, $url, $body, $headers)
273301
{
274302
$sleepDurations = $responseHeaders['X-Ratelimit-Reset'] - time();
275303
sleep($sleepDurations > 0 ? $sleepDurations : 0);
276304
return $this->makeRequest($method, $url, $body, $headers, false);
277305
}
278306

279307
/**
280-
* Make the API call and return the response. This is separated into
281-
* it's own function, so we can mock it easily for testing.
282-
*
283-
* @param string $method the HTTP verb
284-
* @param string $url the final url to call
285-
* @param array|\JsonSerializable $body request body
286-
* @param array $headers any additional request headers
287-
* @param bool $retryOnLimit should retry if rate limit is reach?
288-
*
289-
* @return Response object
290-
*/
308+
* Make the API call and return the response.
309+
* This is separated into it's own function, so we can mock it easily for testing.
310+
*
311+
* @param string $method the HTTP verb
312+
* @param string $url the final url to call
313+
* @param array $body request body
314+
* @param array $headers any additional request headers
315+
* @param bool $retryOnLimit should retry if rate limit is reach?
316+
*
317+
* @return Response object
318+
*/
291319
public function makeRequest($method, $url, $body = null, $headers = null, $retryOnLimit = false)
292320
{
293-
$curl = curl_init($url);
321+
$channel = curl_init($url);
294322

295-
$curlOpts = $this->createCurlOptions($method, $body, $headers);
323+
$options = $this->createCurlOptions($method, $body, $headers);
296324

297-
curl_setopt_array($curl, $curlOpts);
325+
curl_setopt_array($channel, $options);
326+
$content = curl_exec($channel);
298327

299-
$response = $this->prepareResponse($curl);
328+
$response = $this->parseResponse($channel, $content);
300329

301-
if ($response->statusCode() == 429 && $retryOnLimit) {
302-
return $this->retryRequest($response->headers(true), $method, $url, $body, $headers);
330+
if ($response->statusCode() === self::TOO_MANY_REQUESTS_HTTP_CODE && $retryOnLimit) {
331+
$responseHeaders = $response->headers(true);
332+
return $this->retryRequest($responseHeaders, $method, $url, $body, $headers);
303333
}
304334

305-
curl_close($curl);
335+
curl_close($channel);
306336

307337
return $response;
308338
}
@@ -311,9 +341,10 @@ public function makeRequest($method, $url, $body = null, $headers = null, $retry
311341
* Send all saved requests at once
312342
*
313343
* @param array $requests
344+
*
314345
* @return Response[]
315346
*/
316-
public function makeAllRequests($requests = [])
347+
public function makeAllRequests(array $requests = [])
317348
{
318349
if (empty($requests)) {
319350
$requests = $this->savedRequests;
@@ -330,32 +361,26 @@ public function makeAllRequests($requests = [])
330361
$retryRequests = [];
331362
$responses = [];
332363
$sleepDurations = 0;
333-
foreach ($channels as $id => $ch) {
334-
$response = curl_multi_getcontent($ch);
335-
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
336-
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
337-
$responseBody = substr($response, $headerSize);
338-
339-
$responseHeaders = substr($response, 0, $headerSize);
340-
$responseHeaders = explode("\n", $responseHeaders);
341-
$responseHeaders = array_map('trim', $responseHeaders);
342-
343-
$response = new Response($statusCode, $responseBody, $responseHeaders);
344-
if (($statusCode === 429) && $requests[$id]['retryOnLimit']) {
364+
foreach ($channels as $id => $channel) {
365+
366+
$content = curl_multi_getcontent($channel);
367+
$response = $this->parseResponse($channel, $content);
368+
369+
if ($response->statusCode() === self::TOO_MANY_REQUESTS_HTTP_CODE && $requests[$id]['retryOnLimit']) {
345370
$headers = $response->headers(true);
346371
$sleepDurations = max($sleepDurations, $headers['X-Ratelimit-Reset'] - time());
347372
$requestData = [
348373
'method' => $requests[$id]['method'],
349374
'url' => $requests[$id]['url'],
350375
'body' => $requests[$id]['body'],
351-
'headers' =>$headers,
376+
'headers' => $headers,
352377
];
353378
$retryRequests[] = $this->createSavedRequest($requestData, false);
354379
} else {
355380
$responses[] = $response;
356381
}
357382

358-
curl_multi_remove_handle($multiHandle, $ch);
383+
curl_multi_remove_handle($multiHandle, $channel);
359384
}
360385
curl_multi_close($multiHandle);
361386

@@ -368,15 +393,13 @@ public function makeAllRequests($requests = [])
368393
}
369394

370395
/**
371-
* Add variable values to the url.
372-
* (e.g. /your/api/{variable_value}/call)
373-
* Another example: if you have a PHP reserved word, such as and,
374-
* in your url, you must use this method.
375-
*
376-
* @param string $name name of the url segment
377-
*
378-
* @return Client object
379-
*/
396+
* Add variable values to the url. (e.g. /your/api/{variable_value}/call)
397+
* Another example: if you have a PHP reserved word, such as and, in your url, you must use this method.
398+
*
399+
* @param string $name name of the url segment
400+
*
401+
* @return Client object
402+
*/
380403
public function _($name = null)
381404
{
382405
if (isset($name)) {
@@ -391,14 +414,14 @@ public function _($name = null)
391414
}
392415

393416
/**
394-
* Dynamically add method calls to the url, then call a method.
395-
* (e.g. client.name.name.method())
396-
*
397-
* @param string $name name of the dynamic method call or HTTP verb
398-
* @param array $args parameters passed with the method call
399-
*
400-
* @return Client|Response|Response[]|null object
401-
*/
417+
* Dynamically add method calls to the url, then call a method.
418+
* (e.g. client.name.name.method())
419+
*
420+
* @param string $name name of the dynamic method call or HTTP verb
421+
* @param array $args parameters passed with the method call
422+
*
423+
* @return Client|Response|Response[]|null object
424+
*/
402425
public function __call($name, $args)
403426
{
404427
$name = strtolower($name);
@@ -422,10 +445,8 @@ public function __call($name, $args)
422445

423446
if ($this->isConcurrentRequest) {
424447
// save request to be sent later
425-
$this->savedRequests[] = $this->createSavedRequest(
426-
['method' => $name, 'url' => $url, 'body' => $body, 'headers' => $headers],
427-
$retryOnLimit
428-
);
448+
$requestData = ['method' => $name, 'url' => $url, 'body' => $body, 'headers' => $headers];
449+
$this->savedRequests[] = $this->createSavedRequest($requestData, $retryOnLimit);
429450
return null;
430451
}
431452

0 commit comments

Comments
 (0)