Mr. E 6 Posted November 11, 2018 (edited) 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 POSTBecause 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. Thanks in advance Edited November 11, 2018 by Mr. E Share this post Link to post
Kryvich 165 Posted November 11, 2018 (edited) @Mr. E You have to import the WSDL scheme, and Delphi generate necessary code for you. Create New VCL Form Application. File | New | Other | Delphi Projects | WebServices | WSDL Importer Location of WSDL file: https://pruebacfdiconsultaqr.cloudapp.net/ConsultaCFDIService.svc?singleWsdl In the generated unit ConsultaCFDIService replace all "https://pruebacfdiconsultaqr.cloudapp.net/ConsultaCFDIService.svc" to "https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc" Use function GetIConsultaCFDIService to obtain IConsultaCFDIService in the generated unit. Demo application is attached. Tested in Delphi 10.2.3 Community edition. SoapClient.ZIP Edited November 11, 2018 by Kryvich Share this post Link to post
Mr. E 6 Posted November 11, 2018 @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
Kryvich 165 Posted November 11, 2018 (edited) @Mr. E Have you tried my SoapClient attached to the previous post? UseWSDL = False. RIO.URL = https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc -- this is the service you need, right? It is listed in "Ejemplo de consumo HTTP" you provided. Soap client uses POST request over HTTPS. The request returns "CodigoEstatus: S - Comprobante obtenido satisfactoriamente.", the same as indicated in "Respuesta obtenida". 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 November 11, 2018 by Kryvich Share this post Link to post
Mr. E 6 Posted November 11, 2018 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
Mr. E 6 Posted November 11, 2018 Found something that I need to read & learn: HTTP - requests. For someone like me, stuck just with desktop apps, is a must read. Share this post Link to post
Kryvich 165 Posted November 11, 2018 (edited) 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 November 11, 2018 by Kryvich 1 Share this post Link to post
Mr. E 6 Posted November 11, 2018 (edited) @Kryvich THANK YOU! (I'm screaming, Yes!) This is like seeing a sunrise and wondering how it's this possible? 🌞 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 November 11, 2018 by Mr. E Solved! 1 Share this post Link to post
Kryvich 165 Posted November 11, 2018 (edited) Thanks to @Arnaud Bouchez for his great framework mORMot. It makes complicated things simple. Edited November 12, 2018 by Kryvich 1 Share this post Link to post
pcplayer99 11 Posted December 26, 2018 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. 1 Share this post Link to post
Remy Lebeau 1392 Posted February 8, 2019 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; 1 1 Share this post Link to post
Mr. E 6 Posted February 8, 2019 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