Skip to content

Commit 65732c1

Browse files
authored
Merge pull request #6 from cloudloyalty/curl_client
Added CurlClient, the client based on curl PHP extension
2 parents 88d680e + 850360d commit 65732c1

3 files changed

Lines changed: 192 additions & 3 deletions

File tree

lib/Client.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use CloudLoyalty\Api\Generated\Model\V2CalculatePurchaseResponse;
3232
use CloudLoyalty\Api\Generated\Model\V2SetOrderRequest;
3333
use CloudLoyalty\Api\Generated\Model\V2SetOrderResponse;
34+
use CloudLoyalty\Api\Http\Client\CurlClient;
3435
use CloudLoyalty\Api\Http\Request;
3536
use CloudLoyalty\Api\Exception\ProcessingException;
3637
use CloudLoyalty\Api\Exception\TransportException;
@@ -114,7 +115,9 @@ public function __construct(
114115
$this->setAcceptLanguage($config['acceptLanguage']);
115116
}
116117
if ($httpClient === null) {
117-
$httpClient = new NativeClient();
118+
$httpClient = function_exists('curl_init')
119+
? new CurlClient()
120+
: new NativeClient();
118121
}
119122
$this->httpClient = $httpClient;
120123
if ($serializer === null) {

lib/Http/Client/CurlClient.php

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php
2+
3+
namespace CloudLoyalty\Api\Http\Client;
4+
5+
use CloudLoyalty\Api\Http\ClientInterface;
6+
use CloudLoyalty\Api\Http\RequestInterface;
7+
use CloudLoyalty\Api\Http\Response;
8+
use CloudLoyalty\Api\Http\ResponseInterface;
9+
use CloudLoyalty\Api\Exception\TransportException;
10+
11+
class CurlClient implements ClientInterface
12+
{
13+
const DEFAULT_TIMEOUT = 5; // seconds
14+
15+
/**
16+
* @var float
17+
*/
18+
protected $timeout = self::DEFAULT_TIMEOUT;
19+
20+
/**
21+
* @var resource
22+
*/
23+
protected $curl;
24+
25+
26+
/**
27+
* @param array $config
28+
*/
29+
public function __construct(array $config = [])
30+
{
31+
if (isset($config['timeout'])) {
32+
$this->timeout = floatval($config['timeout']);
33+
}
34+
$this->curl = \curl_init();
35+
}
36+
37+
public function __destruct()
38+
{
39+
if ($this->curl) {
40+
\curl_close($this->curl);
41+
}
42+
}
43+
44+
/**
45+
* @param RequestInterface $request
46+
* @return ResponseInterface
47+
* @throws TransportException
48+
*/
49+
public function sendRequest(RequestInterface $request)
50+
{
51+
$headers = $request->getHeaders();
52+
53+
\curl_setopt_array(
54+
$this->curl,
55+
[
56+
\CURLOPT_URL => $request->getUri(),
57+
\CURLOPT_TIMEOUT => $this->timeout,
58+
\CURLOPT_PROTOCOLS => \CURLPROTO_HTTP|\CURLPROTO_HTTPS,
59+
\CURLOPT_CUSTOMREQUEST => $request->getMethod(),
60+
\CURLOPT_RETURNTRANSFER => true,
61+
\CURLOPT_HTTPHEADER => $this->joinHeaders($headers),
62+
\CURLOPT_POSTFIELDS => $request->getBody(),
63+
\CURLOPT_FOLLOWLOCATION => false,
64+
\CURLOPT_HEADER => true,
65+
]
66+
);
67+
68+
$response = \curl_exec($this->curl);
69+
70+
// For any response code curl_exec returns a header+body string
71+
// For networking errors curl_exec returns false, curl_errno and curl_error return the error
72+
73+
// A networking error
74+
if ($response === false) {
75+
throw new TransportException(\curl_error($this->curl), \curl_errno($this->curl));
76+
}
77+
78+
$statusCode = \curl_getinfo($this->curl, \CURLINFO_HTTP_CODE);
79+
$reasonPhrase = $this->getReasonPhrase($statusCode);
80+
81+
// 400-599 codes
82+
if ($statusCode >= 400) {
83+
throw new TransportException(
84+
'HTTP request finished with status ' . $statusCode . ' ' . $reasonPhrase,
85+
$statusCode
86+
);
87+
}
88+
89+
$headerSize = \curl_getinfo($this->curl, \CURLINFO_HEADER_SIZE);
90+
$header = substr($response, 0, $headerSize);
91+
$body = substr($response, $headerSize);
92+
93+
$headersRaw = explode("\n", $header);
94+
$headersRaw = array_map('trim', $headersRaw); // remove possible \r's
95+
$headersRaw = array_filter($headersRaw); // remove newlines
96+
array_shift($headersRaw); // remove HTTP code line
97+
98+
$headers = [];
99+
foreach ($headersRaw as $headerLine) {
100+
list($header, $value) = explode(':', $headerLine, 2);
101+
$headers[$header] = ltrim($value, ' ');
102+
}
103+
104+
return (new Response())
105+
->setStatusCode($statusCode)
106+
->setReasonPhrase($reasonPhrase)
107+
->setHeaders($headers)
108+
->setBody($body ?: '');
109+
}
110+
111+
private function joinHeaders($headers)
112+
{
113+
$joinedHeaders = [];
114+
foreach ($headers as $key => $value) {
115+
$joinedHeaders[] = $key . ': ' . $value; // $value expected to be url-encoded
116+
}
117+
return $joinedHeaders;
118+
}
119+
120+
private function getReasonPhrase($statusCode)
121+
{
122+
static $phrase = [
123+
100 => 'Continue',
124+
101 => 'Switching Protocols',
125+
102 => 'Processing',
126+
200 => 'OK',
127+
201 => 'Created',
128+
202 => 'Accepted',
129+
203 => 'Non-Authoritative Information',
130+
204 => 'No Content',
131+
205 => 'Reset Content',
132+
206 => 'Partial Content',
133+
207 => 'Multi-status',
134+
208 => 'Already Reported',
135+
300 => 'Multiple Choices',
136+
301 => 'Moved Permanently',
137+
302 => 'Found',
138+
303 => 'See Other',
139+
304 => 'Not Modified',
140+
305 => 'Use Proxy',
141+
306 => 'Switch Proxy',
142+
307 => 'Temporary Redirect',
143+
308 => 'Permanent Redirect',
144+
400 => 'Bad Request',
145+
401 => 'Unauthorized',
146+
402 => 'Payment Required',
147+
403 => 'Forbidden',
148+
404 => 'Not Found',
149+
405 => 'Method Not Allowed',
150+
406 => 'Not Acceptable',
151+
407 => 'Proxy Authentication Required',
152+
408 => 'Request Time-out',
153+
409 => 'Conflict',
154+
410 => 'Gone',
155+
411 => 'Length Required',
156+
412 => 'Precondition Failed',
157+
413 => 'Request Entity Too Large',
158+
414 => 'Request-URI Too Large',
159+
415 => 'Unsupported Media Type',
160+
416 => 'Requested range not satisfiable',
161+
417 => 'Expectation Failed',
162+
418 => 'I\'m a teapot',
163+
422 => 'Unprocessable Entity',
164+
423 => 'Locked',
165+
424 => 'Failed Dependency',
166+
425 => 'Unordered Collection',
167+
426 => 'Upgrade Required',
168+
428 => 'Precondition Required',
169+
429 => 'Too Many Requests',
170+
431 => 'Request Header Fields Too Large',
171+
451 => 'Unavailable For Legal Reasons',
172+
500 => 'Internal Server Error',
173+
501 => 'Not Implemented',
174+
502 => 'Bad Gateway',
175+
503 => 'Service Unavailable',
176+
504 => 'Gateway Time-out',
177+
505 => 'HTTP Version not supported',
178+
506 => 'Variant Also Negotiates',
179+
507 => 'Insufficient Storage',
180+
508 => 'Loop Detected',
181+
510 => 'Not Extended',
182+
511 => 'Network Authentication Required',
183+
];
184+
185+
return isset($phrase[$statusCode]) ? $phrase[$statusCode] : '';
186+
}
187+
}

lib/Http/Client/NativeClient.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,7 @@ private function joinHeaders($headers)
8888
{
8989
$joinedHeaders = [];
9090
foreach ($headers as $key => $value) {
91-
// FIXME: should it be encoded somehow?
92-
$joinedHeaders[] = $key . ': ' . $value;
91+
$joinedHeaders[] = $key . ': ' . $value; // $value expected to be url-encoded
9392
}
9493
return $joinedHeaders;
9594
}

0 commit comments

Comments
 (0)