0

I made a simple API in PHP to receive some variables from GET/POST requests and post it to and third-party GraphQL API. But I'm having trouble with content encoding.

The application work that way:

Delphi 5 Desktop Application send a simple GET or POST with some variables to my PHP API, and my PHP API make a query request to a third-party GraphQL API.

The GraphQL I send is:

query {
  node(id: 1){
    ... on Organization{
      fullname
      entities(type: STUDENT, search: "Some Student Name"){
        nodes{
          dbId
          fullname
          eid
        }
      }
    }
  }
}

The request URL example is:

http://api-link/request.php?token=xyzwsa&id=17&tipo=boleto&mat=123456&nome_aluno=Some%20Name%20Here&nome_responsavel=Another%20Name&titulo=Just%20a%20test&numero=001122&venc=2018-04-02&valor=1000&linha=23794.00000%2000000.000000%2000000.000000%200%2000000000000000

When I copy and paste the request URL to browser ou call it on command prompt cURL, works fine.

But, when Delphi 5 calls it, it broken my query string with a lot of "+" (plus sign):

Syntax Error GraphQL request (1:6) Cannot parse the unexpected character "+".

1: query+%7B%0D%0A++node%28id%3A+1%29%7B%0D%0A++++...+on+Organization%7B%0D%0A++++++fullname%0D%0A++++++entities%28type%3A+STUDENT%2C+search%3A+%22Some Student Name%22%29%7B%0D%0A++++++++nodes%7B%0D%0A++++++++++dbId%0D%0A++++++++++fullname%0D%0A++++++++++eid%0D%0A++++++++%7D%0D%0A++++++%7D%0D%0A++++%7D%0D%0A++%7D%0D%0A%7D
        ^

The Delphi 5 Application uses TidHTTP component to do it. Code below:

procedure TForm1.ExportaClassApp(prID : Integer; prNumBoleto, prLinhaDigitavel : String);
const cUrl         = 'http://api-link/request.php?';
      cToken       = 'xyzwsa';
      cTipo        = 'boleto';
      cE_Comercial = #138; (* equivalent to & *)

var sSQL              : String;
    qryPesquisa       : TADOQuery;
    oHTTP             : TIdHTTP;
    sRetornoClassApp  : String;
    HTTPClient        : TidHTTP;
    Lista             : TStringStream;
    sParametros       : String;

    Url_Completa      : String;
    sToken            : String;
    sId               : String;
    sTipo             : String;
    sMatricula        : String;
    sNome_Aluno       : String;
    sNome_Responsavel : String;
    sDescricao        : String;
    sNumBoleto        : String;
    sVencimento       : String;
    sValor            : String;
    sLinhaDigitavel   : String;
begin
   oHTTP      := TIdHTTP.Create(Application);
   HTTPClient := TidHTTP.Create(Application);;
   sDescricao := '';

   sDescricao := Copy(sDescricao,1, length(sDescricao) - 2);

   sToken            := 'token='             + cToken;
   sId               := '&id='               + IntToStr(prId);
   sTipo             := '&tipo='             + cTipo;
   sMatricula        := '&mat='              + '123456';
   sNome_Aluno       := '&nome_aluno='       + 'Some Student Name';
   sNome_Responsavel := '&nome_responsavel=' + 'Another Name';
   sDescricao        := '&titulo='           + 'Just a test';
   sNumBoleto        := '&numero='           + prNumBoleto;
   sVencimento       := '&venc='             + '2018-04-02';
   sValor            := '&valor='            + FloatToStr(843 * 100);
   sLinhaDigitavel   := '&linha='            + prLinhaDigitavel;

   sParametros  := sToken + sId + sTipo + sMatricula + sNome_Aluno + sNome_Responsavel + sDescricao + sNumBoleto + sVencimento + sValor + sLinhaDigitavel;
   Url_Completa := cUrl + sPArametros;
   Url_Completa := StringReplace(Url_Completa,' ','%20',[rfReplaceAll, rfIgnoreCase]);

    if edtContentType.Text <> '' then
       oHTTP.Request.ContentType := edtContentType.Text
    else
       oHTTP.Request.ContentType := '';

    if edtContentEncoding.Text <> '' then
       oHTTP.Request.ContentEncoding := edtContentEncoding.Text
    else
       oHTTP.Request.ContentEncoding := '';    

   sRetornoClassApp := oHTTP.URL.URLDecode(oHTTP.Get(Url_Completa));

   btnLimparClick(Self);

   mmoEnvio.Text   := Url_Completa;
   mmoRetorno.Text := sRetornoClassApp;

   FreeAndNil(oHTTP);
