Hola amigos,
En ésta entrada les hablaré acerca de la versión ‘gratuita’ de Delphi llamada Delphi Starter Edition, versión 10.2 Tokyo la cual nos permite, según su contrato de licencia, generar aplicaciones comerciales aunque con algunas limitaciones propias de éste tipo de ediciones.
Si quieren conocer acerca de éste producto, su licencia de uso y las limitaciones propias de la versión, les proporciono una lista de enlaces que me parecen muy interesantes.
- – Preguntas y Respuestas.
- – Matriz de características.
- – Delphi / C++Builder Starter チュートリアルシリーズ シーズン2.
Quiero mencionar que la intención de ésta entrada es la de mostrar que con ésta versión podemos comenzar a desarrollar soluciones reales y no solo el famoso «Hola Mundo». Por supuesto al ser un producto gratuito tiene algunas limitaciones pero nada que nos evite hacer uso de nuestras capacidades y dar forma a aplicaciones de alto rendimiento.
Para ésta entrada les sugiero que descarguen el programa soapUI el cual es open source y que nos facilitará la vida al generar un cliente para el consumo de Servicios Web así como descargar e instalar Delphi 10.2 Tokio Starter.
Una de las limitantes de la versión Starter es que no cuenta con el asistente para la importación del archivo WSDL el cual genera la unidad que implementa los métodos del Servicio Web para su consumo y aunque ésta unidad se puede obtener vía línea de comandos he querido hacerlo con una solución alternativa a través de un proyecto llamado cURL el cual está orientado a la transferencia de archivos y que soporta varios protocolos como FTP, FTPS, HTTP, HTTPS, TFTP, SCP, SFTP, Telnet, DICT, FILE y LDAP entre otros.
Cabe mencionar que éste proyecto lo conocí hace unos años, para ser exactos el 14 de Marzo de 2011, cuando nuestro amigo Domingo Seaone publicó en el foro delphiaccess punto com una implementación de ésta biblioteca desarrollando una unidad con Delphi y que me ha permitido dar solución a varios requerimientos de clientes a lo largo de éstos 6 años que han pasado.
Pues bien, comenzaremos por obtener los archivos XML de Request y Response del Servicio Web a través de la aplicación soapUI, para ello usaremos el Servicio Web del Banco de México para consultar el tipo de cambio del peso frente al Dólar, el Euro, la Libra Esterlina y el Yen. La url del servicio web es la siguiente:
http://www.banxico.org.mx/DgieWSWeb/DgieWS?WSDL
Creamos un nuevo proyecto SOAP seleccionamos el método tiposDeCambioBanxico y enviamos la petición, el Servicio Web nos regresará la información en un archivo soap XML conteniendo los diferentes tipos de cambio como se muestra en la imagen siguiente:
La versión Starter de Delphi tampoco incluye el asistente XML Data Binding por lo que tenemos que construir el REQUEST y el RESPONSE de forma manual, bueno, no todo en la vida es fácil, pero veamos la situación como una buena oportunidad para aprender algo nuevo. 🙂
Ya estamos listos para comenzar nuestro tutorial, como primer paso vamos a crear una clase que generará la estructura del mensaje de petición SOAP para enviarla a través de cURL al servidor del Servicio Web y serializar la respuesta SOAP para obtener (en este caso) los tipos de cambio disponibles del peso mexicano contra otras monedas.
La clase contendrá las propiedades, variables y métodos necesarios para el consumo del Servicio Web como se muestra a continuación.
TRequestResponse = class
type TRequestResponse = class private xmlArch: IXMLDocument; envelopeSOAP: IXMLNode; Curl: TCURL; curlResult: CURLcode; curlError: string; Stream: TMemoryStream; StreamOut: TMemoryStream; function creaRequest: IXMLDocument; function enviaRequest(url, Post: AnsiString; out Reply: AnsiString): Boolean; function RecursiveFindNode(ANode: IXMLNode; const SearchNodeName: string): IXMLNode; function buscaTC(xml: IXMLDocument): TStrings; public function ConsultaTipoDeCambio(endPoint: AnsiString): TStrings; end;
El método creaRequest genera el mensaje de petición SOAP utilizando el objeto IXMLDocument el cual se encargará de dar el formato adecuado para poder enviarlo al servidor donde está alojado el Servicio Web que queremos consumir.
function TRequestResponse.creaRequest: IXMLDocument;
function TRequestResponse.creaRequest: IXMLDocument; begin xmlArch := NewXMLDocument; xmlArch.Encoding := 'UTF-8'; envelopeSOAP := xmlArch.AddChild('soapenv:Envelope'); with envelopeSOAP do begin Attributes['xmlns:ws'] := 'http://ws.dgie.banxico.org.mx'; Attributes['xmlns:soapenv'] := 'http://schemas.xmlsoap.org/soap/envelope/'; Attributes['xmlns:xsd'] := 'http://www.w3.org/2001/XMLSchema'; Attributes['xmlns:xsi'] := 'http://www.w3.org/2001/XMLSchema-instance'; AddChild('soapenv:Header'); with AddChild('soapenv:Body') do begin with AddChild('ws:tiposDeCambioBanxico') do begin Attributes['soapenv:encodingStyle'] := 'http://schemas.xmlsoap.org/soap/encoding/'; end; end; end; Result := xmlArch; end;
Con éste código se genera el mensaje SOAP necesario para realizar la petición al Servicio Web y que será enviado por medio de cURL.
Por supuesto que habrá formas más efectivas de hacer esto, sin embargo ésta me parece a mi la más simple y controlada.
<?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.dgie.banxico.org.mx"> <soapenv:Header/> <soapenv:Body> <ws:tiposDeCambioBanxico soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </soapenv:Body> </soapenv:Envelope>
A continuación agregaremos la unidad creada por mi amigo Domingo Seoane la cual encapsula el método para enviar la petición al Servicio Web, dicha unidad se llama uCurl.pas la cual publicó en DelphiAccess.
A continuación agregamos la función para realizar la llamada a dicha unidad y que llamaremos enviaRequest. En ésta función necesitaremos la URL del Servicio Web y el Request que hemos creado con la función creaRequest, dicha función regresará dos valores, uno de tipo out donde obtendrémos el Response del Servicio Web y un boolean que nos indicará si la función fué procesada correctamente o no.
function TRequestResponse.enviaRequest(url, Post: AnsiString; out Reply: AnsiString): Boolean;
function TRequestResponse.enviaRequest(url, Post: AnsiString; out Reply: AnsiString): Boolean; //****************************************************************************** function ReadFromStream(Buffer: PAnsiChar; Size, Count: Integer; Stream: TStream): Integer; cdecl; begin Result:= Stream.Read(Buffer^,Size*Count) div Size; end; function SaveToStream(Buffer: PAnsiChar; Size, Count: Integer; Stream: TStream): Integer; cdecl; begin Result:= Stream.Write(Buffer^,Size*Count) div Size; end; function MemoryStreamToString(M:TMemoryStream): AnsiString; begin SetString(Result, PAnsiChar(M.Memory), M.Size); end; procedure ErrorCurl(const mensaje: string); begin raise Exception.Create(mensaje); exit; end; //****************************************************************************** begin Result:= false; Curl:= curl_easy_init; if Curl <> nil then try if curl_easy_setopt(Curl, CURLOPT_VERBOSE, TRUE) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_VERBOSE.'); if curl_easy_setopt(Curl, CURLOPT_USE_SSL, CURLUSESSL_ALL) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_USE_SSL.'); if curl_easy_setopt(Curl, CURLOPT_SSL_VERIFYPEER, FALSE) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_SSL_VERIFYPEER.'); if curl_easy_setopt(Curl, CURLOPT_URL, PAnsiChar(url)) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_URL.'); if curl_easy_setopt(Curl, CURLOPT_POST, 1) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_POST.'); if curl_easy_setopt(Curl, CURLOPT_READFUNCTION, @ReadFromStream) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_READFUNCTION.'); if curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, @SaveToStream) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_WRITEFUNCTION.'); Stream := TMemoryStream.Create; StreamOut := TMemoryStream.Create; try with TStringList.Create do try Add(unicodeString(Post)); SaveToStream(Stream); finally Free; end; Stream.Position:= 0; if curl_easy_setopt(Curl, CURLOPT_INFILE, Stream) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_INFILE.'); if curl_easy_setopt(Curl, CURLOPT_POSTFIELDSIZE, Stream.Size) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_POSTFIELDSIZE.'); Header := nil; Header := curl_slist_append(Header, 'SOAPAction: ""'); Header := curl_slist_append(Header, 'Accept-Encoding: gzip, deflate'); Header := curl_slist_append(Header, 'Content-Type: text/xml;charset=UTF-8;'); Header := curl_slist_append(Header, 'User-Agent: Apache-HttpClient/4.1.1 (java 1.5)'); try if curl_easy_setopt(Curl, CURLOPT_FILE, StreamOut) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_FILE.'); if curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Header) <> CURLE_OK then ErrorCurl('No se pudo asignar CURLOPT_HTTPHEADER.'); curlResult := curl_easy_perform(Curl); if curlResult = CURLE_OK then begin Reply := MemoryStreamToString(StreamOut); curlError := ''; result := true; end else begin curlError := curl_easy_strerror(curlResult); result := false; end; finally curl_slist_free_all(Header); end; finally Stream.Free; StreamOut.Free; end; finally curl_easy_cleanup(Curl); end; end;
Ya estamos a punto de terminar la clase que consumirá el Servicio Web, solo nos falta serializar el Response del Servicio Web lo cual harémos manualmente debido a que tenemos ciertas limitantes al no tener disponible el asistente XML Data Binding en el IDE de nuestro Delphi Starter. Cabe mencionar que si ustedes tienen una forma más elegante de hacerlo, no duden en retroalimentarnos para enriquecer éste tutorial.
Para ello vamos a utilizar dos funciones, una que tome prestada del sitio stackoverflow y una que escribiremos nosotros mismos, dichas funciones son
function RecursiveFindNode(ANode: IXMLNode; const SearchNodeName: string): IXMLNode;
{ Función basada en la original que se tomó del siguiente enlace http://stackoverflow.com/questions/28901757/delphi-find-xml-node } function TRequestResponse.RecursiveFindNode(ANode: IXMLNode; const SearchNodeName: string): IXMLNode; var I: Integer; begin if CompareText(ANode.NodeName, SearchNodeName) = 0 then Result := ANode else if not Assigned(ANode.ChildNodes) then Result := nil else begin for I := 0 to ANode.ChildNodes.Count - 1 do begin Result := RecursiveFindNode(ANode.ChildNodes[I], SearchNodeName); if Assigned(Result) then Exit; end; end; end;
function buscaTC(xml: IXMLDocument): TStrings;
function TRequestResponse.buscaTC(xml: IXMLDocument): TStrings; var I: integer; nodoData: IXMLNode; Linea: string; begin Result := TStringList.Create; Result.Text := ''; nodoData := xml.ChildNodes[1].ChildNodes[1]; if nodoData <> nil then begin for I := 0 to nodoData.ChildNodes.Count-1 do begin if nodoData.ChildNodes[I].NodeName = 'bm:Series' then begin Result.Add(nodoData.ChildNodes[I].Attributes['TITULO'] + #9 + nodoData.ChildNodes[I].Attributes['BANXICO_UNIT_TYPE'] + #9 + nodoData.ChildNodes[I].ChildNodes[0].Attributes['TIME_PERIOD'] + #9 + nodoData.ChildNodes[I].ChildNodes[0].Attributes['OBS_VALUE'] ); end; end; end; end;
Por último vamos a ewscribir el código de la función principal de nuestra clase llamado ConsultaTipoDeCambio y que requiere de la URL del Servicio Web la cual enviaremos desde la forma principal.
function TRequestResponse.ConsultaTipoDeCambio(endPoint: AnsiString): TStrings;
function TRequestResponse.ConsultaTipoDeCambio(endPoint: AnsiString): TStrings; var nodoBody: IXMLNode; nodoResult: IXMLNode; xml: IXMLDocument; response: AnsiString; begin xml := creaRequest; xml.XML.SaveToFile(ExtractFilePath(Application.ExeName) + 'request_' + formatDateTime('yyyymmdd_hhnnss',now) + '.xml' ); if sendRequest(endPoint, AnsiString(xml.XML.Text), response) then begin try xmlArch := NewXMLDocument; xmlArch.XML.Text := response; xmlArch.Active := true; nodoBody := xmlArch.ChildNodes[1].ChildNodes[1]; if nodoBody <> nil then begin nodoResult := RecursiveFindNode(NodoBody, 'result'); if nodoBody <> nil then begin xml := TXMLDocument.Create(nil); xml.XML.Text := nodoResult.ChildNodes[0].NodeValue; xml.Active := true; xml.XML.SaveToFile(ExtractFilePath(Application.ExeName) + 'response.xml', TEncoding.ANSI); Result := BuscaTC(xml); end else begin Result.Text := 'ERROR: No se pudo acceder al nodo "Result"'; end; end else begin Result.Text := 'ERROR: No se pudo acceder al nodo "Body"'; end; except on e: exception do Result.Text := 'ERROR: ' + e.Message; end; end; end;
Ya tenemos nuestra unidad uXMLClass.pas la cual contiene la clase TRequestResponse completa y lista para ser usada por el programa principal.
En la forma principal vamos a agregar dos componentes TButton y un componente TMemo. Perdón pero suelo ser minimalista 😀
Acomodamos los componentes a nuestro gusto y escribiremos el siguiente código en el evento OnClick del componente TButton.
procedure TForm1.Button1Click(Sender: TObject); var ReqResp: TRequestResponse; retorno: TStrings; begin ReqResp := TRequestResponse.Create; retorno := TStringList.Create; retorno := ReqResp.ConsultaTipoDeCambio(AnsiString('http://www.banxico.org.mx/DgieWSWeb/DgieWS')); Memo1.Lines := retorno; end;
Finalmente ejecutamos nuestra aplicación y deberíamos obtener la siguiente respuesta.
Con ésto concluimos éste pequeño tutorial donde pretendemos mostrar que se puede trabajar con ésta edición de Delphi y crear aplicaciones listas para dar solución a requerimientos reales. Por supuesto que hay de requerimientos a requerimientos pero la idea es comenzar nuestro «propio negocio» y poder obtener ingresos suficientes para adquirir una de las versiones de pago y dar el siguiente paso para que nuestro negocio se reafirme y podamos dar soluciones completas y mas profesionales.
Antes de despedirme les quiero decir que vamos a estar desarrollando algunos temas con Delphi Starter con lo que pretendemos aportar nuestro granito de arena, si tienen algún tema que pueda ser creado con ésta versión con gusto lo intentaremos desarrollar.
Les agradeceré que dejen sus comentarios, críticas y/o palabras de aliento que me darán el oxígeno necesario para continuar con ésta serie. 🙂
Hasta la próxima
Todo el código escrito aquí es de libre descarga y utilización solo te pido, si te parece correcto, que menciones su origen y claro cualquier mejora que le hagas publicala que se agradecerá enormemente.
Muchas gracias.
Excelente tutorial amigo Eliseo, ahora hay que hacer un ejemplo con servicios REST.
Me parece interesante amigo, vamos a ver si tengo la capacidad de hacerlo.
Muy buena entrada Eliseo, no conocia la existencia de esa envoltura de Domingo para utilizar cURL. Confieso que esta herramienta la conoci hace muy poco, y a pesar de parecer una «pequeña utilidad» tiene una potencia enorme. Me alegra que se pueda utilizar facilmente desde Delphi
Enhorabuena!!
Gracias amigo Agustín. La verdad es que cURL me ha solucionado más de una tarea. No se que sería de mi sin la ayuda de varios compañeros del foro, no menciono nombres porque no quiero ser injusto con ninguno de ustedes.
Saludos
Muchas gracias
Gracias a ti por leerme.
Saludos
Estupendo artículo Eliseo, hace rato cuando anunciaste la publicación de este tema estaba a la espera y vaya, muy bueno, gracias por compartirlo. Bastante práctico.
Muchas gracia amigo yonni. La verdad es que viendo el nivel de las publicaciones de Salva de Germán de Luis Felipe de Edgar tuyos y demás amigos es un poco intimidante publicar. 🙂
Saludos
Hombre, cada uno tiene lo suyo. Es lo bonito de los aportes, además tus artículos no se quedan atrás de ninguna manera.
Claro claro, pero no deja de ser intimidante 😀
Sin embargo, a la par me resulta un buen reto subir el nivel de las publicaciones cada día 🙂
Saludos
Gracias Eliseo. Un articulo estupendo
Al contrario amigo Domingo. Gracias a ti que nos mostraste ésta útil herramienta y la has puesto disponible para delphi.
Saludos
Eliseo , como siempre soprendiendo con tus valiosos aportes.
He dejado un poco el Desarrollo, pero viendo todas estas grandes herramientas, es una motivación para que nuevamente reinicie mi actividad como Desarrollador.
Muchas gracias Oscar, me has hecho el día.
Si a una sola persona le sirve ésta publicación ya valió la pena hacerla.
Saludos
Enhorabuena Eliseo, que gran calidad tienen tus post. Muchas gracias por compartirlos
Muchas gracias Emilio, el reto es hacer mas y mejores entradas. 🙂
Saludos
Eliseo buenas tardes.
Interesante codigo, al respecto, llegue a tu pagina siguiendo un hilo o comentario tuyo sobre Curl en delphi.
He tratado desde hace tiempo de hacer funcionar la API de Pushbullet con Delphi 7 sin tener exito.
La idea es enviar un simple mensaje en delphi medianteel uso de curl y la push notification APi de Pushbullet.
Vi un ejemplo hecho en Lazarus pero no logre adaptarlo a delphi, use ejemplos de libcurl, tambien intente con ejemplos de idHttp en indy 10 pero tuve error con la libreria de OpenSSL, etc. sin exito alguno.
Te agradeceria si pudieras orientarme o facilitarme un codigo ejemplo.
Saludos.
Que tal Luis
Si tienes el ejemplo en cURL envíalo a mi correo y vemos como adaptarlo para que funcione con Delphi.
Gracias por leerme.
Saludos
Hola Eliseo, te envié un email al la dirección que se se muestra en la parte superior de esta pagina, es respecto al ejemplo curl, ojala puedas checarlo.
Saludos.
Luis
Hola Luis, no he podido enviarte correos, me dice que hay un error en tu dirección de email. Ojalá tuvieras otro de hotmail, gmail, outlook.
Saludos