Delphi使用TIdHTTP POST调用API保存返回PDF文件异常问题咨询
问题根源
- 资源保存逻辑错误:你将
memStream.SaveToFile放在了finally代码块中,无论POST请求是否成功、服务端返回的是不是正常PDF文件,都会强制把内存流里的内容写入本地文件。请求失败时服务端返回的Error username错误文本会直接被存成PDF,自然只有1KB且提示结构无效。 - 参数传递逻辑与后端适配问题:你在浏览器中拼接参数到URL访问的操作本质是GET请求,参数放在URL的查询串中,后端可以正常读取;但代码中POST请求的参数是放在请求体中传输的,如果后端代码只从URL查询串读取参数,不会去请求体中取值,自然拿不到
username等字段,返回账号错误。这也能解释为什么偶尔会请求成功:大概率是后端集群中有部分节点同时兼容读取POST请求体参数。 - 冗余URL符号问题:目标URL末尾多余的
?可能导致部分服务端解析请求异常。 - 缺少请求校验逻辑:没有捕获HTTP请求异常、也没有校验响应状态码,无法提前识别请求失败的情况。
修复方案
首先建议先和接口提供方确认参数传递规则:如果接口要求参数必须放在URL查询串(匹配你浏览器测试的逻辑),直接用GET请求即可;如果要求POST传参,确认请求体的编码格式。以下是兼容两种场景的修复后代码:
var FileName : string; URL: String; Params: TStringList; memStream: TMemoryStream; ResponseCode: Integer; ... begin FileName := MainModule.PDFfileName + '.pdf'; memStream := TMemoryStream.Create; Params := TStringList.Create; try // 方式1:如果接口要求参数放URL查询串(匹配你浏览器测试的逻辑) URL := 'https://someURL/view_integrated_pdf.php'; Params.Add('number=' + MainModule.number); Params.Add('username=' + MainModule.User); Params.Add('client_id=' + MainModule.clientID); Params.Add('user_pass=' + MainModule.Pass); Params.Add('language=en'); // 把参数编码后拼到URL后用GET请求 URL := URL + '?' + Params.Encode; MainModule.IdHTTP.Get(URL, memStream); // 方式2:如果接口确实要求POST传请求体参数,替换上面的GET逻辑为以下代码 (* URL := 'https://someURL/view_integrated_pdf.php'; Params.Add('number=' + MainModule.number); Params.Add('username=' + MainModule.User); Params.Add('client_id=' + MainModule.clientID); Params.Add('user_pass=' + MainModule.Pass); Params.Add('language=en'); // 可选:设置请求头匹配后端要求 MainModule.IdHTTP.Request.ContentType := 'application/x-www-form-urlencoded'; MainModule.IdHTTP.Post(URL, Params, memStream); *) // 校验响应状态 ResponseCode := MainModule.IdHTTP.ResponseCode; if ResponseCode = 200 then begin // 只有请求成功才保存文件,注意要把流指针重置到开头再保存 memStream.Position := 0; memStream.SaveToFile(ServerModule.FilesFolderPath + '\pdfs\' + FileName); pdfForm.ShowModal(); end else begin // 自行处理请求失败逻辑,比如弹出错误提示 ShowMessage('请求失败,错误码:' + IntToStr(ResponseCode)); end; except on E: Exception do begin // 捕获请求异常,比如网络错误、服务端超时等 ShowMessage('请求出错:' + E.Message); end; end; // 资源释放统一放finally,不管请求成功失败都要释放 finally Params.Free; memStream.Free; end; end;
额外注意事项
- 如果参数中包含中文、空格、特殊符号,一定要做URL编码,避免参数解析错误。
- 保存内存流前必须把流的
Position属性设为0,否则会从流的当前位置(通常是写完数据后的末尾)开始保存,得到的是空文件。 - 如果接口要求传递JSON格式的请求体,不能用
TStringList传参,需要把JSON字符串转为流再传入POST方法。
内容的提问来源于stack exchange,提问作者t1f
相关产品推荐
相关产品推荐

