Search code examples
delphiindyindy10idhttp

TIdHTTP Post to API and save response as a .pdf file


I'm calling an API URL with some parameters according to the documentation provided.

The way the API is set up, the response should be an auto-download of a .pdf file.

The parameters are:

  • number - which stands for a 13 digit number that represents the unique file indicator in their system (example: 5277110610029)
  • username and user_pass respectively - which stand for the login credentials to access the system
  • client_id - which stands for a unique client ID associated with my account in their system
  • language - where I indicate the language the file contents should be in (English or other of the available languages)

Documentation indicates it should be made as a Post request.

I have the following code:

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;

If I try the resulting URL and parameters in the browser - it auto-downloads the pdf file the API gives me with the name numberParameter.pdf

If I do it in Delphi using the provided code, 8 out of 10 times it saves a pdf file with a 1 KB Size (normally it is between 32 and 100 for the successful file) and when I try to open it in the program using my pdfForm and subsequent viewer, the viewer throws an error "Invalid PDF structure"

  • What am I doing wrong? / How do you properly save a Post requests that returns a .pdf file to download from the API?

UPDATE

As per the comments, opening up the 1kb resulting PDF in Notepad++ displays the contents as simply Error username.

This is puzzling since I checked and the username being passed is accurate + if I paste the exact same URL with parameter values filed, in the browser, it works perfectly and gives me the correct PDF.

  • Is my code not the correct approach to Post and save the file being sent?

Solution

  • If I try the resulting URL and parameters in the browser - it auto-downloads the pdf file the API gives me with the name numberParameter.pdf

    The only way to do that in a browser is to put the parameters in the URL's query component, eg:

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

    ... and not in a POST body, as your code is doing. Also, a browser would send a GET request, not a POST request. Unless you are filling in an HTML webform (ie <form action="<URL>" method="POST" ...>) and submitting it. Is that what the API documentation says to do?

    Since you did not provide any details from the documentation about what this server is actually expecting, we can't really tell you whether your code is correct or wrong. But it does seem that your manual tests are contradicting what you claim the documentation says the server is expecting.

    If I do it in Delphi using the provided code, 8 out of 10 times it saves a pdf file with a 1 KB Size (normally it is between 32 and 100 for the successful file) and when I try to open it in the program using my pdfForm and subsequent viewer, the viewer throws an error "Invalid PDF structure"

    From comments, you say your file is receiving a textual error message. TIdHTTP would save such text to your TMemoryStream ONLY IF either:

    • the HTTP server is NOT reporting an error at the HTTP level, ie it is sending the text message using an HTTP 2xx success response. I suspect this is what is happening in your case. By default, if the server uses a proper HTTP error code, TIdHTTP will raise an EIdHTTPProtocolException containing the text message and NOT save the text to your TMemoryStream at all.

    • the HTTP server IS reporting an error at the HTTP level, but you are using the hoNoProtocolErrorException and hoWantProtocolErrorContent flags together in the TIdHTTP.HTTPOptions property. In which case, TIdHTTP would not raise EIdHTTPProtocolException and would instead save whatever data the server sends as-is to your TMemoryStream.

    Since there is clearly no HTTP exception being raised, you will have to validate the server's response before you can use the downloaded data in your pdfForm, ie by looking at the TIdHTTP.Response.ContentType and/or TIdHTTP.Response.ContentDisposition property to know whether the server actually sent a PDF file or not.

    This is puzzling since I checked and the username being passed is accurate + if I paste the exact same URL with parameter values filed, in the browser, it works perfectly and gives me the correct PDF.

    Well, for one thing, you have a typo in your code: the numberr field needs to be number instead.

    Beyond that, putting the URL in a browser is NOT the same operation that your code is doing, so try changing your code to mimic what you are doing manually, eg:

    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;
    

    If that does not work, then please update your question to provide the actual documentation.