Skip to content

Commit 5142375

Browse files
committed
Add support for parallel GET requests
1 parent 1bd5985 commit 5142375

File tree

2 files changed

+121
-25
lines changed

2 files changed

+121
-25
lines changed

Curl.class.php

Lines changed: 100 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ class Curl {
55
private $_cookies = array();
66
private $_headers = array();
77

8+
private $_multi_parent = FALSE;
9+
private $_multi_child = FALSE;
10+
private $_before_send = NULL;
11+
private $_success = NULL;
12+
private $_error = NULL;
13+
private $_complete = NULL;
14+
815
public $curl;
16+
public $curls;
917

1018
public $error = FALSE;
1119
public $error_code = 0;
@@ -35,10 +43,41 @@ public function __construct() {
3543
$this->setopt(CURLOPT_RETURNTRANSFER, TRUE);
3644
}
3745

38-
public function get($url, $data=array()) {
39-
$this->setopt(CURLOPT_URL, $this->_buildURL($url, $data));
40-
$this->setopt(CURLOPT_HTTPGET, TRUE);
41-
return $this->_exec();
46+
public function get($url_mixed, $data=array()) {
47+
if (is_array($url_mixed)) {
48+
$curl_multi = curl_multi_init();
49+
$this->_multi_parent = TRUE;
50+
51+
$this->curls = array();
52+
53+
foreach ($url_mixed as $url) {
54+
$curl = new Curl();
55+
$curl->_multi_child = TRUE;
56+
$curl->setOpt(CURLOPT_URL, $this->_buildURL($url, $data), $curl->curl);
57+
$curl->setOpt(CURLOPT_HTTPGET, TRUE);
58+
$this->_call($this->_before_send, $curl);
59+
$this->curls[] = $curl;
60+
61+
$curlm_error_code = curl_multi_add_handle($curl_multi, $curl->curl);
62+
if (!($curlm_error_code === CURLM_OK)) {
63+
throw new ErrorException('cURL multi add handle error: ' .
64+
curl_multi_strerror($curlm_error_code));
65+
}
66+
}
67+
68+
do {
69+
$status = curl_multi_exec($curl_multi, $active);
70+
} while ($status === CURLM_CALL_MULTI_PERFORM || $active);
71+
72+
foreach ($this->curls as $ch) {
73+
$this->_exec($ch);
74+
}
75+
}
76+
else {
77+
$this->setopt(CURLOPT_URL, $this->_buildURL($url_mixed, $data));
78+
$this->setopt(CURLOPT_HTTPGET, TRUE);
79+
return $this->_exec($this);
80+
}
4281
}
4382

4483
public function post($url, $data=array()) {
@@ -91,18 +130,29 @@ public function setCookie($key, $value) {
91130
$this->setopt(CURLOPT_COOKIE, http_build_query($this->_cookies, '', '; '));
92131
}
93132

94-
public function setOpt($option, $value) {
95-
return curl_setopt($this->curl, $option, $value);
133+
public function setOpt($option, $value, $_ch=NULL) {
134+
$ch = is_null($_ch) ? $this->curl : $_ch;
135+
return curl_setopt($ch, $option, $value);
96136
}
97137

98138
public function verbose($on=TRUE) {
99139
$this->setopt(CURLOPT_VERBOSE, $on);
100140
}
101141

102142
public function close() {
143+
if ($this->_multi_parent) {
144+
foreach ($this->curls as $curl) {
145+
curl_close($curl->curl);
146+
}
147+
}
148+
103149
curl_close($this->curl);
104150
}
105151

152+
public function beforeSend($function) {
153+
$this->_before_send = $function;
154+
}
155+
106156
private function http_build_multi_query($data, $key=NULL) {
107157
$query = array();
108158

@@ -150,30 +200,55 @@ private function _postfields($data) {
150200
return $data;
151201
}
152202

153-
protected function _exec() {
154-
$this->response = curl_exec($this->curl);
155-
$this->curl_error_code = curl_errno($this->curl);
156-
$this->curl_error_message = curl_error($this->curl);
157-
$this->curl_error = !($this->curl_error_code === 0);
158-
$this->http_status_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
159-
$this->http_error = in_array(floor($this->http_status_code / 100), array(4, 5));
160-
$this->error = $this->curl_error || $this->http_error;
161-
$this->error_code = $this->error ? ($this->curl_error ? $this->curl_error_code : $this->http_status_code) : 0;
162-
163-
$this->request_headers = preg_split('/\r\n/', curl_getinfo($this->curl, CURLINFO_HEADER_OUT), NULL, PREG_SPLIT_NO_EMPTY);
164-
$this->response_headers = '';
165-
if (!(strpos($this->response, "\r\n\r\n") === FALSE)) {
166-
list($response_header, $this->response) = explode("\r\n\r\n", $this->response, 2);
203+
protected function _exec($_ch=NULL) {
204+
$ch = is_null($_ch) ? $this : $_ch;
205+
206+
if ($ch->_multi_child) {
207+
$ch->response = curl_multi_getcontent($ch->curl);
208+
}
209+
else {
210+
$ch->response = curl_exec($ch->curl);
211+
}
212+
213+
$ch->curl_error_code = curl_errno($ch->curl);
214+
$ch->curl_error_message = curl_error($ch->curl);
215+
$ch->curl_error = !($ch->curl_error_code === 0);
216+
$ch->http_status_code = curl_getinfo($ch->curl, CURLINFO_HTTP_CODE);
217+
$ch->http_error = in_array(floor($ch->http_status_code / 100), array(4, 5));
218+
$ch->error = $ch->curl_error || $ch->http_error;
219+
$ch->error_code = $ch->error ? ($ch->curl_error ? $ch->curl_error_code : $ch->http_status_code) : 0;
220+
221+
$ch->request_headers = preg_split('/\r\n/', curl_getinfo($ch->curl, CURLINFO_HEADER_OUT), NULL, PREG_SPLIT_NO_EMPTY);
222+
$ch->response_headers = '';
223+
if (!(strpos($ch->response, "\r\n\r\n") === FALSE)) {
224+
list($response_header, $ch->response) = explode("\r\n\r\n", $ch->response, 2);
167225
if ($response_header === 'HTTP/1.1 100 Continue') {
168-
list($response_header, $this->response) = explode("\r\n\r\n", $this->response, 2);
226+
list($response_header, $ch->response) = explode("\r\n\r\n", $ch->response, 2);
169227
}
170-
$this->response_headers = preg_split('/\r\n/', $response_header, NULL, PREG_SPLIT_NO_EMPTY);
228+
$ch->response_headers = preg_split('/\r\n/', $response_header, NULL, PREG_SPLIT_NO_EMPTY);
171229
}
172230

173-
$this->http_error_message = $this->error ? (isset($this->response_headers['0']) ? $this->response_headers['0'] : '') : '';
174-
$this->error_message = $this->curl_error ? $this->curl_error_message : $this->http_error_message;
231+
$ch->http_error_message = $ch->error ? (isset($ch->response_headers['0']) ? $ch->response_headers['0'] : '') : '';
232+
$ch->error_message = $ch->curl_error ? $ch->curl_error_message : $ch->http_error_message;
175233

176-
return $this->error_code;
234+
if (!$ch->error) {
235+
$ch->_call($ch->_success);
236+
}
237+
else {
238+
$ch->_call($ch->_error);
239+
}
240+
241+
$ch->_call($ch->_complete);
242+
243+
return $ch->error_code;
244+
}
245+
246+
private function _call($function) {
247+
if (is_callable($function)) {
248+
$args = func_get_args();
249+
array_shift($args);
250+
call_user_func_array($function, $args);
251+
}
177252
}
178253

179254
public function __destruct() {

tests/run.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,25 @@ public function testArrayToStringConversion() {
276276
'foo=bar&baz[qux]=&baz[wibble]=wobble'
277277
);
278278
}
279+
280+
public function testParallelRequests() {
281+
$curl = new Curl();
282+
$curl->beforeSend(function($instance) {
283+
$instance->setHeader('X-DEBUG-TEST', 'request_uri');
284+
$instance->setOpt(CURLOPT_SSL_VERIFYPEER, FALSE);
285+
$instance->setOpt(CURLOPT_SSL_VERIFYHOST, FALSE);
286+
});
287+
$curl->get(array(
288+
Test::TEST_URL . '/a/',
289+
Test::TEST_URL . '/b/',
290+
Test::TEST_URL . '/c/',
291+
), array(
292+
'foo' => 'bar',
293+
));
294+
295+
$len = strlen('/a/?foo=bar');
296+
$this->assertTrue(substr($curl->curls['0']->response, - $len) === '/a/?foo=bar');
297+
$this->assertTrue(substr($curl->curls['1']->response, - $len) === '/b/?foo=bar');
298+
$this->assertTrue(substr($curl->curls['2']->response, - $len) === '/c/?foo=bar');
299+
}
279300
}

0 commit comments

Comments
 (0)