[Solved using Indy] Replicate CURL command with FPHTTPClient lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- lainz · 2018-04-19 17:48 Hi, I finally get CURL to do his job, is a fiscal printer emulator that receives the command Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i curl --noproxy 192.168.56.1 http://192.168.56.1/fiscal.json -H "Content- Type: application/json" --data-binary @%1 > resp.json @%1: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {"ConsultarVersion":{}} This is my code, that doesn't works: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  client: TFPHTTPClient;  ss: TMemoryStream;  sJSON: TJSONStringType;  data: TJSONObject;begin  try    client := TFPHttpClient.Create(nil);  client.AddHeader('Content-Type', 'application/json');    client.AddHeader('Accept', 'application/json');    data := TJSONObject.Create;    data.Add('ConsultarVersion', TJSONObject.Create);    sJSON := data.AsJSON;    data.Free;    ss := TMemoryStream.Create();    ss.Write(Pointer(sJSON)^, length(sJSON));  ss.Position := 0;    client.RequestBody := ss;    Result := client.Post('http://192.168.56.1/fiscal.json');  finally    ss.Free;  client.Free;  end; I'm not sure if is that the fiscal printer needs a file.. instead of JSON in the body. ---------------------------------------------------------------------- Phil · 2018-04-19 18:06 Quote from: lainz on April 19, 2018, 07:48:33 pmI'm not sure if is that the fiscal printer needs a file.. instead of JSON in the body. I think something like this should work: Result := client.SimpleFormPost('http://192.168.56.1', ' {"ConsultarVersion":{}}'); Note the JSON needs to be encoded (not an issue here). ---------------------------------------------------------------------- balazsszekely · 2018-04-19 18:12 I also think SimpleFormPost should work, something like this: http://f orum.lazarus.freepascal.org/index.php/topic,39638.msg272761.html#msg27 2761 ---------------------------------------------------------------------- lainz · 2018-04-19 18:16 Hi @Phil, @Getmem, I try with your code, but the same with my code, it always return an empty string and StatusCode 0 Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i ShowMessage(client.ResponseStatusCode.ToString); // 0    ShowMessage(client.ResponseHeaders.Text); // empty    ShowMessage(client.ResponseStatusText);// empty    ShowMessage(Result); // empty ---------------------------------------------------------------------- balazsszekely · 2018-04-19 18:20 Try to add: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Client.AllowRedirect := True; Just to be sure: you need to post a json file and the server will return a json file? Or you simple need to download a json? ---------------------------------------------------------------------- lainz · 2018-04-19 18:25 No luck. Is a website like the router one, where you can see the settings, and data. And there is an 'endpoint' that is http://192.168.56.1/fiscal.json And I tested with Postman, and it works. So there is something wrong with my lazarus maybe? I will try with 1.8, not trunk. In Postman, I set the radio button 'Raw'. ---------------------------------------------------------------------- lainz · 2018-04-19 18:27 Quote from: GetMem on April 19, 2018, 08:20:47 pmTry to add: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Client.AllowRedirect := True; Just to be sure: you need to post a json file and the server will return a json file? Or you simple need to download a json? Yes, I post the JSON and a JSON is returned. Edit: No luck also with Lazarus 1.8 Info in Postman: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Access- Control-Allow-Methods →OPTIONS, POST, GETAccess-Control-Allow-Origin →*Access-Control-Max-Age →86400Connection →closeContent-Type →application/json; charset=UTF-8Date →Thu, 19 Apr 2018 18:33:39 GMTServer →HSL HTTP Server HSL? ---------------------------------------------------------------------- molly · 2018-04-19 18:45 Quote from: lainz on April 19, 2018, 08:27:59 pmHSL? Perhaps you meant HLS ? ---------------------------------------------------------------------- lainz · 2018-04-19 18:49 Quote from: molly on April 19, 2018, 08:45:05 pmQuote from: lainz on April 19, 2018, 08:27:59 pmHSL? Perhaps you meant HLS ? No, I just copy/paste it. And searching for HSL there is no meaningfull information, maybe it's a 'bug' in text of the emulator.. ---------------------------------------------------------------------- balazsszekely · 2018-04-19 19:03 @lainz As a last try: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  FPHTTPClient: TFPHTTPClient;  SL: TStringList;  Str: String;begin  FPHTTPClient := TFPHTTPClient.Create(nil);  try    FPHTTPClient.AllowRedirect := True;    FPHTTPClient.AddHeader('User-Agent', 'Mozilla/5.0 (compatible; fpweb)');    FPHTTPClient.AddHeader('Content-Type', 'application/json');    FPHTTPClient.AddHeader('Accept', 'application/json');    SL := TStringList.Create;    try      SL.Add('{"ConsultarVersion":{}}');      try        Str := FPHTTPClient.SimpleFormPost('http://192.168.56.1/fiscal.json', SL);      ShowMessage(Str);      except        on E: exception do          ShowMessage(E.Message);      end;    finally      SL.Free;    end;  finally    FPHTTPClient.Free;  end;end; ---------------------------------------------------------------------- molly · 2018-04-19 19:09 Quote from: lainz on April 19, 2018, 08:49:09 pmQuote from: molly on April 19, 2018, 08:45:05 pmQuote from: lainz on April 19, 2018, 08:27:59 pmHSL? Perhaps you meant HLS ? No, I just copy/paste it. And searching for HSL there is no meaningfull information, maybe it's a 'bug' in text of the emulator.. Ok, just trying  I was also unable to find anything useful with regard to HSL (well, in relation with http that is) ---------------------------------------------------------------------- lainz · 2018-04-19 19:10 Thanks @GetMem, but I always get an empty string. I found online that the CURL command is this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i --data- binary DATA  HTTP POST binary data (H) So it should be sent as binary, no matter if it's application/json.. I don't know. But in Postman the only option that works is the 'raw' one. Using form-data, x-www-form-urlencoded or binary does not works. So is binary or not  Quote from: molly on April 19, 2018, 09:09:25 pmQuote from: lainz on April 19, 2018, 08:49:09 pmQuote from: molly on April 19, 2018, 08:45:05 pmQuote from: lainz on April 19, 2018, 08:27:59 pmHSL? Perhaps you meant HLS ? No, I just copy/paste it. And searching for HSL there is no meaningfull information, maybe it's a 'bug' in text of the emulator.. Ok, just trying  I was also unable to find anything useful with regard to HSL (well, in relation with http that is) Thanks molly =) But I get permission to use Indy for now, I will see if it works with Indy. ---------------------------------------------------------------------- lainz · 2018-04-19 19:27 Solved  Indy works fine Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  ReqJson: TStringStream;begin  ReqJson := TStringStream.Create('{"ConsultarVersion":{}}', TEncoding.UTF8);  try    IdHTTP1.Request.ContentType := 'application/json';    ShowMessage(IdHTTP1.Post('http://192.168.56.1/fiscal.json', ReqJson));  finally    ReqJson.Free;  end; I get a JSON with the right data. Code: Javascript  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {        "ConsultarVersion":        {                "Secuencia": "43",              "Estado":                {                        "Impresora":                        [                        ],                      "Fiscal":                        [                                "MemoriaFiscalInicializada"                        ]                },                "Version": "SMH/PT-1000F V: 01.00",                "Marca": "Hasar",                "NombreProducto": "CF",              "VersionMotor": "6.02A",                "FechaFirmware": "17/02/2017 11:10:50",                "VersionProtocolo": "0"        }} Thanks to all, I mark it as solved. ---------------------------------------------------------------------- molly · 2018-04-19 20:12 @lainz: Now that you have a working setup, perhaps you can be bothered to figure out what is the difference between TFPHTTPClient and indy. You could use a service like httpbin (or similar) for that. ---------------------------------------------------------------------- lainz · 2018-04-19 20:21 Quote from: molly on April 19, 2018, 10:12:49 pm@lainz: Now that you have a working setup, perhaps you can be bothered to figure out what is the difference between TFPHTTPClient and indy. You could use a service like httpbin (or similar) for that. Sure. I will do it to see if fphttpclient can be used as well. ---------------------------------------------------------------------- lainz · 2018-04-19 20:34 FPHTTPCLIENT: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {  "args": {},   "data": "{ \"ConsultarVersion\" : {} }",   "files": {},   "form": {},   "headers": {    "Accept": "application/json",     "Connection": "close",     "Content-Length": "27",     "Content-Type": "application/json",     "Host": "httpbin.org"  },   "json": {    "ConsultarVersion": {}  },   "method": "POST",   "origin": "MY IP",   "url": "https://httpbin.org/anything"} INDY: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {  "args": {},   "data": "{\"ConsultarVersion\":{}}",   "files": {},   "form": {},   "headers": {    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",   "Connection": "close",     "Content-Length": "23",     "Content-Type": "application/json",     "Host": "httpbin.org",     "User-Agent": "Mozilla/3.0 (compatible; Indy Library)"  },   "json": {    "ConsultarVersion": {}  },   "method": "POST",   "origin": "MY IP",   "url": "https://httpbin.org/anything"} ---------------------------------------------------------------------- lainz · 2018-04-20 15:08 I noticed the differences between them, but also adding: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function TImpresoraFiscal.POST_FPHTTPClient(sJSON: string): string;var  client: TFPHTTPClient;  reqJSON: TStringStream;begin  ReqJson := TStringStream.Create(sJSON, TEncoding.UTF8);  client := TFPHttpClient.Create(nil);  try    client.AddHeader('Content-Type', 'application/json');    client.AddHeader('Accept',      'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8');  client.AddHeader('User-Agent', 'Mozilla/3.0 (compatible; Indy Library)');    client.RequestBody := reqJSON;    Result := client.Post(URL_IMPRESORA);  finally    reqJSON.Free;    client.Free;  end;end; And getting the exact response from the test site, It does not works, an always returns an empty string with fphttpclient. ---------------------------------------------------------------------- engkin · 2018-04-20 16:35 @lainz, what you posted in Reply #15 does not show everything. AFAIK Indy uses HTTP version 1.0 for posts. Also, Indy handles empty response and turns it into: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i           if Length(LLocalHTTP.Response.ResponseText) = 0 then begin            // Support for HTTP responses without status line and headers          LLocalHTTP.Response.ResponseText := 'HTTP/1.0 200 OK'; {do not localize} A quick test. Indy: QuotePOST /fiscal.json HTTP/1.0 ----------: ---------- Content-Type: application/json Content-Length: 23 Host: somehost.com Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/3.0 (compatible; Indy Library) {"ConsultarVersion":{}} To get FPHTTPClient to use version 1.0: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     client.HTTPVersion := '1.0'; Simple severs might expect Content-Length header directly after Content-Type header. So add it to the headers yourself: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     client.AddHeader('Content-Type', 'application/json');    client.AddHeader('Content-Length', IntToStr(length(sJSON))); My test gave: QuotePOST /fiscal.json HTTP/1.0 Host: somehost.com Content-Type: application/json Content-Length: 27 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*.*;q=0.8 User-Agent: Mozilla/5.0 (compatible; fpweb) ----------: ----- { "ConsultarVersion" : {} } I still see a difference. Edit: The last difference I saw -marked with red dashes above by one of my apps-: Indy keeps the connection alive: QuoteConnection: keep-alive While FPHTTPClient: QuoteConnection: close ---------------------------------------------------------------------- lainz · 2018-04-20 17:55 Adding your suggested lines, I get still empty strings. If anyone want to test, here is the emulator (Windows) http://grupohasar.com/wp- content/uploads/2017/02/IFH-2G-Distribucion-180209.zip The emulator is in the folder 'IFH-2G-Distribucion-180209\HERRAMIENTAS\EMULADOR WINDOWS\Emulador1000-777-240-101-171117 Trio4\1000.exe' and you need to configure a serial port with a tool called 'com0com' (IFH-2G-Distribucion-180209\HERRAMIENTAS\VIRTUALIZADOR PUERTOS SERIE) for x64, is a signed tool. Ports are COM30 and COM31 when the tool installs and executes (setupg.exe). Is really strange, for me it's a bug in the emulator, because I've used fphttpclient always and works fine, but not with this, maybe in the real device works, who knows... ---------------------------------------------------------------------- Remy Lebeau · 2018-04-20 19:39 Quote from: engkin on April 20, 2018, 06:35:29 pmAFAIK Indy uses HTTP version 1.0 for posts. By default, yes.  You can disable that behavior by enabling the hoKeepOrigProtocol flag in the TIdHTTP.HTTPOptions property, then Post() will respect whichever version you specify in the TIdHTTP.ProtocolVersion property. Quote from: engkin on April 20, 2018, 06:35:29 pmAlso, Indy handles empty response and turns it into: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i           if Length(LLocalHTTP.Response.ResponseText) = 0 then begin            // Support for HTTP responses without status line and headers          LLocalHTTP.Response.ResponseText := 'HTTP/1.0 200 OK'; {do not localize} That is to handle old HTTP 0.9 servers, which don't send status lines or headers in responses (they also don't technically support POST either, only GET). The ResponseText should NEVER be empty when communicating with an HTTP 1.0+ server. Quote from: engkin on April 20, 2018, 06:35:29 pmSimple severs might expect Content-Length header directly after Content-Type header. No HTTP server should *require* that, as it is forbidden by the HTTP specification to *require* headers be in any particular order. Quote from: engkin on April 20, 2018, 06:35:29 pmCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     client.AddHeader('Content-Length', IntToStr(length(sJSON))); That will fail to work if the JSON contains any non-ASCII characters.  The 'Content-Length' is the number of *bytes* being sent, not the number of *characters*.  So, if the JSON is encoded as UTF-8 during transmission, the 'Content-Length' must be set to the number of UTF-8 bytes being sent. Quote from: engkin on April 20, 2018, 06:35:29 pmIndy keeps the connection alive: QuoteConnection: keep-alive HTTP 1.1 defaults to keep-alive semantics, unless you specify otherwise.  TIdHTTP explicitly requests keep-alive semantics in HTTP 1.0 unless you specify otherwise (in the TIdHTTP.Request.Connection property).  Ultimately, it is the server that decides whether a keep- alive is actually used or not, and that is reflected in the server's response (in the TIdHTTP.Response.KeepAlive property). Keep-alive or not has no effect whatsoever on how the server processes a POST request, though.  It only affects whether the socket connection is kept open or is closed after the response is sent. ---------------------------------------------------------------------- lainz · 2018-05-12 19:50 Using wireshark we get this: Using Indy Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i POST /fiscal.json HTTP/1.0Connection: keep-aliveContent-Type: application/jsonContent-Length: 22Host: 127.0.0.1Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8User- Agent: Mozilla/3.0 (compatible; Indy Library) {"ConsultarEstado":{}}{        "ConsultarEstado":        {                "Secuencia": "68",              "Estado":                {                        "Impresora":                        [                        ],                      "Fiscal":                        [                              "MemoriaFiscalInicializada"                        ]              },                "EstadoAuxiliar":                [              ],                "EstadoInterno": "EnJornadaFiscal",              "ComprobanteEnCurso": "NoDocumento",                "CodigoComprobante": "Tique",                "NumeroUltimoComprobante": "61",                "CantidadEmitidos": "3",                "CantidadCancelados": "0"        }} Using fphttpclient (yes, is fphttpclient, I've added the user-agent and accept to match indy, to see if that helps..) Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i POST /fiscal.json HTTP/1.0Host: 127.0.0.1Content-Type: application/jsonAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8User- Agent: Mozilla/3.0 (compatible; Indy Library)Content-Length: 22Connection: close {"ConsultarEstado":{}}HTTP/1.0 200 Output FollowsServer: HSL HTTP ServerDate: Sat, 12 May 2018 15:41:39 GMTConnection: closeAccess-Control-Allow-Origin: *Access-Control- Allow-Methods: OPTIONS, POST, GETAccess-Control-Max-Age: 86400Content- Type: application/json; charset=UTF-8 {        "ConsultarEstado":      {                "Secuencia": "61",                "Estado":              {                        "Impresora":                        [                        ],                        "Fiscal":                      [                                "MemoriaFiscalInicializada"                        ]                },                "EstadoAuxiliar":                [                ],                "EstadoInterno": "EnJornadaFiscal",                "ComprobanteEnCurso": "NoDocumento",                "CodigoComprobante": "Tique",                "NumeroUltimoComprobante": "63",                "CantidadEmitidos": "5",                "CantidadCancelados": "0"        }} You can see in both cases, the emulator sends a response. But with fphttpclient it adds this extra stuff: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i HTTP/1.0 200 Output FollowsServer: HSL HTTP ServerDate: Sat, 12 May 2018 15:41:39 GMTConnection: closeAccess-Control-Allow-Origin: *Access- Control-Allow-Methods: OPTIONS, POST, GETAccess-Control-Max-Age: 86400Content-Type: application/json; charset=UTF-8 Maybe because originally tested with KeepConnection set to false.. I've set KeepConnection to true as well, but no luck. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i class function TImpresoraFiscal.POST_FPHTTPClient(sJSON:  string): string;var  client: TFPHTTPClient;  reqJSON: TStringStream;begin  ReqJson := TStringStream.Create(sJSON, TEncoding.UTF8);  client := TFPHttpClient.Create(nil);  client.KeepConnection := True;  try    client.AddHeader('Content-Type', 'application/json');    client.AddHeader('Accept',      'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8');  client.AddHeader('User-Agent', 'Mozilla/3.0 (compatible; Indy Library)');    client.AddHeader('Content-Length', IntToStr(length(sJSON)));    //client.HTTPVersion := '1.0';    client.RequestBody := reqJSON;    try      Result := client.Post('http://' + IP + '/fiscal.json');    except      on e: Exception do        if Result = '' then          Result := '{}';    end;    if Result = '' then      Result := '{}';  finally    reqJSON.Free;    client.Free;  end;end; ---------------------------------------------------------------------- Remy Lebeau · 2018-05-14 17:47 Quote from: lainz on May 12, 2018, 09:50:50 pmUsing wireshark we get this: ... You can see in both cases, the emulator sends a response. But with fphttpclient it adds this extra stuff: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i HTTP/1.0 200 Output FollowsServer: HSL HTTP ServerDate: Sat, 12 May 2018 15:41:39 GMTConnection: closeAccess-Control-Allow-Origin: *Access- Control-Allow-Methods: OPTIONS, POST, GETAccess-Control-Max-Age: 86400Content-Type: application/json; charset=UTF-8 If you were really looking at the raw output from Wireshark, you should be seeing the same HTTP response from the server whether you use Indy or FPHTTPClient.  The fact that you are not seeing the HTTP response headers when using Indy makes me think that you are NOT looking at the Wireshark output correctly, if you are even using Wireshark at all. And just to confirm, you can use Indy's TIdHTTP.Response.ResponseText, TIdHTTP.Response.ResponseCode, and TIdHTTP.Response.RawHeaders properties to verify that you are, in fact, getting those response values from the server, even if you don't see them in Wireshark. ---------------------------------------------------------------------- lainz · 2018-05-14 18:40 Yes. We used Wireshark to visualize the data But to capture we used a third-party tool because Wireshark can not capture localhost. But of course Indy is the one is working fine. I have no problems with it. We did that to debug fphttpclient. We detect that the emulator sends the data for both Indy and fphttpclient. But fphttpclient don't see that response. Edit: is winpcap that doesn't capture localhost and we used rawcap.