Skip to content

Support CURLOPT_RESOLVE option for SWOOLE_HOOK_CURL #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 16, 2021
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
},
"require": {
"php": ">=7.2",
"ext-swoole": ">=4.6",
"friendsofphp/php-cs-fixer": "^2.18"
"ext-swoole": ">=4.6"
},
"require-dev": {
"ext-curl": "*",
"ext-sockets": "*",
"phpunit/phpunit": "~8.0",
"swoole/ide-helper": "dev-master"
"swoole/ide-helper": "~4.6",
"friendsofphp/php-cs-fixer": "^2.18"
},
"suggest": {
"ext-mysqli": "Required to use mysqli database",
Expand Down
1 change: 1 addition & 0 deletions src/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@

!defined('CURLOPT_HEADEROPT') && define('CURLOPT_HEADEROPT', 229);
!defined('CURLOPT_PROXYHEADER') && define('CURLOPT_PROXYHEADER', 10228);
!defined('CURLOPT_RESOLVE') && define('CURLOPT_RESOLVE', 10203);
37 changes: 35 additions & 2 deletions src/core/Curl/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ final class Handler

private $cookieJar = '';

private $resolve = [];

public function __construct(string $url = '')
{
if ($url) {
Expand Down Expand Up @@ -216,7 +218,15 @@ private function create(?array $urlInfo = null): void
if ($urlInfo === null) {
$urlInfo = $this->urlInfo;
}
$this->client = new Client($urlInfo['host'], $urlInfo['port'], $urlInfo['scheme'] === 'https');
$host = $urlInfo['host'];
$port = $urlInfo['port'];
if (isset($this->resolve[$host])) {
if (!$this->hasHeader('Host')) {
$this->setHeader('Host', $host);
}
$this->urlInfo['host'] = $host = $this->resolve[$host][$port] ?? null ?: $host;
}
$this->client = new Client($host, $port, $urlInfo['scheme'] === 'https');
}

private function getUrl(): string
Expand Down Expand Up @@ -304,7 +314,7 @@ private function setPort(int $port): void
private function setError($code, $msg = ''): void
{
$this->errCode = $code;
$this->errMsg = $msg ? $msg : curl_strerror($code);
$this->errMsg = $msg ?: curl_strerror($code);
}

private function hasHeader(string $headerName): bool
Expand Down Expand Up @@ -413,6 +423,25 @@ private function setOption(int $opt, $value): bool
$this->nobody = boolval($value);
$this->method = 'HEAD';
break;
case CURLOPT_RESOLVE:
foreach ((array) $value as $resolve) {
$flag = substr($resolve, 0, 1);
if ($flag === '+' || $flag === '-') {
// TODO: [+]HOST:PORT:ADDRESS
$resolve = substr($resolve, 1);
}
$tmpResolve = explode(':', $resolve, 3);
$host = $tmpResolve[0] ?? '';
$port = $tmpResolve[1] ?? 0;
$ip = $tmpResolve[2] ?? '';
if ($flag === '-') {
unset($this->resolve[$host][$port]);
} else {
// TODO: HOST:PORT:ADDRESS[,ADDRESS]...
$this->resolve[$host][$port] = explode(',', $ip)[0];
}
}
break;
case CURLOPT_IPRESOLVE:
if ($value !== CURL_IPRESOLVE_WHATEVER and $value !== CURL_IPRESOLVE_V4) {
throw new Swoole\Curl\Exception(
Expand Down Expand Up @@ -803,6 +832,10 @@ private function execute()
$this->info['redirect_time'] = microtime(true) - $redirectBeginTime;
}

if (filter_var($this->urlInfo['host'], FILTER_VALIDATE_IP)) {
$this->info['primary_ip'] = $this->urlInfo['host'];
}

$headerContent = '';
if ($client->headers) {
$cb = $this->headerFunction;
Expand Down
2 changes: 2 additions & 0 deletions src/ext/curl.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ function swoole_curl_getinfo(Swoole\Curl\Handler $obj, int $opt = 0)
return $info['redirect_time'];
case CURLINFO_HEADER_SIZE:
return $info['header_size'];
case CURLINFO_PRIMARY_IP:
return $info['primary_ip'];
case CURLINFO_PRIVATE:
return $info['private'];
default:
Expand Down
139 changes: 139 additions & 0 deletions tests/unit/Curl/HandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,145 @@ public function testWriteFunction()
/**
* @covers \Swoole\Curl\Handler::execute()
*/
public function testResolve()
{
Coroutine\run(function () {
$host = 'httpbin.org';
$url = 'https://httpbin.org/get';
$ip = Coroutine::gethostbyname($host);
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:{$ip}"]);

$data = curl_exec($ch);
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
curl_close($ch);
$body = json_decode($data, true);
self::assertSame($body['headers']['Host'], 'httpbin.org');
self::assertEquals($body['url'], $url);
self::assertEquals($ip, $httpPrimaryIp);
});
}

/**
* @covers \Swoole\Curl\Handler::execute()
*/
public function testInvalidResolve()
{
Coroutine\run(function () {
$host = 'httpbin.org';
$url = 'https://httpbin.org/get';
$ip = '127.0.0.1'; // An incorrect IP in use.
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:{$ip}"]);

$body = curl_exec($ch);
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
curl_close($ch);
self::assertFalse($body);
self::assertSame('', $httpPrimaryIp);
});
}

/**
* @covers \Swoole\Curl\Handler::execute()
*/
public function testResolve2()
{
Coroutine\run(function () {
$host = 'httpbin.org';
$url = 'https://httpbin.org/get';
$ip = Coroutine::gethostbyname($host);
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:127.0.0.1", "{$host}:443:{$ip}"]);

$data = curl_exec($ch);
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
$body = json_decode($data, true);
self::assertSame($body['headers']['Host'], 'httpbin.org');
self::assertEquals($body['url'], $url);
self::assertEquals($ip, $httpPrimaryIp);
});
}

/**
* @covers \Swoole\Curl\Handler::execute()
*/
public function testInvalidResolve2()
{
Coroutine\run(function () {
$host = 'httpbin.org';
$url = 'https://httpbin.org/get';
$ip = Coroutine::gethostbyname($host);
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:{$ip}", "+{$host}:443:127.0.0.1"]);

$body = curl_exec($ch);
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
curl_close($ch);
self::assertFalse($body);
self::assertSame('', $httpPrimaryIp);
});
}

/**
* @covers \Swoole\Curl\Handler::execute()
*/
public function testInvalidResolve3()
{
Coroutine\run(function () {
$host = 'httpbin.org';
$url = 'https://httpbin.org/get';
$ip = Coroutine::gethostbyname($host);
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:{$ip}", "{$host}:443:127.0.0.1"]);

$body = curl_exec($ch);
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
curl_close($ch);
self::assertFalse($body);
self::assertSame('', $httpPrimaryIp);
});
}

/**
* @covers \Swoole\Curl\Handler::execute()
*/
public function testResolve3()
{
Coroutine\run(function () {
$host = 'httpbin.org';
$url = 'https://httpbin.org/get';
$ip = Coroutine::gethostbyname($host);
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_RESOLVE, ["{$host}:443:{$ip}", "{$host}:443:127.0.0.1", "-{$host}:443:127.0.0.1"]);

$data = curl_exec($ch);
$httpPrimaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
$body = json_decode($data, true);
self::assertSame($body['headers']['Host'], 'httpbin.org');
self::assertEquals($body['url'], $url);
self::assertSame('', $httpPrimaryIp);
});
}

public function testOptPrivate()
{
Coroutine\run(function () {
Expand Down