Jump to content
Mr. E

How To HTTP POST in Delphi?

Recommended Posts

Regards,

I'm searching how to create an HTTP POST request, but I can't manage to do it.

This is a SOAP Service that I can consume using the ?wsdl (now is hidden); but some nice provider write this:

Quote

https://developers.sw.com.mx/knowledge-base/servicio-publico-de-consulta-estatus-cfdi-sat/
Consumo a través de un POST

Debido a que el SAT oculto la declaración del WebService en productivo lo consumiremos a través de un HTTP Request POST indicándole los datos correspondientes.

----

Consumption through a POST
Because the SAT hidden the WebService declaration in productive, we will consume it through an HTTP Request POST indicating the corresponding data.

POST https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc?wsdl HTTP/1.1
Content-type: text/xml;charset="utf-8"
Accept: text/xml
SOAPAction: http://tempuri.org/IConsultaCFDIService/Consulta
cache-control: no-cache
Host: consultaqr.facturaelectronica.sat.gob.mx
accept-encoding: gzip, deflate
content-length: 414
Connection: close
 
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
   <soapenv:Header/>
   <soapenv:Body>
      <tem:Consulta>
         <!--Optional:-->
         <tem:expresionImpresa><![CDATA[?re=LSO1306189R5&rr=GACJ940911ASA&tt=4999.99&id=e7df3047-f8de-425d-b469-37abe5b4dabb]]></tem:expresionImpresa>
      </tem:Consulta>
   </soapenv:Body>
</soapenv:Envelope>

I try something like this, using Indy; but the web is a dark box for me in Delphi.

// I'm trying using the REST clients, the Indy Http, ... 
uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Xml.xmldom, Xml.XMLIntf, Vcl.ComCtrls, Xml.Win.msxmldom, Xml.XMLDoc
  ,Vcl.StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, Xml.omnixmldom, IPPeerClient,
  REST.Client, Data.Bind.Components, Data.Bind.ObjectScope, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack, IdSSL,
  IdSSLOpenSSL;

...
uses IdMultipartFormData;
...
procedure TForm3.Button3Click(Sender: TObject);
var
  data: TIdMultiPartFormDataStream;
begin
  data := TIdMultiPartFormDataStream.Create;
  try
    // add the used parameters for the script
    data.AddFormField('expresionImpresa', '?re=LAN8507268IA&rr=LAN7008173R5&tt=5800.00&id=4e87d1d7-a7d0-465f-a771-1dd216f63c1a');
    // Call the Post method of TIdHTTP and read the result into TMemo
    Memo1.Lines.Text := IdHTTP1.Post('https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc', data);
    //ERROR
    //HTTP/1.1 415 Cannot process the message because the content type 'multipart/form-data; boundary=--------111018174150958' 
    //was not the expected type 'text/xml; charset=utf-8'.
    //---------------------------
  finally
    data.Free;
  end;
end;


procedure TForm3.Button2Click(Sender: TObject);
var S: TStringList;
   M: TStream;
begin
 S := TStringList.Create;
 M := TMemoryStream.Create;
 try
   S.Values['expresionImpresa'] :=
   '?re=LAN8507268IA&rr=LAN7008173R5&tt=5800.00&id=4e87d1d7-a7d0-465f-a771-1dd216f63c1a';
   //'![CDATA[?re=LAN8507268IA&rr=LAN7008173R5&tt=5800.00&id=4e87d1d7-a7d0-465f-a771-1dd216f63c1a]]';

   IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL1;
   //IdHTTP1.Request.ContentType := 'application/x-www-form-urlencoded';
   IdHTTP1.Post('https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc', S, M);
   Memo1.Lines.Add(Format('Response Code: %d', [IdHTTP1.ResponseCode]));
   Memo1.Lines.Add(Format('Response Text: %s', [IdHTTP1.ResponseText]));
   // ERROR:
   //HTTP/1.1 415 Cannot process the message because the content type
   //'application/x-www-form-urlencoded' was not the expected type 'text/xml; charset=utf-8'.
   //---------------------------
   M.Position := 0;
   S.LoadFromStream(M);
   Memo1.Lines.AddStrings(S);
 finally
   S.Free;
   M.Free;
 end;
end;

Consuming the web services is a lot easier, just I need to Import the WSDL (is hidden right now but I still manage to get before), create the unit and start using the service, the consume is just:

procedure TwsConsultaCFDIEmpresa.doConsulta (const representacionI : string);
var
  ics : IConsultaCFDIService;
  vAcuse : Acuse2;
