PHP/Curl通过HTTP代理上传文件至FTP服务器被Zscaler拦截求助
通过HTTP代理(Zscaler环境)实现PHP FTP文件上传的解决方案
问题核心
你的PHP Curl代码未使用HTTP CONNECT隧道模式,导致FTP上传请求被Zscaler以Not allowed to use FTP over HTTP for non-GET/non-HEAD request拦截;而FileZilla正是通过CONNECT方法建立隧道直接传输FTP协议,才绕过了限制成功上传。此前开启CURLOPT_HTTPPROXYTUNNEL后超时,是因为FTP被动模式下的数据连接未正确通过代理隧道。
修正后的Curl实现代码
$filepath = 'path/to/your/local/file.txt'; $ftp_host = 'ftp-url'; $ftp_user = 'your-ftp-username'; $ftp_pass = 'your-ftp-password'; $proxy_url = 'proxy-url'; $proxy_port = 8080; $fp = fopen($filepath, 'r'); if (!$fp) { die('无法读取本地文件'); } $ftp_url = "ftp://{$ftp_user}:{$ftp_pass}@{$ftp_host}/test.txt"; $ch = curl_init(); // 基础FTP上传配置 curl_setopt($ch, CURLOPT_URL, $ftp_url); curl_setopt($ch, CURLOPT_UPLOAD, true); curl_setopt($ch, CURLOPT_INFILE, $fp); curl_setopt($ch, CURLOPT_INFILESIZE, filesize($filepath)); // 关键:启用HTTP CONNECT隧道(对应FileZilla的代理设置) curl_setopt($ch, CURLOPT_PROXY, $proxy_url); curl_setopt($ch, CURLOPT_PROXYPORT, $proxy_port); curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, true); // 配置FTP被动模式,解决隧道超时问题 curl_setopt($ch, CURLOPT_FTP_USE_EPSV, true); // 优先使用扩展被动模式 curl_setopt($ch, CURLOPT_FTPPORT, '-'); // 强制启用被动模式 curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 延长超时时间适配隧道建立过程 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 可选:开启调试模式排查问题 // curl_setopt($ch, CURLOPT_VERBOSE, true); // $debug_log = fopen('php://temp', 'w+'); // curl_setopt($ch, CURLOPT_STDERR, $debug_log); $result = curl_exec($ch); if (curl_errno($ch)) { echo 'Curl错误: ' . curl_error($ch); // 输出调试日志 // rewind($debug_log); // echo stream_get_contents($debug_log); } else { echo '文件上传成功'; } curl_close($ch); fclose($fp);
关键修改说明
CURLOPT_HTTPPROXYTUNNEL=true:让Curl通过HTTP CONNECT方法建立到FTP服务器的专用隧道,所有FTP协议数据直接通过隧道传输,而非转换成HTTP请求,绕过Zscaler的FTP over HTTP限制。- 被动模式配置:确保FTP的数据连接也通过代理隧道建立,解决之前的超时问题。
- 延长超时时间:隧道建立和FTP握手需要额外时间,避免因超时导致连接失败。
PHP原生FTP扩展的可行性
PHP原生ftp_*系列函数本身不直接支持HTTP代理,但可以通过手动建立HTTP CONNECT隧道的方式实现:
- 先建立代理隧道:
$proxy_socket = stream_socket_client("tcp://{$proxy_url}:{$proxy_port}", $errno, $errstr, 30); if (!$proxy_socket) { die("代理连接失败: $errstr ($errno)"); } // 发送CONNECT请求建立到FTP服务器的隧道 fwrite($proxy_socket, "CONNECT {$ftp_host}:21 HTTP/1.1\r\nHost: {$ftp_host}:21\r\n\r\n"); $response = fread($proxy_socket, 8192); // 验证隧道是否成功建立 if (!strpos($response, '200 Connection Established')) { die("代理隧道建立失败: $response"); }
- 通过隧道socket使用FTP扩展:
// 用隧道socket初始化FTP连接(如需SSL则用ftp_ssl_connect) $ftp_conn = ftp_connect('tcp://', 0, 30, FTP_AUTOSEEK, $proxy_socket); if (!$ftp_conn) { die("FTP连接失败"); } // 登录FTP服务器 if (!ftp_login($ftp_conn, $ftp_user, $ftp_pass)) { die("FTP登录失败"); } // 启用被动模式 ftp_pasv($ftp_conn, true); // 上传文件 if (ftp_put($ftp_conn, 'test.txt', $filepath, FTP_BINARY)) { echo '文件上传成功'; } else { echo '文件上传失败'; } // 关闭连接 ftp_close($ftp_conn); fclose($proxy_socket);
注意:该方式需要确保PHP的FTP扩展已启用,且代理服务器允许CONNECT到FTP的21端口及被动模式所需的高端口。
内容的提问来源于stack exchange,提问作者Xilukarim
相关产品推荐
相关产品推荐

