PHP实现HTTP Proxy:响应CONNECT请求后无后续客户端请求问题排查
问题分析与解决方案
你的核心问题是没有正确处理CONNECT请求的TCP隧道逻辑——CONNECT不是普通的HTTP请求,它的作用是让代理在客户端和目标HTTPS服务器之间建立一个透明的TCP隧道,而非返回响应后就结束连接。
为什么返回200后没收到后续请求?
客户端在收到代理返回的200 Connection Established后,会立即向代理发送TLS握手数据(比如Client Hello),但如果你的代理在返回200后就关闭连接或未监听转发数据,这些TLS数据无法被接收和转发,自然看起来没有后续请求。
正确的处理步骤
- 解析CONNECT目标地址:从客户端请求行提取目标主机和端口(比如
CONNECT example.com:443 HTTP/1.1里的example.com和443)。 - 建立与目标服务器的TCP连接:用PHP套接字函数(如
stream_socket_client)连接目标服务器对应端口。 - 返回200响应:连接成功后,向客户端返回
HTTP/1.1 200 Connection Established\r\n\r\n,注意必须包含HTTP头结束符(两个换行)。 - 双向数据透传:保持客户端和目标服务器的套接字连接,实时转发两端数据,直到任意一端关闭连接。
简化代码示例
// 假设已通过套接字获取到客户端连接$clientSocket $requestLine = fgets($clientSocket); if (preg_match('/CONNECT (\S+):(\d+) HTTP\/1.1/', $requestLine, $matches)) { $targetHost = $matches[1]; $targetPort = (int)$matches[2]; // 连接目标服务器 $targetSocket = stream_socket_client( "$targetHost:$targetPort", $errno, $errstr, 10 // 连接超时时间 ); if (!$targetSocket) { // 连接失败,返回错误响应 fwrite($clientSocket, "HTTP/1.1 503 Service Unavailable\r\n\r\n"); fclose($clientSocket); exit; } // 返回成功响应,通知客户端可以开始TLS握手 fwrite($clientSocket, "HTTP/1.1 200 Connection Established\r\n\r\n"); // 开启双向数据转发 $sockets = [$clientSocket, $targetSocket]; while (true) { $read = $sockets; $write = null; $except = null; // 监听套接字可读事件 if (stream_select($read, $write, $except, null) === false) { break; } // 处理客户端到目标服务器的数据 if (in_array($clientSocket, $read)) { $data = fread($clientSocket, 8192); if (empty($data)) break; fwrite($targetSocket, $data); } // 处理目标服务器到客户端的数据 if (in_array($targetSocket, $read)) { $data = fread($targetSocket, 8192); if (empty($data)) break; fwrite($clientSocket, $data); } } // 关闭所有连接 fclose($clientSocket); fclose($targetSocket); }
注意事项
- 必须使用CLI模式运行代理脚本,PHP的FastCGI或内置服务器环境不适合处理长连接的隧道转发,会因超时限制中断连接。
- 可通过
stream_set_blocking设置套接字为非阻塞模式,配合stream_select提升转发效率。 - 要处理异常情况:比如目标服务器连接失败、数据转发出错,及时关闭连接并返回对应HTTP错误码。
内容的提问来源于stack exchange,提问作者Reza Alizade
相关产品推荐
相关产品推荐

