SOAPopera: Publicando un web service I
Una de los “ultimos” gritos de la moda en cuanto al desarrollo, son los web services. En lo particular, me parecen una tecnologia excelente, por que permiten “consumir” servicios ya construidos sin preocuparte de la plataforma en la que esten construidos. Para muchas aplicaciones, una arquitectura basada en servicios Web puede ser el santo grial… si lo sabes utilizar correctamente.
Hace poco en mi trabajo (empresa X), surgio la necesidad de que la empresa Y trabajara con algunos de nuestros datos para una validacion en una aplicacion propia. Por politicas de X, esta prohibido liberar a externos cualquier base de datos. De manera que necesitabamos que la aplicacion hecha por Y consultara los datos de X sin tener acceso real a los datos… parecia un caso perfecto para los WebServices.
Como buen .Netero, se que es muy facil construir un WebService en VS. De hecho, tengo algunos trabajando en la red interna, sin problemas (notes que dije red interna). Esa es mi experiencia con ellos, asi que sabia que construir un simple WebService que consultara una base de datos y regresara una cadena, no seria problema alguno.
Y no lo fue. Las pruebas estuvieron bien, el servicio parecia que hacia lo que tenia que hacer. Publicamos, y todo bien, de nuevo probe y todo funcionaba bien. Incluso hice un pequeño ejecutable que lo consumia, y de nuevo, todo sin problemas.
Pero a la hora de pedirles a los usuarios que lo probaran, simplemente no se podian conectar. Descartamos tullidez de su parte por que en esta pagina (muy buena para las pruebas, por cierto) tampoco estaba funcionando, asi que el problema caia en mi cancha.
Me pase largas y tediosas horas tratando de decifrar la solucion, pero, al menos ese dia, no lo logre. Despues de mas de 10 horas de cambiar el codigo sin exito comienzas a alucinar, asi que decidi que seria mejor continuar al dia siguiente con la cabeza mas fresca.
Al dia siguiente, inmediatamente note algo extraño (descansar ayuda). La pagina de pruebas de SOAP leia correctamente el WSDL, pero luego, buscaba la funcion en una direccion mas o menos asi:
http://192.168.1.100/servicio/funcion.asmx
En lugar de buscarla en:
http://empresaX.com/servicio/funcion.asmx
No habia notado nada raro hasta que cai en la cuenta de que la direccion 192.168.x.x es una IP local, no publica. Al leer el WSDL, en efecto, encontre la causa del problema
<wsdl:port name=“ServicioSoap“ binding=“tns:ServicioSoap“>
<soap:address location=“https://192.168.1.100/servicio/funcion.asmx“ />
</wsdl:port>
El problema radicaba en que el WSDL (que es automaticamente generado), estaba tomando la IP de la maquina en la que residia (lo cual estaria bien si estuviera directo a internet, pero al estar detras de un Router, la cosa se ponia diferente). De manera que en realidad el WSDL hacia que la aplicacion preguntara por una servicio en una direccion que jamas encontraria.
Dar con el problema fue casi tan dificil como dar con la solucion. En mis busquedas practicamente todos compartian el problema, pero ningun la solucion. Encontre muchas soluciones que no funcionaban o no me convencian. Hasta que llege al benito blog de Kirk Allen Evans, donde habia una solucion para un problema similar.
El chiste aqui es crear una clase derivada de SoapExtensionReflector que nos permitira hacer cambios en el WSDL en tiempo de ejecucion. Debe verse algo asi:
namespace EmpresaX.ServicioWeb { public class LocalAddressReflector : SoapExtensionReflector { public override void ReflectMethod() {} public override void ReflectDescription() { ServiceDescription description = ReflectionContext.ServiceDescription; foreach (Service service in description.Services) { foreach (Port port in service.Ports) { foreach (ServiceDescriptionFormatExtension extension in port.Extensions) { SoapAddressBinding binding = extension as SoapAddressBinding; if (null != binding) { binding.Location = binding.Location.Replace(Properties.Settings.Default.LocalAddress, Properties.Settings.Default.PublicAddress); } } } } } } }
Properties.Settings.Default.LocalAddress y PublicAddress no son mas que valores en el archivo de configuracion que corresponden a las direcciones local y publica del servicio. Lo deje en el archivo de configuracion para no tener que recompilar la aplicacion si estas direcciones cambiaban.
Despues, en el archivo de configuracion, En la seccion de Configuration/System.Web hay que agregar lo siguiente (para asegurarnos que nuestra clase sea cargada).
<webServices>
<soapExtensionReflectorTypes>
<add type=“EmpresaX.ServicioWeb.LocalAddressReflector, WebService“/>
</soapExtensionReflectorTypes>
</webServices>
Cabe destacar que el nombre “localAddresReflector” lo puedes reemplazar por el que tu hayas elegido para tu clase, y que “WebService” no es mas que el nombre del folder (en este caso, raiz) donde se encuentra mi clase (sin eso, no funciona).
Y eso hizo el truco, cuando lo ejecute, el WSDL ya estaba correcto:
<wsdl:port name=“ServicioSoap“ binding=“tns:ServicioSoap“>
<soap:address location=“https://empresaX/servicio/funcion.asmx“ />
</wsdl:port>
Cabe mencionar que esto puede ser util no solo para mi caso, sino para cambiar, por ejemplo, “http” por “https” (el caso de Kirk), o bien, para especificar un puerto diferente para el web service (agregando “:1234″ al final de la direccion).
Y ahi tuve la solucion, despues de apenas 15 horas de trabajar en ella. Espero que si llegas a este post en un caso desesperado, te resulte de utilidad.
September 17th, 2008 at 11:44 am
Hola fijate que yo tengo el mismo problema en Java al momento de publicarlo en un servidor externo el wsdl mantiene la soap:address location como localhost y creo que por eso no puedo ver el resultado, Pero aun no encuentro una clase que cambien ese dato al momento de publicar el servicio, me podrías dar un norte de como buscarlo en internet.