h1

Usando Telnet para llamar Web Services

10/23/2009

Wizdoc [Icon By Buuf]  Tips & Tricks.
Hace tiempo publiqué un post donde hablo un poco acerca de las herramientas que todo desarrollador debe conocer para simplificar el trabajo y mejorar su productividad. Entre ellas incluyo a SOAP UI, que nos facilita integrar web services en una aplicación. Como mencioné anteriormente, SOAP UI permite generar peticiones a un web service, ya sea mediante el WSDL de manera local o a través de la URL donde se encuentra remotamente dicho documento, en caso de ser accesible desde nuestra computadora.

Sin embargo, muchas veces al integrar web services resulta que por cuestiones de seguridad, existen dos servidores de prueba – uno de nuestro lado y otro del lado del servicio que estamos integrando – con restricciones de IP. Esto complica nuestro trabajo porque no podremos probar la funcionalidad de manera directa desde nuestra máquina pues al intentar llamar al web service, nuestro cliente marcará un Connection Refused o Connection Timeout.

Existen versiones de SOAP UI para Solaris y Linux, pero puede llegar a dificultarse instalar la herramienta ya que en más de una ocasión he visto que a los desarrolladores se les proporcionan usuarios sin permisos mas que de ejecución, y sólo en ciertos directorios aprobados por el área de seguridad.

¿Qué hacer al respecto? Valiéndonos de SOAP UI y el pedestre pero poderoso cliente Telnet podremos avanzar sin muchas complicaciones:

Para generar nuestras peticiones debemos contar con el WSDL que describe la funcionalidad del servicio. La manera más fácil de obtenerlo es apuntar al URL donde está publicado y recuperarlo mediante la ejecución de telnet <ip o nombre del servidor> <puerto> de la siguiente manera:

# telnet 192.168.100.105 8080
Trying 192.168.100.105…
Connected to 192.168.100.105.
Escape character is ‘^]’.
_

Una vez que hemos abierto una nueva conexión, ejecutamos una petición HTTP GET para recuperar el WSDL que estamos buscando mediante GET <URI del WSDL> más dos <Enters>, como se muestra a continuación:

# telnet 192.168.100.105 8080
Trying 192.168.100.105…
Connected to 192.168.100.105.
Escape character is ‘^]’.
GET /axis/services/RemoteWebService?wsdl
<Enter>
<Enter>

Y esto generará como respuesta el WSDL completo. Para aquellos que cuenten con los permisos correspondientes, es posible utilizar el comando wget (localizado en /usr/bin/wget o en /usr/sfw/bin/wget) con la dirección completa del WSDL para recuperarlo:

# wget http://192.168.100.105:8080/axis/services/RemoteWebService?wsdl
–19:35:26– http://192.168.100.105:8080/axis/services/RemoteWebService?wsdl
Connecting to 192.168.100.105:8080… connected.
HTTP request sent, awaiting response… 200 OK
Length: unspecified [text/xml]
Saving to: `RemoteWebService?wsdl’

[ <=> ] 16,010 39.5K/s in 0.4s

19:35:27 (39.5 KB/s) – `RemoteWebService?wsdl’ saved [16010]

# _

Ahora, mediante SOAP UI o alguna otra herramienta como XML Spy podemos generar los XML de las peticiones al web service y editar dichos documentos para agregarles los valores necesarios. Un ejemplo con SOAP UI es el siguiente:

SOAP UI XML Request

Ejemplo de una petición XML-SOAP con un WSDL importado mediante SOAP UI. (Dar click en imagen para ver versión original).

Al haber creado un nuevo proyecto en SOAP UI mediante el WSDL, tenemos un template de petición al web service que vamos a llamar. Podemos editarlo y agregar los valores faltantes, incluyendo la etiqueta <?xml version="1.0" encoding="UTF-8"?> que es indispensable incluir al inicio de nuestro mensaje:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:xsd="http://www.w3.org/2001/XMLSchema&quot; xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/&quot; xmlns:ser="http://in.dustry.com/web/services"&gt;
  <soapenv:Header/>
  <soapenv:Body>
    <ser:getUserProfile soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"&gt;
      <seq_id xsi:type="xsd:string">11223344</seq_id>
      <client_id xsi:type="xsd:string">1122</client_id>
    </ser:getUserProfile>
  </soapenv:Body>
</soapenv:Envelope>

Debemos remover los espacios e indentaciones para simplificar nuestro copy-paste final en el cliente de Telnet (en un momento veremos por qué):

<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:xsd="http://www.w3.org/2001/XMLSchema&quot; xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/&quot; xmlns:ser="http://in.dustry.com/web/services"><soapenv:Header/><soapenv:Body><ser:getUserProfile soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><seq_id xsi:type="xsd:string">11223344</seq_id><client_id xsi:type="xsd:string">1122</client_id></ser:getUserProfile></soapenv:Body></soapenv:Envelope>

Y hacemos el paso de la muerte, ejecutando nuevamente nuestro cliente Telnet, pero en ves de realizar un HTTP GET, generaremos una petición HTTP POST con ciertos parámetros que son tomados por el cliente Telnet como el encabezado de nuestra petición:

# telnet 192.168.100.105 8080
Trying 192.168.100.105…
Connected to 192.168.100.105.
Escape character is ‘^]’.
POST /axis/services/RemoteWebService HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: ""
User-Agent: Jakarta Commons-HttpClient/3.1
Host: 123.456.789.101
Content-Length: 527
<Enter>
<Enter>

