martes, noviembre 22, 2011

Consumir cualquier Web Service con Visual FoxPRO


* ---
* Actualización 2018-16-20: Se actualiza el ejemplo, código e imágenes.
* ---

Que tal Colegas.

Este artículo es para mostrarles como podemos invocar cualquier Web Service desde Visual FoxPRO.

Primero quisiera comentar, que NO soy un experto en ésto de los WEB service's, pero he tomado algo de experiencia en el uso de los mismos. En éste artículo trataré de explicarlo con palabras simples.

Es importante leer al respecto, siempre es sencillo encontrar información en Wikipedia.

Vamos comenzando por la lógica de "ejecución" de un web service:

1. Creamos la petición de ejecución de una función del Web Service, ésto se llama XML REQUEST.
2. Enviamos el REQUEST al web service, la cual es una URL que termina con wsdl.
3. Obtenemos un XML RESPONSE, que es el resultado de haber invocado la función específica del WS.

Vamos por partes:

Para saber como es el REQUEST, utilizo una aplicación Open Source llamada soapUI, la cual pueden descargar desde: http://www.soapui.org/

Lo descargamos, al abrir el soapUI vemos ésto:




La URL del web service es: http://www.gencfd.com/wsEjemplo/index.php?wsdl




Con ésto, soapUI interperta las funciones que existen en ése WebService, y nos crea un ejemplo de REQUEST por cada una de ellas, en éste caso observaremos como el REQUEST de la función SUMA:



Hasta aquí, tenemos cubierto el paso 1 de la ejecución de un Web Service, el dos es simple.. damos clic en el botón de "PLAY" y se cumple el segundo paso, al mismo tiempo que obtenermos el XML RESPONSE:



Bien, como hacemos ésto con FoxPRO:

Al conocer la estructura del XML Request, pues lo creamos con FoxPRO, usen el método que a ustedes les parezca mejor, en lo personal me agrada la utileria Free de Chilkat, pero para el ejemplo crearé el Request con TEXT.. ENDTEXT

He creado una clase para consumir WS, en ella muestro el ejemplo anterior en código, está comentada por lo que creo no es necesario hablar más al respecto.

Va el código:

oWS = CREATEOBJECT( "VFP_WebService","http://www.gencfd.com/wsEjemplo/index.php?wsdl") 
lcRespuesta = oWS.Suma(10,8)
IF oWS.iStatus != 0
    MESSAGEBOX(oWS.sError,16,"Error al ejecutar WS")
ELSE
    MESSAGEBOX(lcRespuesta,64,"EjecutandoWS desde VFP - PortalFOX")
ENDIF  


* -- Ahora usando el ActiveX de Chilkat --
* -- **http://www.chilkatsoft.com/ChilkatXml.asp** --
lcRespuesta = oWS.Suma_Chilkat(10,8)
IF oWS.iStatus != 0
    MESSAGEBOX(oWS.sError,16,"Error al ejecutar WS")
ELSE
    MESSAGEBOX(lcRespuesta,64,"EjecutandoWS desde VFP usando Chilkat - PortalFOX")
ENDIF  


