You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP HTTP代理:收到CONNECT请求后如何创建双向隧道?

PHP实现HTTP代理处理TLS CONNECT请求的方案

处理TLS请求的CONNECT方法核心是建立透明TCP隧道:代理不需要解析或处理TLS加密数据,只需要在客户端和目标服务器之间双向转发原始字节流。要实现这个逻辑,必须用到PHP的底层Socket操作——普通HTTP请求处理函数(如curl、file_get_contents)无法满足长连接双向传输的需求。

实现步骤

  1. 解析CONNECT请求:从客户端请求中提取目标主机和端口(格式通常为CONNECT example.com:443 HTTP/1.1)
  2. 建立到目标服务器的Socket连接:用PHP的Socket函数创建与目标服务器的TCP连接
  3. 通知客户端隧道就绪:返回HTTP/1.1 200 Connection Established响应,告知客户端可以开始发送TLS握手数据
  4. 双向转发数据:监听客户端和目标服务器的Socket,实时转发两端的数据,直到任意一端关闭连接

完整代码示例

// 启动代理服务器,监听本地8080端口
$proxySocket = stream_socket_server('tcp://0.0.0.0:8080', $errno, $errstr);
if (!$proxySocket) {
    die("Failed to start proxy: $errstr ($errno)");
}

while (true) {
    // 接受客户端连接
    $clientSocket = stream_socket_accept($proxySocket);
    if (!$clientSocket) continue;

    // 读取客户端请求首行
    $requestLine = fgets($clientSocket);
    if (!$requestLine) {
        fclose($clientSocket);
        continue;
    }

    // 处理CONNECT请求
    if (str_starts_with(trim($requestLine), 'CONNECT')) {
        // 解析目标主机和端口
        if (!preg_match('/CONNECT (\S+):(\d+) HTTP\/\d\.\d/', $requestLine, $matches)) {
            fwrite($clientSocket, "HTTP/1.1 400 Bad Request\r\n\r\n");
            fclose($clientSocket);
            continue;
        }
        $targetHost = $matches[1];
        $targetPort = (int)$matches[2];

        // 连接目标服务器
        $targetSocket = stream_socket_client(
            "$targetHost:$targetPort",
            $errno,
            $errstr,
            10 // 10秒超时
        );
        if (!$targetSocket) {
            fwrite($clientSocket, "HTTP/1.1 503 Service Unavailable\r\n\r\n");
            fclose($clientSocket);
            continue;
        }

        // 通知客户端隧道已建立
        fwrite($clientSocket, "HTTP/1.1 200 Connection Established\r\n\r\n");

        // 双向转发数据
        $sockets = [$clientSocket, $targetSocket];
        while (true) {
            $read = $sockets;
            $write = null;
            $except = null;

            // 监听Socket可读事件(阻塞等待数据)
            if (stream_select($read, $write, $except, null) === false) {
                break;
            }

            foreach ($read as $socket) {
                // 读取数据(每次最多8KB,平衡性能和延迟)
                $data = fread($socket, 8192);
                if ($data === false || strlen($data) === 0) {
                    // 一端断开,关闭所有连接
                    fclose($clientSocket);
                    fclose($targetSocket);
                    goto end_tunnel; // 跳出循环
                }

                // 转发数据到另一端
                $destSocket = ($socket === $clientSocket) ? $targetSocket : $clientSocket;
                fwrite($destSocket, $data);
            }
        }
        end_tunnel:
        continue;
    }

    // 处理普通HTTP请求(你的现有逻辑)
    // ... 这里放你原本处理GET/POST等请求的代码 ...
}

// 关闭代理服务器(实际运行中可能不会走到这里)
fclose($proxySocket);

关键注意事项

  • Socket资源管理:必须确保所有Socket连接在断开后被正确关闭,避免资源泄漏
  • 性能与并发:上述代码是单进程阻塞模式,同一时间只能处理一个客户端连接。如果需要高并发,可以结合pcntl_fork实现多进程,或使用ReactPHP等异步Socket库
  • 超时设置:连接目标服务器时设置合理超时,防止代理被挂起
  • 错误处理:针对解析失败、连接失败等情况返回对应的HTTP状态码,符合代理规范

内容的提问来源于stack exchange,提问作者Reza Alizade

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 13:07:44