RafaelHarth RafaelHarth - 3 months ago 18
reST (reStructuredText) Question

Cannot receive contentType "application/pdf" using RestResponse

My application sends data(personal info) using "application/json". If data is valid, then, server sends me back a PDF File through "application/pdf". But when RestResponse arrives, an exception pops out: "No mapping for the unicode character exists in the target multibyte code page". I don't know whats happening. I'm working on this in the past 4 days and I can't fix this. Exception pops out at line: "RRequest.Execute;". Here's my code guys:

procedure TThreadBoleto.Execute;
var
RCtx : TRttiContext;
RType : TRttiType;
RProp : TRttiProperty;
I : integer;
PDF : TFile;
begin
try
try
RClient := TRESTClient.Create('');
with RClient do begin
AcceptEncoding := 'identity';
FallbackCharsetEncoding := 'UTF-8';
Accept := 'application/json;text/plain;application/pdf;';
AcceptCharset := 'UTF-8';
BaseURL := 'https://sandbox.boletocloud.com/api/v1/boletos';
ContentType := 'application/x-www-form-urlencoded';
HandleRedirects := true;

RCtx := TRttiContext.Create;
RType := RCtx.GetType(THRBoleto.ClassType);
I := 0;
for RProp in RType.GetProperties do
begin
Params.AddItem;
Params.Items[I].name := LowerCase(RProp.Name.Replace('_','.'));
Params.Items[I].Value := RProp.GetValue(THRBoleto).AsString;
I := I + 1;
end;
end;

RRequest := TRESTRequest.Create(RRequest);
with RRequest do begin
Accept := 'application/json;text/plain;application/pdf;';
Client := RClient;
Method := rmPost;
SynchronizedEvents := false;
AcceptCharset := 'UTF-8';
end;

RResponse := TRESTResponse.Create(RResponse);
RResponse.ContentType := 'application/pdf;*/*;';
RResponse.ContentEncoding := 'UTF-8';

RAuth := THTTPBasicAuthenticator.Create('','');
with RAuth do begin
Username := 'anAPItokenAccess';
Password := 'token';
end;

RClient.Authenticator := RAuth;
RRequest.Response := RResponse;

RRequest.Execute;
PDF.WriteAllBytes(ExtractFilePath(Application.ExeName)+'boleto.pdf',RResponse.RawBytes);
OutputStrings.Add(RResponse.Content);
OutputStrings.Add('');
OutputStrings.Add('');
OutputStrings.AddStrings(RResponse.Headers);
except on E:Exception do
ShowMessage('Error: '+E.Message);
end;
finally
THRBoleto.Free;
end;
end;

Answer

Are you sure the error is happening on the RRequest.Execute() call? You are trying to receive the PDF data as a String when you read the RResponse.Content property, so I would expect a Unicode error on that call instead. A PDF file is not textual data, it is binary data, so the only safe way to receive it is with the RResponse.RawBytes property.

Also, you should not be setting the RResponse.ContentType or RResponse.ContentEncoding properties at all (not to mention that you are setting them to invalid values anyway). They will be filled in according to the actual response that is received.

Since you are setting the RRequest.Accept property to include 3 different media types that you are willing to accept in a response, you need to look at the RResponse.ContentType property value to make sure you are actually receiving a PDF file before saving the RawBytes to a .pdf file. If you receive a text or JSON response instead, you can't process them as a PDF.

Personally, I find the REST components to be quite buggy. You might have better luck using Indy's TIdHTTP instead, eg:

uses
  ..., IdGlobal, IdGlobalProtocols, IdHTTP, IdSSLOpenSSL;

procedure TThreadBoleto.Execute;
var
  RCtx : TRttiContext;
  RType : TRttiType;
  RProp : TRttiProperty;
  Client: TIdHTTP;
  Params: TStringList;
  Response: TMemoryStream;
begin
  Client := TIdHTTP.Create;
  try
    with Client.Request do begin
      AcceptEncoding := 'identity';
      Accept := 'application/json;text/plain;application/pdf';
      AcceptCharset := 'UTF-8';
      ContentType := 'application/x-www-form-urlencoded';
      BasicAuthentication := True;
      Username := 'anAPItokenAccess';
      Password := 'token';
    end;
    Client.HandleRedirects := true;

    RCtx := TRttiContext.Create;
    RType := RCtx.GetType(THRBoleto.ClassType);

    Response := TMemoryStream.Create;
    try
      Params := TStringList.Create;
      try
        for RProp in RType.GetProperties do
          Params.Add(LowerCase(RProp.Name.Replace('_','.')) + '=' + RProp.GetValue(THRBoleto).AsString);

        Client.Post('https://sandbox.boletocloud.com/api/v1/boletos', Params, Response);
      finally
        Params.Free;
      end;

      Response.Position := 0;
      case PosInStrArray(ExtractHeaderMediaType(Client.Response.ContentType), ['application/pdf', 'application/json', 'text/plain'], False) of
        0: begin
          // save PDF
          Response.SaveToFile(ExtractFilePath(Application.ExeName)+'boleto.pdf');
          OutputStrings.Add('[PDF file]');
        end;
        1: begin
          // process JSON as needed
          OutputStrings.Add(ReadStringAsCharset(Response, Client.Response.Charset));
        end;
        2: begin
          // process Text as needed
          OutputStrings.Add(ReadStringAsCharset(Response, Client.Response.Charset));
        end;
      else
        // something else!
        OutputStrings.Add('[Unexpected!]');
      end;
    finally
      Response.Free;
    end;

    OutputStrings.Add('');
    OutputStrings.Add('');
    OutputStrings.AddStrings(Client.Response.RawHeaders);
  finally
    Client.Free;
  end;
end;

procedure TThreadBoleto.DoTerminate;
begin
  if FatalException <> nil then
  begin
    // Note: ShowMessage() is NOT thread-safe!
    ShowMessage('Error: ' + Exception(FatalException).Message);
  end;
  THRBoleto.Free;
  inherited;
end;
Comments