begin
  try
    ics := GetIConsultaCFDIService(false);
    // representacionI is something like:
    // ?re=LAN8507268IA&rr=LAN7008173R5&tt=5800.00&id=4e87d1d7-a7d0-465f-a771-1dd216f63c1a
    vAcuse:= ics.Consulta(representacionI);
  finally
  	with vAcuse do
    ShowMessage(CodigoEstatus +'/ '+EsCancelable +'/ '+ Estado  +'/ '+EstatusCancelacion);    
  end;
end;

 

I need to read some basic documentation, but alas, I don't know how to start. Maybe with a working example can I go forward.

I don't know if using the "REST clients" are something that I need.

 

Edit: Using Firefox I got an extension named "Httprequester" but I can't make a successful request/POST. :classic_sad:

Thanks in advance

Edited by Mr. E

Share this post


Link to post

@Mr. E You have to import the WSDL scheme, and Delphi generate necessary code for you.

  1. Create New VCL Form Application.
  2. File | New | Other | Delphi Projects | WebServices | WSDL Importer
  3. Location of WSDL file: https://pruebacfdiconsultaqr.cloudapp.net/ConsultaCFDIService.svc?singleWsdl
  4. In the generated unit ConsultaCFDIService replace all "https://pruebacfdiconsultaqr.cloudapp.net/ConsultaCFDIService.svc" to "https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc"
  5. Use function GetIConsultaCFDIService to obtain IConsultaCFDIService in the generated unit.

Demo application is attached. Tested in Delphi 10.2.3 Community edition.SimpleSoapClient.jpg.be5890c262bf45dc9159bdc3069a1d34.jpg

 

SoapClient.ZIP

Edited by Kryvich

Share this post


Link to post

@KryvichThank you, I appreciate your work.

The problem: the sat.gob.mx has been doing changes to their web services, this is the second variant of the w.s., and they decide to hide the wsdl for this and many others services.

Luckily an authorized certification provider (PAC) create this test service, cloning the functions of the real w.s.

Hiding the wsdl services is forcing us to use it using a HTTP POST Request in Delphi. This is what I seek to learn, preventing more changes in the future.

This sat is tied to m$ technologies, leaving us (the Delphi devs.) with the burden to adapt our approach to each changes or new services they publish.

Again thank you very much.

Share this post


Link to post

@Mr. E Have you tried my SoapClient attached to the previous post? UseWSDL = False.

But you can use other third-party library, or prepare and call HttpSendRequest manually, it is in WinApi.WinInet. I can recommend THttpRequest class from SynCrtSock.pas, mORMot. Something like this:

fHttpRequest.Request(
  'https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc?wsdl',
  'POST', 20000, InHeader, inData, ' text/xml;charset="utf-8"', outHeaders, outData);

P.S. Oh, you wrote "has been doing changes". Well then you always can use THttpRequest class and modify the request as you need.

Edited by Kryvich

Share this post


Link to post
3 minutes ago, Kryvich said:

But you can use other third-party library, ... I can recommend THttpRequest class from SynCrtSock.pas, mORMot. Something like this:

This!

Now I need to learn how fill the parameters, as the example provided:

POST https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc?wsdl HTTP/1.1
Content-type: text/xml;charset="utf-8"
Accept: text/xml
SOAPAction: http://tempuri.org/IConsultaCFDIService/Consulta
cache-control: no-cache
Host: consultaqr.facturaelectronica.sat.gob.mx
accept-encoding: gzip, deflate
content-length: 414
Connection: close
 
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
   <soapenv:Header/>
   <soapenv:Body>
      <tem:Consulta>
         <!--Optional:-->
         <tem:expresionImpresa><![CDATA[?re=LSO1306189R5&rr=GACJ940911ASA&tt=4999.99&id=e7df3047-f8de-425d-b469-37abe5b4dabb]]></tem:expresionImpresa>
      </tem:Consulta>
   </soapenv:Body>
</soapenv:Envelope>

Looking the mORMot unit/doc and searching for examples.

 

Thank you!

Share this post


Link to post

Each of us was in such a situation. See, this is a ready-made working example. You can use it as a basis:

program TestPostRequest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, SynCrtSock;

const
  RequestHeaderTemplate =
    'SOAPAction: http://tempuri.org/IConsultaCFDIService/Consulta';
  RequestDataTemplate =
    '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">'
    + '   <soapenv:Header/>'
    + '   <soapenv:Body>'
    + '      <tem:Consulta>'
    + '         <!--Optional:-->'
    + '         <tem:expresionImpresa><![CDATA[%expresionImpresa%]]></tem:expresionImpresa>'
    + '      </tem:Consulta>'
    + '   </soapenv:Body>'
    + '</soapenv:Envelope>';

function SendCommand(Request: THttpRequest; const ExpresionImpresa: string): SockString;
var
  outHeaders: SockString;
