Skip to content

Commit 6dfea60

Browse files
committed
#3: Add async support to the cURL client
1 parent 507eb46 commit 6dfea60

16 files changed

+1007
-172
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"php": ">=5.5",
1515
"ext-curl": "*",
1616
"php-http/httplug": "^1.0",
17+
"php-http/httplug-async": "dev-master",
1718
"php-http/message-factory": "^0.2"
1819
},
1920
"require-dev": {

composer.lock

Lines changed: 56 additions & 55 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/CurlHttpClient.php

Lines changed: 53 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
<?php
22
namespace Http\Curl;
33

4+
use Http\Client\Exception;
45
use Http\Client\Exception\RequestException;
6+
use Http\Client\HttpAsyncClient;
57
use Http\Client\HttpClient;
8+
use Http\Client\Promise;
69
use Http\Message\MessageFactory;
7-
use Http\Message\MessageFactoryAwareTemplate;
810
use Http\Message\StreamFactory;
9-
use Http\Message\StreamFactoryAwareTemplate;
1011
use Psr\Http\Message\RequestInterface;
1112
use Psr\Http\Message\ResponseInterface;
1213

@@ -15,30 +16,41 @@
1516
*
1617
* @license http://opensource.org/licenses/MIT MIT
1718
*
18-
* @author Kemist <kemist1980@gmail.com>
1919
* @author Михаил Красильников <m.krasilnikov@yandex.ru>
2020
* @author Blake Williams <github@shabbyrobe.org>
2121
*
22-
* @since 1.0.0
22+
* @api
23+
* @since 1.0
2324
*/
24-
class CurlHttpClient implements HttpClient
25+
class CurlHttpClient implements HttpClient, HttpAsyncClient
2526
{
26-
use MessageFactoryAwareTemplate;
27-
use StreamFactoryAwareTemplate;
27+
/**
28+
* Client settings
29+
*
30+
* @var array
31+
*/
32+
private $settings;
2833

2934
/**
30-
* cURL handle opened resource
35+
* cURL response parser
36+
*
37+
* @var ResponseParser
38+
*/
39+
private $responseParser;
40+
41+
/**
42+
* cURL synchronous requests handle
3143
*
3244
* @var resource|null
3345
*/
3446
private $handle = null;
3547

3648
/**
37-
* Client settings
49+
* Simultaneous requests runner
3850
*
39-
* @var array
51+
* @var MultiRunner|null
4052
*/
41-
private $settings;
53+
private $multiRunner = null;
4254

4355
/**
4456
* Create new client
@@ -54,15 +66,14 @@ class CurlHttpClient implements HttpClient
5466
* @param StreamFactory $streamFactory HTTP Stream factory
5567
* @param array $options Client options
5668
*
57-
* @since 1.00
69+
* @since 1.0
5870
*/
5971
public function __construct(
6072
MessageFactory $messageFactory,
6173
StreamFactory $streamFactory,
6274
array $options = []
6375
) {
64-
$this->setMessageFactory($messageFactory);
65-
$this->setStreamFactory($streamFactory);
76+
$this->responseParser = new ResponseParser($messageFactory, $streamFactory);
6677
$this->settings = array_merge(
6778
[
6879
'curl_options' => [],
@@ -95,93 +106,56 @@ public function __destruct()
95106
* @throws \InvalidArgumentException
96107
* @throws RequestException
97108
*
98-
* @since 1.00
109+
* @since 1.0
99110
*/
100111
public function sendRequest(RequestInterface $request)
101112
{
102113
$options = $this->createCurlOptions($request);
103114

104-
try {
105-
$this->request($options, $raw, $info);
106-
} catch (\RuntimeException $e) {
107-
throw new RequestException($e->getMessage(), $request, $e);
115+
if (is_resource($this->handle)) {
116+
curl_reset($this->handle);
117+
} else {
118+
$this->handle = curl_init();
108119
}
109120

110-
$response = $this->getMessageFactory()->createResponse();
111-
112-
$headerSize = $info['header_size'];
113-
$rawHeaders = substr($raw, 0, $headerSize);
114-
$headers = $this->parseRawHeaders($rawHeaders);
115-
116-
foreach ($headers as $header) {
117-
$header = trim($header);
118-
if ('' === $header) {
119-
continue;
120-
}
121-
122-
// Status line
123-
if (substr(strtolower($header), 0, 5) === 'http/') {
124-
$parts = explode(' ', $header, 3);
125-
$response = $response
126-
->withStatus($parts[1])
127-
->withProtocolVersion(substr($parts[0], 5));
128-
continue;
129-
}
121+
curl_setopt_array($this->handle, $options);
122+
$raw = curl_exec($this->handle);
130123

131-
// Extract header
132-
$parts = explode(':', $header, 2);
133-
$headerName = trim(urldecode($parts[0]));
134-
$headerValue = trim(urldecode($parts[1]));
135-
if ($response->hasHeader($headerName)) {
136-
$response = $response->withAddedHeader($headerName, $headerValue);
137-
} else {
138-
$response = $response->withHeader($headerName, $headerValue);
139-
}
124+
if (curl_errno($this->handle) > 0) {
125+
throw new RequestException(curl_error($this->handle), $request);
140126
}
141127

142-
/*
143-
* substr can return boolean value for empty string. But createStream does not support
144-
* booleans. Converting to string.
145-
*/
146-
$content = (string) substr($raw, $headerSize);
147-
$stream = $this->getStreamFactory()->createStream($content);
148-
$response = $response->withBody($stream);
128+
$info = curl_getinfo($this->handle);
149129

150-
return $response;
130+
return $this->responseParser->parse($raw, $info);
151131
}
152132

153133
/**
154-
* Perform request via cURL
134+
* Sends a PSR-7 request in an asynchronous way.
155135
*
156-
* @param array $options cURL options
157-
* @param string $raw raw response
158-
* @param array $info cURL response info
136+
* @param RequestInterface $request
159137
*
160-
* @throws \RuntimeException on cURL error
138+
* @return Promise
161139
*
162-
* @since 1.00
140+
* @throws Exception
141+
*
142+
* @since 1.0
163143
*/
164-
protected function request($options, &$raw, &$info)
144+
public function sendAsyncRequest(RequestInterface $request)
165145
{
166-
if (is_resource($this->handle)) {
167-
curl_reset($this->handle);
168-
} else {
169-
$this->handle = curl_init();
146+
if (!$this->multiRunner instanceof MultiRunner) {
147+
$this->multiRunner = new MultiRunner($this->responseParser);
170148
}
171149

172-
curl_setopt_array($this->handle, $options);
173-
$raw = curl_exec($this->handle);
150+
$handle = curl_init();
151+
$options = $this->createCurlOptions($request);
152+
curl_setopt_array($handle, $options);
174153

175-
if (curl_errno($this->handle) > 0) {
176-
throw new \RuntimeException(
177-
sprintf(
178-
'Curl error: (%d) %s',
179-
curl_errno($this->handle),
180-
curl_error($this->handle)
181-
)
182-
);
183-
}
184-
$info = curl_getinfo($this->handle);
154+
$core = new PromiseCore($request, $handle);
155+
$promise = new CurlPromise($core, $this->multiRunner);
156+
$this->multiRunner->add($core);
157+
158+
return $promise;
185159
}
186160

187161
/**
@@ -284,22 +258,4 @@ private function createHeaders(RequestInterface $request, array $options)
284258
}
285259
return $curlHeaders;
286260
}
287-
288-
/**
289-
* Parse raw headers from HTTP response
290-
*
291-
* @param string $rawHeaders
292-
*
293-
* @return string[]
294-
*/
295-
private function parseRawHeaders($rawHeaders)
296-
{
297-
$allHeaders = explode("\r\n\r\n", $rawHeaders);
298-
$lastHeaders = trim(array_pop($allHeaders));
299-
while (count($allHeaders) > 0 && '' === $lastHeaders) {
300-
$lastHeaders = trim(array_pop($allHeaders));
301-
}
302-
$headers = explode("\r\n", $lastHeaders);
303-
return $headers;
304-
}
305261
}

0 commit comments

Comments
 (0)