A continuación explico qué significa cada línea del encabezado:

• La primera línea indica que realizaremos una petición HTTP POST a la dirección del web service remoto en /axis/services/RemoteWebService, usando el protocolo HTTP versión 1.1.
• La línea dos indica que podremos enviar mensajes comprimidos. Esta línea es opcional.
• La línea tres indica la codificación de nuestro mensaje.
• La línea cuatro indica que este es un mensaje SOAP y que realizaremos una acción con esta llamada. Puede dejarse vacía, pero es indispensable incluirla pues de lo contrario el servidor nos responderá con el mensaje de error "no SOAPAction header!".
• Las líneas cinco y seis indican el user-agent o cliente que está realizando la petición, así como la IP desde la que se está llamando. Conviene incluirlas en caso de que el web service remoto realice algún tipo de filtrado por IP o tipo de cliente.
• Finalmente, la longitud del mensaje que debe ser exactamente la misma del mensaje SOAP enviado.

Luego copiamos el XML de nuestra llamada al web service y tecleamos dos <enters> nuevamente;

# telnet 192.168.100.105 8080
Trying 192.168.100.105…
Connected to 192.168.100.105.
Escape character is ‘^]’.
POST /axis/services/RemoteWebService HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: ""
User-Agent: Jakarta Commons-HttpClient/3.1
Host: 123.456.789.101
Content-Length: 527
 
 
<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:xsd="http://www.w3.org/2001/XMLSchema&quot; xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/&quot; xmlns:ser="http://in.dustry.com/web/services"><soapenv:Header/><soapenv:Body><ser:getUserProfile soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><seq_id xsi:type="xsd:string">11223344</seq_id><client_id xsi:type="xsd:string">1122</client_id></ser:getUserProfile></soapenv:Body></soapenv:Envelope>
<Enter>
<Enter>

Con esta llamada al web service, se deberá generar una respuesta que nos será devuelta en forma de otro mensaje XML-SOAP:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Powered-By: Servlet 2.4; JBoss-4.2.1.GA (build: SVNTag=JBoss_4_2_1_GA date=200707131605)/Tomcat-5.5
Content-Type: text/xml;charset=utf-8
Transfer-Encoding: chunked
Date: Wed, 21 Oct 2009 16:34:56 GMT

4b8
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope>
  <soapenv:Body>
    <ns1:getUserProfileResponse>
      <userprofile href="#id0"/>
    </ns1:getUserProfileResponse>
    <multiRef id="id0" soapenc:root="0">
      <SEQ_ID xsi:type="soapenc:string">556677889900</SEQ_ID>
      <regionId href="#id1"/><userType href="#id2"/>
    </multiRef>
    <multiRef id="id1" soapenc:root="0">9</multiRef>
    <multiRef id="id2" soapenc:root="0">1</multiRef>
  </soapenv:Body>
</soapenv:Envelope>
0

¡Voila! Algo talachuda pero exitosa llamada a un web service desde un cliente Telnet.

Troubleshooting

Hay un par de detalles que debemos observar cuando realicemos la llamada:

La primera, algo trivial, es el detalle de la codificación del mensaje. Siempre es importante saber el tipo de codificación que están usando tanto el cliente como el web service, pues en caso de ser diferentes podemos toparnos con errores debido a que cualquiera de los dos puntos, ya sea el de publicación o el de consumo, no reconozcan algunos de los caracteres del XML enviado o la respuesta retornada. Por ello, si el servidor codifica UTF-8, el cliente deberá ajustarse a UTF-8.

Segunda y más importante es el tamaño del mensaje en la última línea del encabezado HTTP así como los dos <enters> que tecleamos entre encabezado y mensaje y al final del mensaje. Por ello es mejor eliminar espacios e indentación del mensaje: disminuyen el riesgo que incluyamos "caracteres demás" ya que pueden aparecernos cualquiera de los siguientes errores:

• java.net.SocketTimeoutException: Read timed out. Debido a que el web service está esperando el resto del mensaje "faltante". Esto es porque la longitud es incorrecta o nos faltaron los dos <enters> al final del propio mensaje.

• org.xml.sax.SAXParseException: Reference is not allowed in prolog. Debido a que encuentra un ampersand (&) en el encabezado de la petición. Esto es porque nos faltó un <enter> entre el encabezado y el mensaje o la longitud que se incluyó es demasiada.

• org.xml.sax.SAXParseException: The processing instruction target matching &quot;[xX][mM][lL]&quot; is not allowed. Esto se debe a que el parser del lado del servidor está encontrando un caracter menor-que (<), mayor-que (>), comillas (") ó ampersand (&) en el encabezado de la petición. De nueva cuenta, puede ser porque nos faltó un <enter> entre el encabezado y el mensaje XML o nos quedamos cortos con la longitud que se incluyó en el encabezado.

Conclusiones

Y así podemos hacer de manera rápida y puntual una pequeña prueba de concepto para verificar que un web service está arriba, parsee correctamente un mensaje y nos regrese una respuesta válida. Claro que si vamos a desarrollar algo más que un par de peticiones, siempre será mejor instalar SOAP UI o pedir a los administradores que nos echen la mano con el firewall para poder ver el web service publicado desde las máquinas de desarrollo, con el fin de que mejoremos nuestra productividad, acabemos más rápido y todos seamos más felices.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: