我这边要做一个http代理的重新解析,再次转发服务。
流程是:
curl -i -x 127.0.0.1:9081 -U OTYuNDMuMTA2LjI5OjkwOTg=:cmYwZjBoNzBkY3dxbjdieTo5Z2NjYktWdQ== 'https://seller.xiapi.shopee.cn'
## -U 后面是真实的代理IP和端口号的base64_encode
curl -i -x 96.43.106.29:9098 -U rf0f0h70dcwqn7by:9gccbKVu 'https://seller.xiapi.shopee.cn'
3.在Workerman这边利用AsyncTcpConnection 模拟上述的解析后的curl命令请求发送消息。。在本电脑环境测试是可以的,但是在服务器上面测试,客户端执行CURL命令后一直报错,如下:
HTTP/1.1 200 Connection established
curl: (35) error:02FFF036:system library:func(4095):Connection reset by peer
目前排查到的情况是是服务器上Workerman和代理建立连接,并发送请求头后,代理返回HTTP/1.1 200 Connection established之后,将剩余的报文传转发给代理服务器时,被关闭了连接。
我这边猜测是服务器上面可能是请求到目标网址:https://seller.xiapi.shopee.cn ,需要SSL验证,想知道如果使用AsyncTcpConnection模拟CURL -x命令代理请求时,如何跳过对目标网址的证书验证?
文件: start.php 文件
<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use \Workerman\Connection\AsyncTcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/Tool.php';
$listen = 'tcp://0.0.0.0:9081';
$worker = new Worker($listen);
$worker->onWorkerStart = function () {};
$worker->onConnect = function (TcpConnection $connection) {
echo "---- onConnect ----" . PHP_EOL;
};
$onMsgNum = 0;
$worker->onMessage = function (TcpConnection $connection, $data) {
global $onMsgNum;
$onMsgNum++;
echo "---- onMessage[{$onMsgNum}] start----" . PHP_EOL;
echo $data;
echo PHP_EOL;
$parse = Tool::parseHttpHeaderAndBodyText($data);
if (!$parse) {
echo "parse http header and body text fail" . PHP_EOL;
$connection->close();;
}
$httpHeaderInfo = Tool::parseHttpHeaderInfo($parse['headerRaw']);
$bodyRaw = $parse['bodyRaw'];
echo "parse header: " .PHP_EOL;
var_dump($httpHeaderInfo);;
echo PHP_EOL;
$realProxyInfo = Tool::parseProxyInfo($httpHeaderInfo['headers']['Proxy-Authorization']);
$proxyIp = $realProxyInfo['ip'];
$proxyPort = $realProxyInfo['port'];
$proxyUsername = $realProxyInfo['username'];
$proxyPassword = $realProxyInfo['password'];
$httpHeaderInfo['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword);
$realHeaderText = Tool::buildHttpHeaderText($httpHeaderInfo);
echo "---- real header info ---" . PHP_EOL;
var_dump($httpHeaderInfo);
echo "---- real proxy info ----" . PHP_EOL;
echo json_encode($realProxyInfo, JSON_UNESCAPED_SLASHES);
echo PHP_EOL;
echo "---- real header text ----" . PHP_EOL;
echo $realHeaderText;
echo "---- real header text end ----" . PHP_EOL;
// curl -x $proxyIp:$proxyport https://shopee.com
$proxyAddress = 'tcp://' . $proxyIp . ':' . $proxyPort;
$proxyTcp = new AsyncTcpConnection($proxyAddress, []);
$proxyTcp->onConnect = function (AsyncTcpConnection $asyncTcpConnection) use ($proxyAddress, $realHeaderText, $bodyRaw) {
echo "--proxyTcp[{$proxyAddress}]:onConnect--" . PHP_EOL;
// realHeaderText自带了\r\n,所以这边只需要留一个空行即可。
$sendData = $realHeaderText . "\r\n" . $bodyRaw;
echo $sendData . PHP_EOL;
echo "---- send data end ---" . PHP_EOL;
$asyncTcpConnection->send($sendData);
};
$proxyReceiveNumber = 0;
$proxyTcp->onMessage = function (AsyncTcpConnection $asyncTcpConnection, $receiveData) use (&$proxyReceiveNumber, $proxyAddress, $connection) {
$proxyReceiveNumber++;
echo "--proxyTcp[{$proxyAddress}]:onMessage:[{$proxyReceiveNumber}] start--" . PHP_EOL;
echo $receiveData;
echo "--proxyTcp[{$proxyAddress}]:onMessage:[{$proxyReceiveNumber}] end--" . PHP_EOL;
$connection->send($receiveData);
};
$proxyTcp->onClose = function (AsyncTcpConnection $asyncTcpConnection) use ($proxyAddress, $connection) {
echo "--proxyTcp[{$proxyAddress}]:onClose--" . PHP_EOL;
$connection->close();
};
$proxyTcp->onError = function (AsyncTcpConnection $asyncTcpConnection, $code, $msg) use ($proxyAddress, $connection) {
echo "--proxyTcp[{$proxyAddress}]:onError,code[{$code}],msg[{$msg}]--" . PHP_EOL;
$connection->close();;
};
// 执行代理请求
$proxyTcp->connect();
// 重新配置onMessage回调函数,将客户端后续的http数据流转发给proxyTcp连接
$connection->onMessage = function (TcpConnection $connection, $clientData) use ($proxyTcp, $proxyAddress) {
echo "--- inner on message, forward to proxy [$proxyAddress] ---" . PHP_EOL;
$proxyTcp->send($clientData);
};
$connection->onClose = function (TcpConnection $connection) use ($proxyTcp, $proxyAddress) {
echo "--- inner on close, close proxy con [{$proxyAddress}] ---" . PHP_EOL;
$proxyTcp->close();
};
$connection->onError = function (TcpConnection $connection, $code, $msg) use ($proxyTcp, $proxyAddress) {
echo "--- inner on error, code[{$code}], msg[{$msg}], close proxy tcp connection ---" . PHP_EOL;
$proxyTcp->close();;
};
echo "---- onMessage[{$onMsgNum}] end----" . PHP_EOL;
};
$worker->onClose = function (TcpConnection $connection) {
echo "---- onClose ----" . PHP_EOL;
};
$worker->onError = function (TcpConnection $connection, $code, $msg) {
echo "---- onError ----" . PHP_EOL;
echo "err code[{$code}], msg[{$msg}]" . PHP_EOL;
};
Worker::runAll();;
文件2 : Tool.php
<?php
class Tool {
public static function buildAuth($ip, $port, $username, $password) {
$usr = base64_encode($ip . ':' . $port);
$pwd = base64_encode($username . ':' . $password);
return $usr . ":" . $pwd;
}
public static function parseProxyInfo($proxyAuthorizationStr) {
$authParts = explode(' ', $proxyAuthorizationStr);
$usrPwd = base64_decode($authParts[1]);
list ($usr, $pwd) = explode(':', $usrPwd);
list($ip, $port) = explode(':', base64_decode($usr));
list($username, $password) = explode(':', base64_decode($pwd));
return [
'ip' => $ip,
'port' => $port,
'username' => $username,
'password' => $password
];
}
public static function proxyUrlDecrypt($string, $key) {
$de = base64_decode($string);
return \openssl_decrypt($de, 'AES-128-ECB', $key, 0, '');
}
public static function parseDecryptProxyUrl($auth, $key) {
$proxyUrl = Tool::proxyUrlDecrypt($auth, $key);
list ($usrAndPwd, $ipAndPort) = explode('@', $proxyUrl);
list ($usr, $pwd) = explode(':', $usrAndPwd);
list ($ip, $port) = explode(':', $ipAndPort);
return [
'ip' => $ip,
'port' => $port,
'username' => $usr,
'password' => $pwd,
];
}
public static function parseHttpHeaderAndBodyText($message) {
if (empty($message)) {
return false;
}
// 请求头和请求体的分隔符是两个回车换行
$separator = "\r\n\r\n";
$pos = strpos($message, $separator);
if (false === $pos) {
return false; // 没有找到分隔符,无法解析
}
$headerRaw = substr($message, 0, $pos);
$bodyRaw = substr($message, $pos + strlen($separator));
return [
'headerRaw' => $headerRaw,
'bodyRaw' => $bodyRaw,
];
}
public static function parseHttpHeaderInfo($httpHeaderText) {
$headers = explode("\r\n", $httpHeaderText);
$result = [];
// 解析第一行
list($result['method'], $result['uri'], $result['protocol']) = explode(' ', $headers[0]);
$result['headers'] = [];
// 解析后续头部信息
for ($i = 1; $i < count($headers); $i++) {
$pos = strpos($headers[$i], ':');
if ($pos !== false) {
$name = substr($headers[$i], 0, $pos);
$value = trim(substr($headers[$i], $pos + 1));
$result['headers'][$name] = $value;
}
}
return $result;
}
public static function buildHttpHeaderText($httpHeaderInfo) {
$CRLF = "\r\n";
// 请求行
$headerStr = sprintf("%s %s %s", $httpHeaderInfo['method'], $httpHeaderInfo['uri'], $httpHeaderInfo['protocol']) . $CRLF;
foreach ($httpHeaderInfo['headers'] as $name => $value) {
$headerStr .= $name . ': ' . $value . $CRLF;
}
return $headerStr;
}
public static function unpackRealProxyInfo($proxyAuthorizationStr, $key) {
$authParts = explode(' ', $proxyAuthorizationStr);
$authStr = $authParts[1];
$authStr = base64_decode($authStr); // 因为tcp传输过来会进行一次base64_encode,这边要先decode
return Tool::parseDecryptProxyUrl($authStr, $key);
}
}
https://www.workerman.net/doc/workerman/async-tcp-connection/construct.html#%E7%A4%BA%E4%BE%8B%203%E3%80%81%E5%BC%82%E6%AD%A5%E8%AE%BF%E9%97%AE%E5%A4%96%E9%83%A8wss%E7%AB%AF%E5%8F%A3%EF%BC%8C%E5%B9%B6%E8%AE%BE%E7%BD%AE%E6%9C%AC%E5%9C%B0ssl%E8%AF%81%E4%B9%A6
按照手册设置下
'verify_peer' => false
试下