1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* |
4
|
|
|
* @author [email protected] [email protected] |
5
|
|
|
* Date: 2017/6/16 |
6
|
|
|
* Time: 10:02 |
7
|
|
|
* @version $Id: $ |
8
|
|
|
* @since 1.0 |
9
|
|
|
* @copyright Sina Corp. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace MultiHttp; |
13
|
|
|
|
14
|
|
|
use MultiHttp\Exception\InvalidArgumentException; |
15
|
|
|
|
16
|
|
|
class Request extends Http { |
17
|
|
|
protected static $curlAlias = array( |
18
|
|
|
'url' => 'CURLOPT_URL', |
19
|
|
|
'uri' => 'CURLOPT_URL', |
20
|
|
|
'debug' => 'CURLOPT_VERBOSE',//for debug verbose |
21
|
|
|
'method' => 'CURLOPT_CUSTOMREQUEST', |
22
|
|
|
'data' => 'CURLOPT_POSTFIELDS', // array or string , file begin with '@' |
23
|
|
|
'ua' => 'CURLOPT_USERAGENT', |
24
|
|
|
'timeout' => 'CURLOPT_TIMEOUT', // (secs) 0 means indefinitely |
25
|
|
|
'connect_timeout' => 'CURLOPT_CONNECTTIMEOUT', |
26
|
|
|
'referer' => 'CURLOPT_REFERER', |
27
|
|
|
'binary' => 'CURLOPT_BINARYTRANSFER', |
28
|
|
|
'port' => 'CURLOPT_PORT', |
29
|
|
|
'header' => 'CURLOPT_HEADER', // TRUE:include header |
30
|
|
|
'headers' => 'CURLOPT_HTTPHEADER', // array |
31
|
|
|
'download' => 'CURLOPT_FILE', // writing file stream (using fopen()), default is STDOUT |
32
|
|
|
'upload' => 'CURLOPT_INFILE', // reading file stream |
33
|
|
|
'transfer' => 'CURLOPT_RETURNTRANSFER', // TRUE:return string; FALSE:output directly (curl_exec) |
34
|
|
|
'follow_location' => 'CURLOPT_FOLLOWLOCATION', |
35
|
|
|
'timeout_ms' => 'CURLOPT_TIMEOUT_MS', // milliseconds, libcurl version > 7.36.0 , |
36
|
|
|
); |
37
|
|
|
public $curlHandle; |
38
|
|
|
protected $options = array( |
39
|
|
|
'CURLOPT_MAXREDIRS' => 10, |
40
|
|
|
'header' => true, |
41
|
|
|
'method' => self::GET, |
42
|
|
|
'transfer' => true, |
43
|
|
|
'follow_location' => true, |
44
|
|
|
'timeout' => 0); |
45
|
|
|
protected $endCallback; |
46
|
|
|
protected $withURIQuery; |
47
|
|
|
protected static $logger; |
48
|
|
|
protected $uri; |
49
|
2 |
|
|
50
|
|
|
protected function __construct() { |
51
|
2 |
|
|
52
|
|
|
} |
53
|
2 |
|
|
54
|
2 |
|
public static function create() { |
55
|
|
|
return new self; |
56
|
|
|
} |
57
|
1 |
|
|
58
|
1 |
|
public function endCallback() { |
59
|
|
|
return $this->endCallback; |
60
|
|
|
} |
61
|
2 |
|
|
62
|
2 |
|
public function hasEndCallback() { |
63
|
|
|
return isset($this->endCallback); |
64
|
|
|
} |
65
|
2 |
|
|
66
|
2 |
|
public function onEnd(callable$callback) { |
67
|
|
|
if (!is_callable($callback)) {throw new InvalidArgumentException('callback not is callable :'.print_r(callback, 1)); |
68
|
|
|
} |
69
|
2 |
|
|
70
|
2 |
|
$this->endCallback = $callback; |
71
|
|
|
return $this; |
72
|
|
|
} |
73
|
2 |
|
|
74
|
2 |
|
public function getURI() { |
75
|
|
|
return $this->uri; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @param $field alias or field name |
80
|
|
|
* @return bool|mixed |
81
|
2 |
|
*/ |
82
|
|
|
public function getIni($field) { |
83
|
|
|
$alias = self::optionAlias($field); |
84
|
2 |
|
// if (null === ($rawField = constant($alias))) {throw new InvalidArgumentException('field is invalid'); |
|
|
|
|
85
|
2 |
|
// } |
86
|
|
|
return isset($this->options[$alias])?$this->options[$alias]:false; |
87
|
|
|
} |
88
|
2 |
|
|
89
|
|
|
|
90
|
|
|
public function addQuery($data) { |
91
|
|
|
if (!empty($data)) { |
92
|
|
|
if (is_array($data)) { |
93
|
|
|
$this->withURIQuery = http_build_query($data); |
94
|
|
|
} else if (is_string($data)) { |
95
|
1 |
|
$this->withURIQuery = $data; |
96
|
1 |
|
} else { |
97
|
1 |
|
throw new InvalidArgumentException('data must be array or string'); |
98
|
1 |
|
} |
99
|
1 |
|
} |
100
|
1 |
|
return $this; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
public function post($uri, array $payload = array(), array $options = array()) { |
104
|
|
|
return $this->ini(Http::POST, $uri, $payload, $options); |
105
|
1 |
|
} |
106
|
|
|
|
107
|
|
|
protected function ini($method, $url, array $data = array(), array $options = array()) { |
108
|
1 |
|
$options = array('url' => $url, 'method' => $method, 'data' => $data)+$options; |
109
|
1 |
|
$this->addOptions($options); |
110
|
|
|
|
111
|
|
|
return $this; |
112
|
2 |
|
} |
113
|
2 |
|
|
114
|
2 |
|
public function addOptions(array $options = array()) { |
115
|
|
|
$this->options = $options+$this->options; |
116
|
2 |
|
$this->uri = $this->options['url']; |
117
|
|
|
return $this; |
118
|
|
|
} |
119
|
2 |
|
|
120
|
2 |
|
/* no body */ |
121
|
2 |
|
|
122
|
|
|
function put($uri, array $payload = array(), array $options = array()) { |
|
|
|
|
123
|
|
|
return $this->ini(Http::PUT, $uri, $payload, $options); |
124
|
2 |
|
} |
125
|
2 |
|
|
126
|
|
|
function patch($uri, array $payload = array(), array $options = array()) { |
|
|
|
|
127
|
2 |
|
return $this->ini(Http::PATCH, $uri, $payload, $options); |
128
|
1 |
|
} |
129
|
1 |
|
|
130
|
|
|
public function get($uri, array $options = array()) { |
131
|
2 |
|
return $this->ini(Http::GET, $uri, array(), $options); |
132
|
2 |
|
} |
133
|
2 |
|
|
134
|
|
|
function options($uri, array $options = array()) { |
|
|
|
|
135
|
|
|
return $this->ini(Http::OPTIONS, $uri, array(), $options); |
136
|
2 |
|
} |
137
|
|
|
|
138
|
|
|
function head($uri, array $options = array()) { |
|
|
|
|
139
|
|
|
return $this->ini(Http::HEAD, $uri, array('CURLOPT_NOBODY' => true), $options); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
function delete($uri, array $options = array()) { |
|
|
|
|
143
|
|
|
return $this->ini(Http::DELETE, $uri, array(), $options); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
function trace($uri, array $options = array()) { |
|
|
|
|
147
|
|
|
return $this->ini(Http::TRACE, $uri, array(), $options); |
148
|
|
|
} |
149
|
1 |
|
|
150
|
1 |
|
/** |
151
|
|
|
* @return Response |
152
|
|
|
*/ |
153
|
1 |
|
public function send() { |
154
|
1 |
|
$this->applyOptions(); |
155
|
|
|
$response = $this->makeResponse(); |
156
|
|
|
if ($this->endCallback) { |
157
|
|
|
$func = $this->endCallback; |
158
|
|
|
$func($response); |
159
|
|
|
} |
160
|
|
|
return $response; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
public function applyOptions() { |
164
|
|
|
$curl = curl_init(); |
165
|
2 |
|
$this->curlHandle = $curl; |
166
|
2 |
|
$this->prepare(); |
167
|
|
|
return $this; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
protected function prepare() { |
171
|
|
|
if (empty($this->options['url'])) { |
172
|
1 |
|
throw new InvalidArgumentException('url can not empty'); |
173
|
1 |
|
} |
174
|
1 |
|
|
175
|
1 |
|
if (isset($this->options['data'])) { |
176
|
1 |
|
$this->options['data'] = is_array($this->options['data'])?http_build_query($this->options['data']):$this->options['data'];//for better compatibility |
177
|
1 |
|
} |
178
|
|
|
if (isset($this->withURIQuery)) { |
179
|
1 |
|
$this->options['url'] .= strpos($this->options['url'], '?') === FALSE?'?':'&'; |
180
|
|
|
$this->options['url'] .= $this->withURIQuery; |
181
|
|
|
} |
182
|
2 |
|
if (isset($this->options['callback'])) { |
183
|
2 |
|
$this->onEnd($this->options['callback']); |
184
|
2 |
|
unset($this->options['callback']); |
185
|
2 |
|
} |
186
|
2 |
|
//swap ip and host |
187
|
|
|
if (!empty($this->options['ip'])) { |
188
|
|
|
$matches = array(); |
189
|
2 |
|
preg_match('/\/\/([^\/]+)/', $this->options['url'], $matches); |
190
|
|
|
$host = $matches[1]; |
191
|
2 |
|
if (empty($this->options['headers']) || !is_array($this->options['headers'])) { |
192
|
1 |
|
$this->options['headers'] = array('Host: '.$host); |
193
|
1 |
|
} else { |
194
|
1 |
|
$this->options['headers'][] = 'Host: '.$host; |
195
|
1 |
|
} |
196
|
1 |
|
$this->options['url'] = preg_replace('/\/\/([^\/]+)/', '//'.$this->options['ip'], $this->options['url']); |
197
|
|
|
unset($this->options['ip']); |
198
|
|
|
unset($host); |
199
|
|
|
} |
200
|
1 |
|
//process version |
201
|
1 |
|
if (!empty($this->options['http_version'])) { |
202
|
1 |
|
$version = $this->options['http_version']; |
203
|
|
|
if ($version == '1.0') {$this->options['CURLOPT_HTTP_VERSION'] = CURLOPT_HTTP_VERSION_1_0; |
204
|
|
|
} elseif ($version == '1.1') {$this->options['CURLOPT_HTTP_VERSION'] = CURLOPT_HTTP_VERSION_1_1; |
205
|
2 |
|
} |
206
|
|
|
|
207
|
|
|
unset($version); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
//convert secs to milliseconds |
211
|
|
|
if (defined('CURLOPT_TIMEOUT_MS')) { |
212
|
|
|
if (!isset($this->options['timeout_ms'])) { |
213
|
|
|
$this->options['timeout_ms'] = intval($this->options['timeout']*1000); |
214
|
|
|
} else { |
215
|
2 |
|
$this->options['timeout_ms'] = intval($this->options['timeout_ms']); |
216
|
2 |
|
} |
217
|
2 |
|
} |
218
|
|
|
|
219
|
1 |
|
|
220
|
|
|
$cURLOptions = self::filterAndRaw($this->options); |
221
|
|
|
|
222
|
|
|
curl_setopt_array($this->curlHandle, $cURLOptions); |
223
|
2 |
|
|
224
|
2 |
|
return $this; |
225
|
|
|
} |
226
|
2 |
|
|
227
|
|
|
protected static function filterAndRaw(array &$options) { |
228
|
|
|
$opts = array(); |
229
|
2 |
|
foreach ($options as $key => $val) { |
230
|
|
|
$alias = self::optionAlias($key); |
231
|
|
|
$options[$alias] = $val; |
232
|
2 |
|
if ($alias) {$opts[constant($alias)] = $val; |
233
|
2 |
|
} |
234
|
2 |
|
unset($options[$key]); |
235
|
2 |
|
} |
236
|
2 |
|
return $opts; |
237
|
2 |
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
2 |
|
* @param $key |
241
|
2 |
|
* @return mixed |
242
|
|
|
*/ |
243
|
|
|
protected static function optionAlias($key) { |
244
|
|
|
$alias = false; |
245
|
|
|
if (isset(self::$curlAlias[$key])) { |
246
|
|
|
$alias = self::$curlAlias[$key]; |
247
|
2 |
|
} elseif ((substr($key, 0, strlen('CURLOPT_')) == 'CURLOPT_') && defined($key)) { |
248
|
2 |
|
$alias = $key; |
249
|
2 |
|
} |
250
|
2 |
|
return $alias; |
251
|
2 |
|
} |
252
|
2 |
|
public function makeResponse($isMultiCurl = false) { |
253
|
|
|
$body = $isMultiCurl?curl_multi_getcontent($this->curlHandle):curl_exec($this->curlHandle); |
254
|
2 |
|
$info = curl_getinfo($this->curlHandle); |
255
|
|
|
$errno = curl_errno($this->curlHandle); |
256
|
2 |
|
$error = curl_error($this->curlHandle); |
257
|
2 |
|
$response = Response::create($this, $body, $info, $errno, $error); |
258
|
2 |
|
self::log($response); |
259
|
2 |
|
return $response; |
260
|
2 |
|
} |
261
|
2 |
|
|
262
|
2 |
|
private static function log(Response $response) |
263
|
|
|
{ |
264
|
|
|
if (is_null(self::$logger)) { |
265
|
|
|
return; |
266
|
|
|
} |
267
|
|
|
if($response->hasErrors()){ |
268
|
|
|
self::$logger->error($response->request->getURI() . "\t" . $response->error, array( |
269
|
|
|
'response' => print_r($response,1), |
270
|
|
|
)); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
public static function setLogger($logger) |
276
|
|
|
{ |
277
|
|
|
self::$logger = $logger; |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.