DEFINE CLASS VFP_WebService AS CUSTOM

    * --- Definimos las propiedades ---
    sError = ""
    iStatus = 0 
    sURL_WS = ""

    * --- Definimos la función del WebService ---
    FUNCTION Suma(tnI, tnJ)
        * --- Paso 1. Creo el XML Request ---
        lcXMLRequest = this.CreaRequest(tnI, tnJ)
        lsXMLResponse = ADDBS(SYS(2023)) + SYS(2015) + [.xml]
        * --- Paso 2. Ejecuto el WS | Paso 3. Obtengo el Response ---
        this.iStatus =  this.EjecutaWS( this.sURL_WS, lcXMLRequest , lsXMLResponse )
        IF this.iStatus != 0  && Ocurrió un error el cual está especificado en sError.
            RETURN ""
        ENDIF 
        lcXMLResponse = FILETOSTR(lsXMLResponse)
        * --- Parseamos el XML Response ---
        * --- Para el ejemplo está así, manejando texto, ustedes deben manejar XML (falta de tiempo, perdón) ---
        lcRespuestaWS = STREXTRACT(lcXMLResponse ,[<res xsi:type="xsd:integer">],[</res>])
        this.borraArchivo(lsXMLResponse)
        RETURN lcRespuestaWS 
    ENDFUNC 

    *---------------------------------------------------
    FUNCTION EjecutaWS(tcURL_WSDL, tcFileRequest , tsFileResponse )
    *---------------------------------------------------
        TRY 
            oHTTP = CREATEOBJECT('Msxml2.ServerXMLHTTP.6.0')
            oHTTP.OPEN("POST", tcURL_WSDL, .F.)
            oHTTP.setRequestHeader("User-Agent", "EjecutandoWS desde VFP - PortalFOX")
            oHTTP.setRequestHeader("Content-Type", "text/xml;charset=utf-8")
            oHTTP.SEND(tcFileRequest)
        CATCH TO loErr
            this.sError = "Error: " + TRANSFORM(loErr.ErrorNo) +  " Mensaje: " + loErr.Message
            this.iStatus = -1      
        ENDTRY 
        IF this.iStatus != 0
            RETURN -1
        ENDIF 
        * --- Si el status es diferente a 200, ocurrió algún error de conectividad con el WS ---
        IF oHTTP.STATUS = 200
            lcRespuestaWS = oHTTP.responseText
            * --- Se genera el XML del response | Este es el paso 3!! ---
            STRTOFILE(STRCONV(lcRespuestaWS ,9),tsFileResponse)
            this.iStatus = 0
            this.sError = ""
            RETURN 0
        ELSE
            this.sError = "Error: No se logró la conexión con el Web Service."
            this.iStatus = -1
            RETURN -1
        ENDIF
    ENDFUNC 
    *---------------------------------------------------

    *---------------------------------------------------
    FUNCTION CreaRequest(tnI, tnJ)
    *---------------------------------------------------
        TEXT TO lcXMLRequest TEXTMERGE PRETEXT 7 NOSHOW 
            <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:urn="urn:suma">
                <soapenv:Header/>
                <soapenv:Body>
                    <urn:suma soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
                        <i xsi:type="xsd:integer"><<tnI>></i>
                        <j xsi:type="xsd:integer"><<tnJ>></j>
                    </urn:suma>
                </soapenv:Body>
            </soapenv:Envelope>
        ENDTEXT 
        RETURN lcXMLRequest 
    ENDFUNC  
    *---------------------------------------------------
    
    *---------------------------------------------------
    FUNCTION CreaRequest_Chilkat(tnI, tnJ)
    *---------------------------------------------------
loXml = CreateObject('Chilkat.Xml')
loXml.Encoding = "UTF-8"
loXml.Tag = "soapenv:Envelope"
        loXml.AddAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance")
        loXml.AddAttribute("xmlns:xsd","http://www.w3.org/2001/XMLSchema")
        loXml.AddAttribute("xmlns:soapenv","http://schemas.xmlsoap.org/soap/envelope/")
        loXml.AddAttribute("xmlns:urn","urn:suma")
        loXml = loXml.NewChild("soapenv:Header","")
loXml.GetRoot2()
loXml = loXml.NewChild("soapenv:Body","")
        loXml = loXml.NewChild("urn:suma","")
        loXml.AddAttribute("soapenv:encodingStyle","http://schemas.xmlsoap.org/soap/encoding/")
        loXml = loXml.NewChild("i",tnI)
        loXml.AddAttribute("xsi:type","xsd:integer")
        loXml.GetParent2()
        loXml = loXml.NewChild("j",tnJ)
        loXml.AddAttribute("xsi:type","xsd:integer")
        loXml.GetRoot2()
        RETURN loXML.getxml()
    ENDFUNC  
*---------------------------------------------------
FUNCTION GetChilkatPath
*---------------------------------------------------
* Esta función obtiene el resultado de evaluar
* un Chilkat Xml Path. Para mas referencia
* revisar: http://www.chilkatsoft.com/refdoc/xChilkatXmlRef.html
LPARAMETERS etPath,spXml
loXml = CreateObject('Chilkat.Xml')
lnSuccess = loXml.LoadXmlFile(spXML)
IF (lnSuccess = 0) THEN
    this.sError = "Error de parse en el archivo xml"+ loXml.LastErrorText
    this.iStatus = -999
