使用AsyncTcpConnection模拟CURL -x命令代理请求时,如何跳过证书验证?

东山

问题描述

我这边要做一个http代理的重新解析,再次转发服务。
流程是:

  1. 客户端发起请求:
    curl -i -x 127.0.0.1:9081 -U OTYuNDMuMTA2LjI5OjkwOTg=:cmYwZjBoNzBkY3dxbjdieTo5Z2NjYktWdQ== 'https://seller.xiapi.shopee.cn'
    ## -U 后面是真实的代理IP和端口号的base64_encode
  2. Workerman 开启0.0.0.0:9081 端口监听, 将CURL的请求信息解析出真正的代理IP和代理密码. 解析后的CURL请求命令如下:
    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);
    }

}

为此你搜索到了哪些方案及不适用的原因

  1. 我这边因为业务场景必须使用类似curl -x 的命令发送请求,尝试过使用异步的http-client去实现,将客户端请求的报文,利用http-client进行转发,但是一直没尝试成功
495 1 1
1个回答

年代过于久远,无法发表回答
🔝