end;

Here is the browser/cURL request/response headers (that works):

Array
(
    [0] => POST /graphql HTTP/1.1
    [1] => Host: joy.classapp.co
    [2] => User-Agent: PHP Curl/1.6 (+https://github.com/php-mod/curl)
    [3] => Accept: */*
    [4] => Content-Length: 400
    [5] => Content-Type: application/x-www-form-urlencoded
)
Array
(
    [0] => HTTP/1.1 200 OK
    [1] => Access-Control-Allow-Origin: *
    [2] => Content-Type: application/json; charset=utf-8
    [3] => Date: Mon, 02 Apr 2018 14:37:19 GMT
    [4] => ETag: W/"4d-r2dRUM/0NEHToQUzFDAesWSzSWY"
    [5] => Server: nginx/1.12.1
    [6] => X-Content-Type-Options: nosniff
    [7] => X-DNS-Prefetch-Control: off
    [8] => X-Download-Options: noopen
    [9] => X-Frame-Options: SAMEORIGIN
    [10] => X-XSS-Protection: 1; mode=block
    [11] => Content-Length: 77
    [12] => Connection: keep-alive
)

And below the Delphi 5 TidHTTP request/response headers (that doesn't works):

Array
(
    [0] => POST /graphql HTTP/1.1
    [1] => Host: joy.classapp.co
    [2] => User-Agent: PHP Curl/1.6 ( https://github.com/php-mod/curl)
    [3] => Accept: */*
    [4] => Content-Length: 407
    [5] => Content-Type: application/x-www-form-urlencoded
)
Array
(
    [0] => HTTP/1.1 400 Bad Request
    [1] => Access-Control-Allow-Origin: *
    [2] => Content-Type: application/json; charset=utf-8
    [3] => Date: Mon, 02 Apr 2018 14:47:53 GMT
    [4] => ETag: W/"1f6-RtlkHyZy4MNsaj0EjU9LCzebnSs"
    [5] => Server: nginx/1.12.1
    [6] => X-Content-Type-Options: nosniff
    [7] => X-DNS-Prefetch-Control: off
    [8] => X-Download-Options: noopen
    [9] => X-Frame-Options: SAMEORIGIN
    [10] => X-XSS-Protection: 1; mode=block
    [11] => Content-Length: 502
    [12] => Connection: keep-alive
)

I tried to change Content-Type em Curl Class (I'm using Curl/Curl library) to make POST to GraphQL API, unsuccessful.

I don't know why it's work perfectly in browser/cURL and not in Delphi 5. The problem is in my PHP API or in the Delphi 5 TidHTTP component? Or both?

EDIT: Tried both Delphi 5 and Delphi 2006 with Indy v10.1.5, using GET and POST http verbs. Same error.

Thanks for attention.

Lord_Dracon
  • 290
  • 1
  • 4
  • 14
  • 1
    Please show the actual Delphi code that is not working for you. Delphi 5 is VERY VERY old, are you using the Indy version that shipped with Delphi 5, or are you using an up-to-date modern version? – Remy Lebeau Apr 02 '18 at 15:04
  • @RemyLebeau Tried both Delphi 5 and Delphi 2006 with Indy v10.1.5, using GET and POST http verbs. Same error. I edited my post with Delphi code. – Lord_Dracon Apr 02 '18 at 17:02
  • 1
    Why are you using such outdated versions to begin with? Delphi 5 is almost 20 years old, and Indy 10.1.5 is almost as old as that. The current version of Indy is 10.6.2. There have been MANY changes and fixes to `TIdHTTP` over the last decade. – Remy Lebeau Apr 02 '18 at 17:27
  • 1
    Also, the failure you are experiencing is on a POST request, but your Delphi code is sending a GET request instead. You didn't show the content of the POST body, or the PHP code that is receiving the GET and then generating the POST. So there are several points of possible failure, but you haven't provided enough diagnostic data to figure out which one is actually failing. – Remy Lebeau Apr 02 '18 at 17:42

2 Answers2

2

There are a few problems with your Delphi code.

  • You are instantiating 2 TIdHTTP objects, but only using 1 of them and leaking the other.

  • you should be using TIdURI to format the URL query parameters, not StringReplace().

  • you don't need to, nor should you be, using TIdURI to decode the result of TIdHTTP.Get(). Just use the result as-is.

Try this:

procedure TForm1.ExportaClassApp(prID : Integer; prNumBoleto, prLinhaDigitavel : String);
const
  cUrl         = 'http://api-link/request.php?';
  cToken       = 'xyzwsa';
  cTipo        = 'boleto';
var
  oHTTP             : TIdHTTP;
  sToken            : String;
  sId               : String;
  sTipo             : String;
  sMatricula        : String;
  sNome_Aluno       : String;
  sNome_Responsavel : String;
  sDescricao        : String;
  sNumBoleto        : String;
  sVencimento       : String;
  sValor            : String;
  sLinhaDigitavel   : String;
  sParametros       : String;
  Url_Completa      : String;
  sRetornoClassApp  : String;
begin
  sToken            := 'token='             + cToken;
  sId               := '&id='               + IntToStr(prId);
  sTipo             := '&tipo='             + cTipo;
  sMatricula        := '&mat='              + '123456';
  sNome_Aluno       := '&nome_aluno='       + TIdURI.ParamsEncode('Some Student Name');
  sNome_Responsavel := '&nome_responsavel=' + TIdURI.ParamsEncode('Another Name');
  sDescricao        := '&titulo='           + TIdURI.ParamsEncode('Just a test');
  sNumBoleto        := '&numero='           + TIdURI.ParamsEncode(prNumBoleto);
  sVencimento       := '&venc='             + '2018-04-02';
  sValor            := '&valor='            + FloatToStr(843 * 100);
  sLinhaDigitavel   := '&linha='            + TIdURI.ParamsEncode(prLinhaDigitavel);

  sParametros  := sToken + sId + sTipo + sMatricula + sNome_Aluno + sNome_Responsavel + sDescricao + sNumBoleto + sVencimento + sValor + sLinhaDigitavel;
  Url_Completa := cUrl + sParametros;

  oHTTP := TIdHTTP.Create(nil);
  try
    oHTTP.Request.ContentType := edtContentType.Text;
    oHTTP.Request.ContentEncoding := edtContentEncoding.Text;
    sRetornoClassApp := oHTTP.Get(Url_Completa);
  finally
    oHTTP.Free;
  end;

  btnLimparClick(Self);

  mmoEnvio.Text   := Url_Completa;
  mmoRetorno.Text := sRetornoClassApp;
end;

Alternatively:

procedure TForm1.ExportaClassApp(prID : Integer; prNumBoleto, prLinhaDigitavel : String);
const
  cUrl         = 'http://api-link/request.php?';
  cToken       = 'xyzwsa';
  cTipo        = 'boleto';
var
  oHTTP             : TIdHTTP;
  sParametros       : String;
  Url_Completa      : String;
  sRetornoClassApp  : String;
begin
  sParametros  := Format('token=%s&id=%d&tipo=%s&mat=%s&nome_aluno=%s&nome_responsavel=%s&titulo=%s&numero=%s&venc=%s&valor=%f&linha=%s',
    [cToken,
     prId,
     cTipo,
     '123456',
     TIdURI.ParamsEncode('Some Student Name'),
     TIdURI.ParamsEncode('Another Name'),
     TIdURI.ParamsEncode('Just a test'),
     TIdURI.ParamsEncode(prNumBoleto),
     '2018-04-02',
     843 * 100,
     TIdURI.ParamsEncode(prLinhaDigitavel)]
  );

  Url_Completa := cUrl + sParametros;

  oHTTP := TIdHTTP.Create(nil);
  try
    oHTTP.Request.ContentType := edtContentType.Text;
    oHTTP.Request.ContentEncoding := edtContentEncoding.Text;
    sRetornoClassApp := oHTTP.Get(Url_Completa);
  finally
    oHTTP.Free;
  end;

  btnLimparClick(Self);

  mmoEnvio.Text   := Url_Completa;
  mmoRetorno.Text := sRetornoClassApp;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
0

We identified that this problem only occurred with strings with accentuation. So, after a little research, we found a solution (maybe not the most elegant) to replace ASCII characters to RFC 3986 specification (space turns into %20 instead of "+", for example), and we apply that function only in the variables that may have some accentuation:

sNome_Aluno       := '&nome_aluno='       + fnstUrlEncodeUTF8('Some Student Name');
sNome_Responsavel := '&nome_responsavel=' + fnstUrlEncodeUTF8('Another Name');
sDescricao        := '&titulo='           + fnstUrlEncodeUTF8('Just a test');

And it worked.

Also, we removed the second instance of TIdHTTP mentioned by Remy Lebeau.

Thanks.

EDIT: I'm only the PHP developer, and I work with another dev that are Delphi developer. We work in a small company that doesn't want to acquire a newer Delphi, unfortunately.

Lord_Dracon
  • 290
  • 1
  • 4
  • 14