PHP HTTP代理:收到CONNECT请求后如何创建双向隧道?
PHP实现HTTP代理处理TLS CONNECT请求的方案
处理TLS请求的CONNECT方法核心是建立透明TCP隧道:代理不需要解析或处理TLS加密数据,只需要在客户端和目标服务器之间双向转发原始字节流。要实现这个逻辑,必须用到PHP的底层Socket操作——普通HTTP请求处理函数(如curl、file_get_contents)无法满足长连接双向传输的需求。
实现步骤
- 解析CONNECT请求:从客户端请求中提取目标主机和端口(格式通常为
CONNECT example.com:443 HTTP/1.1) - 建立到目标服务器的Socket连接:用PHP的Socket函数创建与目标服务器的TCP连接
- 通知客户端隧道就绪:返回
HTTP/1.1 200 Connection Established响应,告知客户端可以开始发送TLS握手数据 - 双向转发数据:监听客户端和目标服务器的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
相关产品推荐
相关产品推荐

