1
1
<?php
2
2
3
3
/**
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
+ */
13
13
14
14
namespace SendGrid ;
15
15
27
27
*/
28
28
class Client
29
29
{
30
- /** @var string */
30
+ const TOO_MANY_REQUESTS_HTTP_CODE = 429 ;
31
+
32
+ /**
33
+ * @var string
34
+ */
31
35
protected $ host ;
32
- /** @var array */
36
+
37
+ /**
38
+ * @var array
39
+ */
33
40
protected $ headers ;
34
- /** @var string */
41
+
42
+ /**
43
+ * @var string
44
+ */
35
45
protected $ version ;
36
- /** @var array */
46
+
47
+ /**
48
+ * @var array
49
+ */
37
50
protected $ path ;
38
- /** @var array */
51
+
52
+ /**
53
+ * @var array
54
+ */
39
55
protected $ curlOptions ;
40
- /** @var bool $isConcurrentRequest */
56
+
57
+ /**
58
+ * @var bool
59
+ */
41
60
protected $ isConcurrentRequest ;
42
- /** @var array $savedRequests */
61
+
62
+ /**
63
+ * @var array
64
+ */
43
65
protected $ savedRequests ;
44
- /** @var bool */
66
+
67
+ /**
68
+ * @var bool
69
+ */
45
70
protected $ retryOnLimit ;
46
71
47
72
/**
48
73
* These are the supported HTTP verbs
49
74
*
50
75
* @var array
51
76
*/
52
- private $ methods = ['get ' , 'post ' , 'patch ' , 'put ' , 'delete ' ];
77
+ private $ methods = ['get ' , 'post ' , 'patch ' , 'put ' , 'delete ' ];
53
78
54
79
/**
55
80
* Initialize the client
@@ -105,6 +130,14 @@ public function getPath()
105
130
return $ this ->path ;
106
131
}
107
132
133
+ /**
134
+ * @return array
135
+ */
136
+ public function getCurlOptions ()
137
+ {
138
+ return $ this ->curlOptions ;
139
+ }
140
+
108
141
/**
109
142
* Set extra options to set during curl initialization
110
143
*
@@ -134,7 +167,7 @@ public function setRetryOnLimit($retry)
134
167
}
135
168
136
169
/**
137
- * set concurrent request flag
170
+ * Set concurrent request flag
138
171
*
139
172
* @param bool $isConcurrent
140
173
*
@@ -148,20 +181,12 @@ public function setIsConcurrentRequest($isConcurrent)
148
181
}
149
182
150
183
/**
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
152
189
*/
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
- */
165
190
private function buildUrl ($ queryParams = null )
166
191
{
167
192
$ path = '/ ' . implode ('/ ' , $ this ->path );
@@ -178,13 +203,14 @@ private function buildUrl($queryParams = null)
178
203
* @param string $method
179
204
* @param array $body
180
205
* @param array $headers
206
+ *
181
207
* @return array
182
208
*/
183
209
private function createCurlOptions ($ method , $ body = null , $ headers = null )
184
210
{
185
211
$ options = [
186
212
CURLOPT_RETURNTRANSFER => true ,
187
- CURLOPT_HEADER => 1 ,
213
+ CURLOPT_HEADER => true ,
188
214
CURLOPT_CUSTOMREQUEST => strtoupper ($ method ),
189
215
CURLOPT_SSL_VERIFYPEER => true ,
190
216
CURLOPT_FAILONERROR => false
@@ -213,7 +239,7 @@ private function createCurlOptions($method, $body = null, $headers = null)
213
239
*
214
240
* @return array
215
241
*/
216
- private function createSavedRequest ($ requestData , $ retryOnLimit = false )
242
+ private function createSavedRequest (array $ requestData , $ retryOnLimit = false )
217
243
{
218
244
return array_merge ($ requestData , ['retryOnLimit ' => $ retryOnLimit ]);
219
245
}
@@ -223,7 +249,7 @@ private function createSavedRequest($requestData, $retryOnLimit = false)
223
249
*
224
250
* @return array
225
251
*/
226
- private function createCurlMultiHandle ($ requests )
252
+ private function createCurlMultiHandle (array $ requests )
227
253
{
228
254
$ channels = [];
229
255
$ multiHandle = curl_multi_init ();
@@ -241,68 +267,72 @@ private function createCurlMultiHandle($requests)
241
267
/**
242
268
* Prepare response object
243
269
*
244
- * @param resource $curl the curl resource
270
+ * @param resource $channel the curl resource
271
+ * @param string $content
245
272
*
246
273
* @return Response object
247
274
*/
248
- private function prepareResponse ( $ curl )
275
+ private function parseResponse ( $ channel , $ content )
249
276
{
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 );
255
283
$ responseHeaders = explode ("\n" , $ responseHeaders );
256
284
$ responseHeaders = array_map ('trim ' , $ responseHeaders );
257
- $ response = new Response ( $ statusCode , $ responseBody , $ responseHeaders );
258
- return $ response ;
285
+
286
+ return new Response ( $ statusCode , $ responseBody , $ responseHeaders ) ;
259
287
}
260
-
288
+
261
289
/**
262
290
* Retry request
263
291
*
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
269
297
*
270
298
* @return Response response object
271
299
*/
272
- private function retryRequest ($ responseHeaders , $ method , $ url , $ body , $ headers )
300
+ private function retryRequest (array $ responseHeaders , $ method , $ url , $ body , $ headers )
273
301
{
274
302
$ sleepDurations = $ responseHeaders ['X-Ratelimit-Reset ' ] - time ();
275
303
sleep ($ sleepDurations > 0 ? $ sleepDurations : 0 );
276
304
return $ this ->makeRequest ($ method , $ url , $ body , $ headers , false );
277
305
}
278
306
279
307
/**
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
+ */
291
319
public function makeRequest ($ method , $ url , $ body = null , $ headers = null , $ retryOnLimit = false )
292
320
{
293
- $ curl = curl_init ($ url );
321
+ $ channel = curl_init ($ url );
294
322
295
- $ curlOpts = $ this ->createCurlOptions ($ method , $ body , $ headers );
323
+ $ options = $ this ->createCurlOptions ($ method , $ body , $ headers );
296
324
297
- curl_setopt_array ($ curl , $ curlOpts );
325
+ curl_setopt_array ($ channel , $ options );
326
+ $ content = curl_exec ($ channel );
298
327
299
- $ response = $ this ->prepareResponse ( $ curl );
328
+ $ response = $ this ->parseResponse ( $ channel , $ content );
300
329
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 );
303
333
}
304
334
305
- curl_close ($ curl );
335
+ curl_close ($ channel );
306
336
307
337
return $ response ;
308
338
}
@@ -311,9 +341,10 @@ public function makeRequest($method, $url, $body = null, $headers = null, $retry
311
341
* Send all saved requests at once
312
342
*
313
343
* @param array $requests
344
+ *
314
345
* @return Response[]
315
346
*/
316
- public function makeAllRequests ($ requests = [])
347
+ public function makeAllRequests (array $ requests = [])
317
348
{
318
349
if (empty ($ requests )) {
319
350
$ requests = $ this ->savedRequests ;
@@ -330,32 +361,26 @@ public function makeAllRequests($requests = [])
330
361
$ retryRequests = [];
331
362
$ responses = [];
332
363
$ 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 ' ]) {
345
370
$ headers = $ response ->headers (true );
346
371
$ sleepDurations = max ($ sleepDurations , $ headers ['X-Ratelimit-Reset ' ] - time ());
347
372
$ requestData = [
348
373
'method ' => $ requests [$ id ]['method ' ],
349
374
'url ' => $ requests [$ id ]['url ' ],
350
375
'body ' => $ requests [$ id ]['body ' ],
351
- 'headers ' =>$ headers ,
376
+ 'headers ' => $ headers ,
352
377
];
353
378
$ retryRequests [] = $ this ->createSavedRequest ($ requestData , false );
354
379
} else {
355
380
$ responses [] = $ response ;
356
381
}
357
382
358
- curl_multi_remove_handle ($ multiHandle , $ ch );
383
+ curl_multi_remove_handle ($ multiHandle , $ channel );
359
384
}
360
385
curl_multi_close ($ multiHandle );
361
386
@@ -368,15 +393,13 @@ public function makeAllRequests($requests = [])
368
393
}
369
394
370
395
/**
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
+ */
380
403
public function _ ($ name = null )
381
404
{
382
405
if (isset ($ name )) {
@@ -391,14 +414,14 @@ public function _($name = null)
391
414
}
392
415
393
416
/**
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
+ */
402
425
public function __call ($ name , $ args )
403
426
{
404
427
$ name = strtolower ($ name );
@@ -422,10 +445,8 @@ public function __call($name, $args)
422
445
423
446
if ($ this ->isConcurrentRequest ) {
424
447
// 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 );
429
450
return null ;
430
451
}
431
452
0 commit comments