RETURN ""     
ENDIF
loXml.GetRoot2()
lcTemp = loXml.ChilkatPath(etPath)
loXML = .null.
RELEASE loXML
RETURN IIF(ISNULL(lcTemp),"",lcTemp)
ENDFUNC 
*---------------------------------------------------
    *---------------------------------------------------
    FUNCTION Suma_Chilkat(tnI, tnJ)
        * --- Paso 1. Creo el XML Request ---
        lcXMLRequest = this.CreaRequest_Chilkat(tnI, tnJ)
        lsXMLResponse = ADDBS(SYS(2023)) + SYS(2015) + [.xml]
        * --- Paso 2. Ejecuto el WS | Paso 3. Obtengo el Response ---
        this.iStatus =  this.EjecutaWS( this.sURL_WS, lcXMLRequest , lsXMLResponse )
        IF this.iStatus != 0  && Ocurrió un error el cual está especificado en sError.
            RETURN ""
        ENDIF 
        lcRespuestaWS = this.GetChilkatPath("/C/SOAP-ENV:Envelope|SOAP-ENV:Body|ns1:sumaResponse|res|*",lsXMLResponse )
        this.borraArchivo(lsXMLResponse )
        RETURN lcRespuestaWS 
    ENDFUNC 

    *---------------------------------------------------
    FUNCTION BorraArchivo(tsFile)
    *---------------------------------------------------
        IF FILE(tsFile)
            DELETE FILE (tsFile)
        ENDIF 
    ENDFUNC 
    *---------------------------------------------------

    *---------------------------------------------------
    * Evento constructor
    PROCEDURE Init
    *---------------------------------------------------
        LPARAMETERS tcURLWS
        this.sURL_WS = tcURLWS
        this.iStatus = 0
        this.sError = ""
    ENDPROC
    *---------------------------------------------------
ENDDEFINE  

Pueden descargar el código desde aquí.

Suerte con sus Web Services!!

Baltazar Moreno
http://disxii.com
VFP9SP2 - Win7
Guadalajara, Jalisco, México

19 comentarios:

wpalomo dijo...

Saludos

Excelente, me sirvio mucho. En base a esta explicación desarrolle algo parecido para consultar nombres en base al numero de RUC, aquí en Ecuador.

Anónimo dijo...

El ejemplo funciona bien, pero cuendo usas https simplemente no conecta.
Si conoces algo del porque? te lo agradecieria enormemente.
Saludos

Baltazar Moreno dijo...

Necesita tener certificados válidos de ssl, si la conexión no es segura marcará esos errores.

Saludos.

Unknown dijo...

Te felicito y te agradezco mucho, me sirvio.

Pero talvez me podrias ayudar.

En algunas ocasiones me devuelve: Código de excepción OLE IDispatch 0 de msxml6.dll: The operation timed out

Yo esperaba que los errores los pasara a catch

Alguna sugerencia para saber que diga cual es el error?

He buscado mucho para resolverlo.

Estoy usando el modo sincronico.

Buen dia.

Baltazar Moreno dijo...

Que tal Carlos.

Puedes controlar el tiempo de timeout agregando el siguiente código:

oHTTP = CREATEOBJECT('Msxml2.ServerXMLHTTP.6.0')
lResolve = 5 * 1000
lConnect = 5 * 1000
lSend = 40 * 1000
lReceive = 40 * 1000
oHTTP.setTimeouts(lResolve, lConnect, lSend, lReceive)

oHTTP.OPEN("POST", pURL_WSDL, .F.)


Ojo, debes establecer los tiempos antes de realizar el Open al objeto.

Saludos.

Roxana dijo...

Hola! Aunque tengo el certificado instalado en la maquina me da error de certificado incorrecto o invalido.
Probé con este código que adjunto ya me pide el certificado antes de ejecutar el servicio con lo cual ya no me da error de certificado pero no devuelve datos. me devuelve toda la estructura del servicio, como sino reconociera el método que quiero ejecutar que se llama ECO.

oHTTP=Create('MSXML2.XMLHTTP')


oHTTP.Open("POST", "https://www.certificados.aduana.xxx/ws2/autos?wsdl", .F.)