begin
  Result := '';
  try
    Request.Request('ConsultaCFDIService.svc?wsdl', 'POST', 20000,
      RequestHeaderTemplate,
      SockString(StringReplace(RequestDataTemplate, '%expresionImpresa%',
        ExpresionImpresa, [])),
      'text/xml;charset="utf-8"', outHeaders, Result);
  except
    on E: Exception do begin
      Writeln('Error: ', E.Message);
      Exit;
    end;
  end;
end;

var
  Request: THttpRequest;
  Answer: SockString;
begin
  try
    Request := TWinHTTP.Create('consultaqr.facturaelectronica.sat.gob.mx', '', True);
    try
      Answer := SendCommand(Request,
        '?re=LSO1306189R5&rr=GACJ940911ASA&tt=4999.99&id=e7df3047-f8de-425d-b469-37abe5b4dabb');
      Writeln('Answer:');
      Writeln('----------------');
      Writeln(Answer);
      Writeln('----------------');
      // Next requests go here ...
    finally
      Request.Free;
    end;
    Writeln('Press Enter to continue.');
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

 

Edited by Kryvich
  • Thanks 1

Share this post


Link to post

@Kryvich THANK YOU! (I'm screaming, Yes!)

This is like seeing a sunrise and wondering how it's this possible? 🌞

image.png.611325b4600644b7759af07dbecdb0ce.png

I'm reading your code, is slick and to the point.

This isn't trivial for anyone without previous knowledge, I need a lot of theory to learn (web, sockets, mORMot, etc.)

--

How can I mark the post as solved (with a lot to read/learn) ?

 

 

 

Edited by Mr. E
Solved!
  • Like 1

Share this post


Link to post

if it is a WebService, maybe you no need to know how http works. You can just use Delphi's WebService framework, import the server side WebService functions (in Delphi it is based on IInterface),and just call its WebService's  functions by using THTTPRIO.

  • Like 1

Share this post


Link to post
On 11/10/2018 at 4:53 PM, Mr. E said:

I try something like this, using Indy; but the web is a dark box for me in Delphi.

Your TIdHTTP code is all wrong for this kind of POST request.  Try something more like this instead:

uses
  ..., IdGlobalProtocols, IdHTTP, IdSSLOpenSSL;

procedure TForm3.Button2Click(Sender: TObject);
var
  SoapMsg: string;
  PostData, ResponseData: TStream;
begin
  // buid up this string however you want (XML library, etc) ...
  SoapMsg :=
    '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">' +
    '  <soapenv:Header/>' +
    '  <soapenv:Body>' +
    '    <tem:Consulta>' +
    '      <!--Optional:-->' +
    '      <tem:expresionImpresa><![CDATA[?re=LSO1306189R5&rr=GACJ940911ASA&tt=4999.99&id=e7df3047-f8de-425d-b469-37abe5b4dabb]]></tem:expresionImpresa>' +
    '    </tem:Consulta>' +
    '  </soapenv:Body>' +
    '</soapenv:Envelope>';

  ResponseData := TMemoryStream.Create;
  try
    PostData := TStringStream.Create(SoapMsg, TEncoding.UTF8);
    try
      IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL1;

      IdHTTP1.HTTPOptions := IdHTTP1.HTTPOptions + [hoNoProtocolErrorException, hoWantProtocolErrorContent];

      IdHTTP1.Request.ContentType := 'text/xml';
      IdHTTP1.Request.Charset := 'utf-8';
      IdHTTP1.Request.Accept := 'text/xml';
      IdHTTP1.Request.CacheControl := 'no-cache';
      IdHTTP1.Request.CustomHeaders.Values['SOAPAction'] := 'http://tempuri.org/IConsultaCFDIService/Consulta';

      IdHTTP1.Post('https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc?wsdl', PostData, ResponseData);
    finally
      PostData.Free;
    end;

    Memo1.Lines.BeginUpdate;
    try
      Memo1.Lines.Add(Format('Response Code: %d', [IdHTTP1.ResponseCode]));
      Memo1.Lines.Add(Format('Response Text: %s', [IdHTTP1.ResponseText]));
      ResponseData.Position := 0;
      ReadStringsAsCharset(ResponseData, Memo1.Lines, IdHTTP1.Response.Charset);
    finally
      Memo1.Lines.EndUpdate;
    end;
  finally
    ResponseData.Free;
  end;
end;

 

  • Like 1
  • Thanks 1

Share this post


Link to post
57 minutes ago, Remy Lebeau said:

Your TIdHTTP code is all wrong for this kind of POST request.  Try something more like this instead

Thank you Gambit/Remy Lebeau! 😀

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×