使用C++Builder的TIdHTTP通过代理下载文件时遇EIdHTTPProtocolException异常
使用TIdHTTP通过PHP代理下载文件时抛出EIdHTTPProtocolException异常,错误消息为待下载XML文件的第一行内容:<?xml version="1.0" encoding="UTF-8" standalone="yes"?>。直接通过浏览器访问目标URL(无论是否走代理)均可正常下载,但客户端代码走代理时触发报错。
客户端C++Builder代码
TIdHTTP *IdHTTP1 = new TIdHTTP(this); TFileStream *FileStream1; try { FileStream1 = new TFileStream(myTempFileOnDisk, fmCreate); TIdSSLIOHandlerSocketOpenSSL *LHandler = new TIdSSLIOHandlerSocketOpenSSL(this); IdHTTP1->IOHandler = LHandler; IdHTTP1->HandleRedirects = true; IdHTTP1->Request->BasicAuthentication = true; IdHTTP1->Request->Username = "myUserName"; IdHTTP1->Request->Password = "myBase64EncodedPassword"; IdHTTP1->Request->UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0"; //IdHTTP1->Get("https://www.myurl.com/myFileFolder/myFileToDownload.xml", FileStream1); // => 可正常运行! IdHTTP1->Request->ContentType = "text/xml; charset=\"UTF-8\""; IdHTTP1->Get("https://www.myurl.com/myProxy.php", FileStream1); // => 报错 myBoxMessage = "Success!"; if(IdHTTP1->ResponseCode != 200) myBoxMessage = "Failure : "+ IdHTTP1->ResponseText +"."; LHandler->Free(); } catch(EIdHTTPProtocolException &EX0Vl) {myLogMessage = EX0Vl.Message; myLogFunction();} catch(EIdIOHandlerPropInvalid &EX0Vl) {myLogMessage = EX0Vl.Message; myLogFunction();} catch(...) {myLogMessage = "Exception non planifiée."; myLogFunction();} FileStream1->Free();
初始服务端PHP代理代码
if ($_SERVER['PHP_AUTH_PW'] == "myBase64EncodedPassword") { try { $filename = (true)?"myFileToDownload.xml":"myFileToDownload.xlsx"; $filepath = 'https://www.myurl.com/myFileFolder/'. $filename; if(fopen($filepath, 'r')) { header('Pragma: public'); //header('Content-type: application/zip'); header('Content-type: text/xml'); //header('Content-Transfer-Encoding: Binary'); header('Content-Transfer-Encoding: UTF-8'); header('Content-Description: File Transfer'); header('Content-Length: '. filesize($filename)); header('Content-Disposition: attachment; filename='. $filename); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Location: '. $filepath); header('Expires: 0'); ob_clean(); flush(); readfile($filepath); exit; } else { header('HTTP/1.1 404 Not found'); error_log(date('Y-m-d H:i:s').", myProxy : File not found".PHP_EOL, 3, './myLogFile.log'); exit("File not found"); } } catch (Exception $EX0) { header('HTTP/1.1 500 Internal Server Error'); error_log(date('Y-m-d H:i:s').", myProxy : Internal Server Error".PHP_EOL, 3, './myLogFile.log'); exit("Internal Server Error"); } }
待下载XML文件内容
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ArticleTable xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> ... </ArticleTable>
更新后的服务端PHP代码(仍报错)
if ($_SERVER['PHP_AUTH_PW'] == "myBase64EncodedPassword") { try { $filename = (true)?"myFileToDownload.xml":"myFileToDownload.xlsx"; $filepath = 'https://www.myurl.com/myFileFolder/'. $filename; if(fopen($filepath, 'r')) { header('Pragma: public'); //header('Content-type: application/zip'); header('Content-type: text/xml; charset=UTF-8'); header('Content-Transfer-Encoding: Binary'); //header('Content-Transfer-Encoding: UTF-8'); header('Content-Description: File Transfer'); header('Content-Length: '. filesize($filename)); header('Content-Disposition: attachment; filename='. $filename); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Location: '. $filepath); header('Expires: 0'); ob_clean(); flush(); readfile($filepath); header('HTTP/1.1 200'); exit(0); } else { header('HTTP/1.1 404 Not found'); error_log(date('Y-m-d H:i:s').", myProxy : File not found".PHP_EOL, 3, './myLogFile.log'); exit("File not found"); } } catch (Exception $EX0) { header('HTTP/1.1 500 Internal Server Error'); error_log(date('Y-m-d H:i:s').", myProxy : Internal Server Error".PHP_EOL, 3, './myLogFile.log'); exit("Internal Server Error"); } }
移除冲突的
Location响应头:PHP代理中同时发送了Location跳转指令和readfile文件内容输出,导致HTTP响应格式混乱。浏览器会优先处理跳转,但TIdHTTP解析时会把多余的实体内容判定为错误信息。直接删除header('Location: '. $filepath);这一行,让代理直接输出文件内容。修正
Content-Length计算错误:filesize($filename)只能获取服务器本地文件大小,对远程HTTPS URL无效,会返回错误值,导致TIdHTTP校验响应长度失败。要么删除Content-Length头,让PHP自动处理;要么改用CURL先获取远程文件的正确大小:$ch = curl_init($filepath); curl_setopt($ch, CURLOPT_NOBODY, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_exec($ch); $fileSize = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD); curl_close($ch); header('Content-Length: '. $fileSize);删除客户端冗余的
ContentType设置:GET请求不需要设置Request->ContentType,这是用于指定POST/PUT请求的发送内容类型,直接删掉IdHTTP1->Request->ContentType = "text/xml; charset=\"UTF-8\"";。调整HTTP状态码的发送顺序:更新后的PHP代码中
header('HTTP/1.1 200');放在readfile之后,此时响应头已经发送,设置无效。默认响应状态码就是200,直接删除这一行即可。替换
fopen为更可靠的远程文件校验方式:fopen远程URL依赖allow_url_fopen配置,改用CURL校验文件是否存在更稳定:$ch = curl_init($filepath); curl_setopt($ch, CURLOPT_NOBODY, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); $exists = curl_exec($ch); $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($exists && $statusCode == 200) { // 输出文件逻辑 }
内容的提问来源于stack exchange,提问作者ibouka