oHTTP.setRequestHeader("Content-Type", "text/xml;charset=UTF-8")
oHTTP.setRequestHeader("SOAPAction",'https://www.certificados.xxx/ws2/autos#eco")


=MESSAGEBOX( pFileRequest)
*oHTTP.SEND(pFileRequest)
oHTTP.SEND()

Por favor agradezco cualquier ayuda que me puedan dar.
Gracias!

Roxana dijo...

Hola! Probe los ejemplos citados y como mi servicio requiere un certificado SSL me daba error de certificado invalido. A pesar de que el certificado esta instalado en mi PC y con soapUI me responde bien el servicio.
Probe este ejemplo que adjunto y al probarlo me pide el certificado antes de ejcutarse y luego ya no da error de certificado, pero no me devuelbe datos. Devuelve la estructura completa del sergicio como si pudieras la url del servicio en el navegador.

El metodo que quiero ejecutar se llama “eco” y me parece que no esta tomado el metodo a ejecutar y por eso contesta con la estructura del servicio.
Adjunto el ejemplo a ver si me pueden ayudar:

TEXT TO sXMLRequest TEXTMERGE NOSHOW




30-66298283-7
123



ENDTEXT


pXMLResponse = ADDBS(SYS(2023)) + SYS(2015) + [.xml]

pFileRequest = sXMLRequest

TRY


oHTTP=Create('MSXML2.XMLHTTP')
oHTTP.Open("POST", "https://www.certificados.aduana.xxx/ws2/autos?wsdl", .F.)
oHTTP.setRequestHeader("Content-Type", "text/xml;charset=UTF-8")
oHTTP.setRequestHeader("SOAPAction", "https://www.certificados.aduana.dnrpa.gov.ar/ws2/autos#eco")


=MESSAGEBOX( pFileRequest)
*oHTTP.SEND(pFileRequest)
oHTTP.SEND()

Baltazar Moreno dijo...

Supongo que la url del web service es:

https://www.certificados.aduana.dnrpa.gov.ar/ws2/autos?wsdl

Ésta marca error de certificado (NET::ERR_CERT_AUTHORITY_INVALID), como lo comentaba anteriormente, el certificado SSL debe ser válido, no basta con instalarlo manualmente en la computadora.

Tendria que reportarlo al dueño del wsdl para que corrija ésta situación.

Saludos.

Roxana dijo...

Gracias por responder.
El certificado es válido. De hecho si lo ejecuto con el soapPUI funciona perfecto.
Estoy hablando con los creadores del servicio y en otros lenguajes la invocación les funciona bien. Por eso pienso que es un tema de mi código VFP.
Si tenes algún ejemplo de la invocación de un servicio con SSL te lo agradecería mucho. Quiza viendo el ejemplo veo mi error.

Baltazar Moreno dijo...

No es válido, a mi me regresa ese error en mi navegador (NET::ERR_CERT_AUTHORITY_INVALID) , no hay problema en tu código, el problema es que al usar el objeto XMLHTTP, lo cual está documentado:

https://support.microsoft.com/en-us/kb/290761

"The ServerXMLHTTP and XMLHTTP components have limited HTTPS support"

Aunque, existen algunas opciones, que en lo particular no he usado, puedes probarlas:

https://msdn.microsoft.com/en-us/library/ms763811(v=vs.85).aspx

Podria ser: SXH_OPTION_IGNORE_SERVER_SSL_CERT_ERROR_FLAGS

Saludos.


pplupe dijo...

Gracias por tu gran aporte.
yo apenas comienzo con esto, tengo los esquemas(xsd) y los archivos wsdl pero no se como usar estos, podrias apoyar en esto

Anónimo dijo...

Después de tanto y hacer varias pruebas encontré una forma de conectar con https desde VFP vía WebService, lo único que cambie fue la forma de crear la conexión:


oHTTP = CREATEOBJECT("Microsoft.XMLHTTP")
oHTTP.OPEN("POST", pURL_WSDL, .F.)
oHTTP.SEND(pFileRequest)

pURL_WSDL es la URL del webservice con https
pFileRequest es la respuesta recibida por la consulta al webservice

Saludos
Roberto Yuniz

José Manuel dijo...

Larga VIDA al zorro, una consulta copie tu ejemplo y lo he corrido pero me sale la pantalla con los datos vacios, por si acaso he cambiado las locaciones (lima, peru), pero nada no me muestra info. hay algo mal o es que despues de tantos años ya no esta activa la direccion que estas consultando, gracias por tu respuesta.

José Manuel dijo...

Larga VIDA al zorro, una consulta copie tu ejemplo y lo he corrido pero me sale la pantalla con los datos vacios, por si acaso he cambiado las locaciones (lima, peru), pero nada no me muestra info. hay algo mal o es que despues de tantos años ya no esta activa la direccion que estas consultando, gracias por tu respuesta.

Baltazar Moreno dijo...

La verdad, no sé si aún funcione el Servicio, puedes probar aquí:
http://www.webservicex.net/globalweather.asmx?op=GetWeather

Saludos.

Anónimo dijo...

Las imagenes ya no existen, puedes actualizar este post?

Baltazar Moreno dijo...

El WebService http://www.webservicex.net/globalweather.asmx?WSDL

Ya no existe:

{"code":"PAGE_NOT_FOUND","message":"Page not found"}

Por lo que el ejemplo ya no es válido; prepararé una actualización de ésta entrada de blog.

Saludos.

Baltazar Moreno dijo...

Gracias a que Xuan Madrid me pasó un PDF con las imágenes las he actualizado.

¡Saludos!

Baltazar Moreno dijo...

He actualizado el código, con ejemplo nuevo y un webservice propio (solo de ejemplo)

Saludos a todos.