【问题标题】:TIdHTTP Post to API and save response as a .pdf fileTIdHTTP 发布到 API 并将响应保存为 .pdf 文件
【发布时间】:2021-11-24 23:20:20
【问题描述】:

根据提供的文档,我正在调用 API URL 和一些 parameters

按照 API 的设置方式,响应应该是自动下载 .pdf 文件。

参数为:

  • number - 代表 13 位数字,代表系统中的唯一文件指示符(例如:5277110610029)
  • usernameuser_pass 分别代表访问系统的登录凭据
  • client_id - 代表在他们的系统中与我的帐户关联的唯一客户 ID
  • language - 我指出文件内容应使用的语言(英语或其他可用语言)

文档表明它应该作为Post 请求。

我有以下代码:

var
FileName : string;
URL: String;
Params: TStringList;
memStream: TMemoryStream;
...

begin
FileName:= MainModule.PDFfileName + '.pdf';

 begin
 URL := 'https://someURL/view_integrated_pdf.php?';
 Params := TStringList.Create;
 memStream := TMemoryStream.Create;

  try
  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.Post(URL, Params, memStream);

  finally
  memStream.SaveToFile(ServerModule.FilesFolderPath+'\pdfs\'+FileName);
  Params.Free;
  memStream.Free;

  end;
 end;

pdfForm.ShowModal();

end;

如果我在浏览器中尝试生成的 URLparameters - 它会自动下载 API 给我的名为 numberParameter.pdfpdf 文件

如果我使用提供的代码在 Delphi 中执行此操作,则 10 次中有 8 次会保存 pdf file1 KB Size(通常它在成功文件的 32100 之间)并且当我尝试使用我的pdfForm 和随后的viewer 在程序中打开它,查看器会抛出错误"Invalid PDF structure"

  • 我做错了什么? / 如何正确保存返回 .pdf 文件以从 API 下载的 Post 请求?

更新

根据 cmets,在 Notepad++ 中打开 1kb 生成的 PDF 会简单地将内容显示为 Error username.

这令人费解,因为我检查过并且传递的用户名是准确的 + 如果我在浏览器中粘贴带有参数值的完全相同的 URL,它可以完美运行并为我提供正确的 PDF。

  • 我的代码不是正确的发布和保存正在发送的文件的方法吗?

【问题讨论】:

  • 服务器的响应是什么?您可以尝试在一些高级文本编辑器中打开您保存的文件并查看内容。
  • @MiroslavPenchev 在delphi应用程序中运行代码后,什么也没有。 pdfForm 作为模式弹出,它应该显示 pdf 文件 - 只有它告诉我它的结构无效。使用 Adob​​e 在 Windows 上打开它告诉我它无法打开它(加上 1kb 对应的大小)。使用 Notepad++ 打开它(感谢您的建议 - 刚刚尝试过)将文件内容显示为错误用户名(这是错误的,因为我检查了用户名并在浏览器中粘贴了相同的 URL)。猜API而不是给我pdf内容只是将错误消息写入文件。
  • 因此,您很可能会得到一些带有错误消息/代码的 HTML。如果是这种情况 - 那么您执行的 POST 请求不是服务器所期望的,它会给您一个错误,而不是您期望的 PDF。
  • @MiroslavPenchev 是的。但我没有看到我的代码有任何问题(可能除了使用 memStream?)你同意吗?
  • @t1f 这无关紧要:服务器可能会遇到与您的代码或请求的完美程度无关的问题 - 您必须期待否定响应。

标签: delphi indy indy10 idhttp


【解决方案1】:

如果我在浏览器中尝试生成的 URLparameters - 它会自动下载 API 给我的名为 numberParameter.pdfpdf 文件

在浏览器中执行此操作的唯一方法是将参数放入 URL 的查询组件中,例如:

https://someURL/view_integrated_pdf.php?number=...&username=...&client_id=...&user_pass=...&language=en

... 而不是在 POST 正文中,就像您的代码所做的那样。此外,浏览器会发送GET 请求,而不是POST 请求。除非您正在填写 HTML 网络表单(即<form action="<URL>" method="POST" ...>)并提交它。这就是 API 文档所说的吗?

由于您没有从文档中提供有关此服务器实际期望的任何详细信息,因此我们无法真正告诉您您的代码是正确还是错误。但您的手动测试似乎与您声称的文档所说的服务器所期望的相矛盾。

如果我使用提供的代码在 Delphi 中执行此操作,则 10 次中有 8 次会保存 1 KB 大小的 pdf 文件(成功文件通常在 32 到 100 之间),当我尝试打开它时使用我的 pdfForm 和后续查看器的程序,查看器会抛出错误“无效的 PDF 结构”

来自 cmets,您说您的文件正在接收文本错误消息。 TIdHTTP 会将此类文本保存到您的 TMemoryStream ONLY IF

  • HTTP 服务器在 HTTP 级别报告错误,即它正在使用 HTTP 2xx 成功响应发送文本消息。我怀疑这就是你的情况。默认情况下,如果服务器使用正确 HTTP 错误代码,TIdHTTP 将引发包含文本消息的EIdHTTPProtocolException,并且NOT 将文本保存到您的TMemoryStream完全没有。

  • HTTP 服务器 IS 在 HTTP 级别报告错误,但您在 TIdHTTP.HTTPOptions 属性中同时使用了 hoNoProtocolErrorExceptionhoWantProtocolErrorContent 标志。在这种情况下,TIdHTTP 不会引发EIdHTTPProtocolException,而是将服务器发送的任何数据原样保存到您的TMemoryStream

由于显然没有引发 HTTP 异常,您必须先验证服务器的响应,然后才能使用 pdfForm 中的下载数据,即通过查看 TIdHTTP.Response.ContentType 和/或 TIdHTTP.Response.ContentDisposition 属性来知道服务器是否真的发送了 PDF 文件。

这令人费解,因为我检查过并且传递的用户名是准确的 + 如果我在浏览器中粘贴带有参数值的完全相同的 URL,它可以完美运行并为我提供正确的 PDF。

好吧,一方面,您的代码中有一个错字:numberr 字段需要改为 number

除此之外,将 URL 放入浏览器与您的代码正在执行的操作不同,因此请尝试更改您的代码以模仿您手动执行的操作,例如:

uses
  ..., IdGlobalProtocols, IdHTTP, IdURI;

...

var
  URL : string;
  memStream: TMemoryStream;
begin
  // All parameters into the URI for a HTTP GET request
  URL := 'https://someURL/view_integrated_pdf.php'
       + '?number=' + TIdURI.ParamsEncode(MainModule.number)
       + '&username=' + TIdURI.ParamsEncode(MainModule.User)
       + '&client_id=' + TIdURI.ParamsEncode(MainModule.clientID)
       + '&user_pass=' + TIdURI.ParamsEncode(MainModule.Pass)
       + '&language=en';

  memStream := TMemoryStream.Create;
  try
    MainModule.IdHTTP.Get(URL, memStream);

    // Is it really PDF? Other formats such as plaintext is not wanted.
    if not IsHeaderMediaType(MainModule.IdHTTP.ContentType, 'application/pdf') then Exit;

    memStream.SaveToFile(ServerModule.FilesFolderPath + '\pdfs\' + MainModule.PDFfileName + '.pdf');
  finally
    memStream.Free;
  end;

  pdfForm.ShowModal;
end;

如果这不起作用,请更新您的问题以提供实际文档。

【讨论】:

  • 不确定你是否知道:"i.e." and "e.g." versus "ie" and "eg"
  • @AmigoJack 是的,我愿意。而且我更喜欢我的写作方式
  • 感谢您的详细解答。我可以确认我没有使用您在 HTTPOptions 中提到的两个选项,您的第一种情况似乎是这种情况。 numberr 是我的一个粘贴编辑的错字,不过很好。 API 文档是用我的母语编写的,对于该特定部分,它只是说明了基本 url、所需参数以及应该使用 HTTP POST 调用它的事实。成功调用后,它确实应该提供 pdf 文件。正如您所指出的,我的困惑是我相信浏览器相同的调用 (GET) 也会验证我的 POST 代码。
  • @RemyLebeau 似乎可以正常工作,因为我在代码中使用 GET 而不是 POST(感谢您的示例!)。因此,我会假设文档有问题,并且它们的真正含义是 GET,而不是 POST。刚刚检查并注意到文档中的每个方法都被标记为需要 POST,没有 GET、PUT 等。因此,似乎是错误的复制粘贴。我会尝试与他们澄清。再次感谢您!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-21
  • 2018-03-30
  • 2022-01-09
相关资源
最近更新 更多