Skip to content

Commit 05b8161

Browse files
authored
Support CURLOPT_RESOLVE option for SWOOLE_HOOK_CURL (#107)
* Support CURLOPT_RESOLVE option for SWOOLE_HOOK_CURL * Support CURLOPT_RESOLVE option for SWOOLE_HOOK_CURL * Update composer.json * Support CURLOPT_RESOLVE option for SWOOLE_HOOK_CURL * Support CURLOPT_RESOLVE option for SWOOLE_HOOK_CURL * Support CURLOPT_RESOLVE option for SWOOLE_HOOK_CURL * cs-fix * remove * update * Add more tests * Fix test
1 parent a1ff497 commit 05b8161

File tree

5 files changed

+180
-5
lines changed

5 files changed

+180
-5
lines changed

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
},
1818
"require": {
1919
"php": ">=7.2",
20-
"ext-swoole": ">=4.6",
21-
"friendsofphp/php-cs-fixer": "^2.18"
20+
"ext-swoole": ">=4.6"
2221
},
2322
"require-dev": {
2423
"ext-curl": "*",
2524
"ext-sockets": "*",
2625
"phpunit/phpunit": "~8.0",
27-
"swoole/ide-helper": "dev-master"
26+
"swoole/ide-helper": "~4.6",
27+
"friendsofphp/php-cs-fixer": "^2.18"
2828
},
2929
"suggest": {
3030
"ext-mysqli": "Required to use mysqli database",

src/constants.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313

1414
!defined('CURLOPT_HEADEROPT') && define('CURLOPT_HEADEROPT', 229);
1515
!defined('CURLOPT_PROXYHEADER') && define('CURLOPT_PROXYHEADER', 10228);
16+
!defined('CURLOPT_RESOLVE') && define('CURLOPT_RESOLVE', 10203);

src/core/Curl/Handler.php

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ final class Handler
130130

131131
private $cookieJar = '';
132132

133+
private $resolve = [];
134+
133135
public function __construct(string $url = '')
134136
{
135137
if ($url) {
@@ -216,7 +218,15 @@ private function create(?array $urlInfo = null): void
216218
if ($urlInfo === null) {
217219
$urlInfo = $this->urlInfo;
218220
}
219-
$this->client = new Client($urlInfo['host'], $urlInfo['port'], $urlInfo['scheme'] === 'https');
221+
$host = $urlInfo['host'];
222+
$port = $urlInfo['port'];
223+
if (isset($this->resolve[$host])) {
224+
if (!$this->hasHeader('Host')) {
225+
$this->setHeader('Host', $host);
226+
}
227+
$this->urlInfo['host'] = $host = $this->resolve[$host][$port] ?? null ?: $host;
228+
}
229+
$this->client = new Client($host, $port, $urlInfo['scheme'] === 'https');
220230
}
221231

222232
private function getUrl(): string
@@ -304,7 +314,7 @@ private function setPort(int $port): void
304314
private function setError($code, $msg = ''): void
305315
{
306316
$this->errCode = $code;
307-
$this->errMsg = $msg ? $msg : curl_strerror($code);
317+
$this->errMsg = $msg ?: curl_strerror($code);
308318
}
309319

310320
private function hasHeader(string $headerName): bool
@@ -413,6 +423,25 @@ private function setOption(int $opt, $value): bool
413423
$this->nobody = boolval($value);
414424
$this->method = 'HEAD';
415425
break;
426+
case CURLOPT_RESOLVE:
427+
foreach ((array) $value as $resolve) {
428+
$flag = substr($resolve, 0, 1);
429+
if ($flag === '+' || $flag === '-') {
430+
// TODO: [+]HOST:PORT:ADDRESS
431+
$resolve = substr($resolve, 1);
432+
}
433+
$tmpResolve = explode(':', $resolve, 3);
434+
$host = $tmpResolve[0] ?? '';
435+
$port = $tmpResolve[1] ?? 0;
436+
$ip = $tmpResolve[2] ?? '';
437+
if ($flag === '-') {
438+
unset($this->resolve[$host][$port]);
439+
} else {
440+
// TODO: HOST:PORT:ADDRESS[,ADDRESS]...
441+
$this->resolve[$host][$port] = explode(',', $ip)[0];
442+
}
443+
}
444+
break;
416445
case CURLOPT_IPRESOLVE:
417446
if ($value !== CURL_IPRESOLVE_WHATEVER and $value !== CURL_IPRESOLVE_V4) {
418447
throw new Swoole\Curl\Exception(
@@ -803,6 +832,10 @@ private function execute()
803832
$this->info['redirect_time'] = microtime(true) - $redirectBeginTime;
804833
}
805834

835+
if (filter_var($this->urlInfo['host'], FILTER_VALIDATE_IP)) {
836+
$this->info['primary_ip'] = $this->urlInfo['host'];
837+
}
838+
806839
$headerContent = '';
807840
if ($client->headers) {
808841
$cb = $this->headerFunction;

src/ext/curl.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ function swoole_curl_getinfo(Swoole\Curl\Handler $obj, int $opt = 0)
6363
return $info['redirect_time'];
6464
case CURLINFO_HEADER_SIZE:
6565
return $info['header_size'];
66+
case CURLINFO_PRIMARY_IP:
67+
return $info['primary_ip'];
6668
case CURLINFO_PRIVATE:
6769
return $info['private'];
6870
default:

tests/unit/Curl/HandlerTest.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,145 @@ public function testWriteFunction()
135135
/**
136136
* @covers \Swoole\Curl\Handler::execute()
137137
*/
138+
public function testResolve()
139+
{
140+
Coroutine\run(function () {
141+
$host = 'httpbin.org';
142+
$url = 'https://httpbin.org/get';
143+
$ip = Coroutine::gethostbyname($host);
144+
$ch = curl_init();
145+
146+
curl_setopt($ch, CURLOPT_URL, $url);
147+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
148+
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:{$ip}"]);
149+
150+
$data = curl_exec($ch);
151+
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
152+
curl_close($ch);
153+
$body = json_decode($data, true);
154+
self::assertSame($body['headers']['Host'], 'httpbin.org');
155+
self::assertEquals($body['url'], $url);
156+
self::assertEquals($ip, $httpPrimaryIp);
157+
});
158+
}
159+
160+
/**
161+
* @covers \Swoole\Curl\Handler::execute()
162+
*/
163+
public function testInvalidResolve()
164+
{
165+
Coroutine\run(function () {
166+
$host = 'httpbin.org';
167+
$url = 'https://httpbin.org/get';
168+
$ip = '127.0.0.1'; // An incorrect IP in use.
169+
$ch = curl_init();
170+
171+
curl_setopt($ch, CURLOPT_URL, $url);
172+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
173+
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:{$ip}"]);
174+
175+
$body = curl_exec($ch);
176+
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
177+
curl_close($ch);
178+
self::assertFalse($body);
179+
self::assertSame('', $httpPrimaryIp);
180+
});
181+
}
182+
183+
/**
184+
* @covers \Swoole\Curl\Handler::execute()
185+
*/
186+
public function testResolve2()
187+
{
188+
Coroutine\run(function () {
189+
$host = 'httpbin.org';
190+
$url = 'https://httpbin.org/get';
191+
$ip = Coroutine::gethostbyname($host);
192+
$ch = curl_init();
193+
194+
curl_setopt($ch, CURLOPT_URL, $url);
195+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
196+
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:127.0.0.1", "{$host}:443:{$ip}"]);
197+
198+
$data = curl_exec($ch);
199+
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
200+
$body = json_decode($data, true);
201+
self::assertSame($body['headers']['Host'], 'httpbin.org');
202+
self::assertEquals($body['url'], $url);
203+
self::assertEquals($ip, $httpPrimaryIp);
204+
});
205+
}
206+
207+
/**
208+
* @covers \Swoole\Curl\Handler::execute()
209+
*/
210+
public function testInvalidResolve2()
211+
{
212+
Coroutine\run(function () {
213+
$host = 'httpbin.org';
214+
$url = 'https://httpbin.org/get';
215+
$ip = Coroutine::gethostbyname($host);
216+
$ch = curl_init();
217+
218+
curl_setopt($ch, CURLOPT_URL, $url);
219+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
220+
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:{$ip}", "+{$host}:443:127.0.0.1"]);
221+
222+
$body = curl_exec($ch);
223+
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
224+
curl_close($ch);
225+
self::assertFalse($body);
226+
self::assertSame('', $httpPrimaryIp);
227+
});
228+
}
229+
230+
/**
231+
* @covers \Swoole\Curl\Handler::execute()
232+
*/
233+
public function testInvalidResolve3()
234+
{
235+
Coroutine\run(function () {
236+
$host = 'httpbin.org';
237+
$url = 'https://httpbin.org/get';
238+
$ip = Coroutine::gethostbyname($host);
239+
$ch = curl_init();
240+
241+
curl_setopt($ch, CURLOPT_URL, $url);
242+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
243+
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:{$ip}", "{$host}:443:127.0.0.1"]);
244+
245+
$body = curl_exec($ch);
246+
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
247+
curl_close($ch);
248+
self::assertFalse($body);
249+
self::assertSame('', $httpPrimaryIp);
250+
});
251+
}
252+
253+
/**
254+
* @covers \Swoole\Curl\Handler::execute()
255+
*/
256+
public function testResolve3()
257+
{
258+
Coroutine\run(function () {
259+
$host = 'httpbin.org';
260+
$url = 'https://httpbin.org/get';
261+
$ip = Coroutine::gethostbyname($host);
262+
$ch = curl_init();
263+
264+
curl_setopt($ch, CURLOPT_URL, $url);
265+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
266+
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:{$ip}", "{$host}:443:127.0.0.1", "-{$host}:443:127.0.0.1"]);
267+
268+
$data = curl_exec($ch);
269+
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
270+
$body = json_decode($data, true);
271+
self::assertSame($body['headers']['Host'], 'httpbin.org');
272+
self::assertEquals($body['url'], $url);
273+
self::assertSame('', $httpPrimaryIp);
274+
});
275+
}
276+
138277
public function testOptPrivate()
139278
{
140279
Coroutine\run(function () {

0 commit comments

Comments
 (0)