Seleccionar página

Añadir logging a un cliente SOAP de web services

por | Jun 9, 2008 | General

Cuando trabajamos con web services en java, a la hora de depurar nos sería muy útil el poder ver el xml (protocolo SOAP) que se envían cliente y servidor.
Sin embargo no existe una forma ‘sencilla’ de hacerlo.

La idea consiste en hacer una clase que implemente el interface javax.xml.rpc.handler.Handler, capaz de interceptar los mensajes SOAP antes de su envío del cliente al servidor y antes de la recepción por parte del cliente de los mensajes enviados por el servidor.
Pueden añadirse varios handlers en serie para procesar las llamadas (handler chains). Este comportamiento es idéntico al de un ServletFilter en una cadena de filtros.
Desde esta clase tenemos acceso al contenido del mensaje SOAP, por lo que somos capaces de leerlo y escribirlo en un fichero de texto o similar.

En el caso de un cliente hecho con apache axis, podemos obtener la lista de Handlers asociados a un nombre de puerto (javax.xml.namespace.QName) y a un punto de destino a través de la clase org.apache.axis.client.Service. Una vez obtenida la lista, solo nos resta añadir nuestro Handler.

Vamos a ello. En primer lugar implementamos el interfaz javax.xml.rpc.handler.Handler para interceptar la llamada soap:

package com.madeinxpain.seamcity.ws;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.rpc.handler.Handler;
import javax.xml.rpc.handler.HandlerInfo;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.handler.soap.SOAPMessageContext;

public class SoapLoggingHandler implements Handler{

private HandlerInfo handlerInfo;
private List streams;

public SoapLoggingHandler() {
streams = new ArrayList();
try {
this.addOutputStreamLog(System.out);
this.addFileLog(«soap.xml»);
} catch (Exception e) {
e.printStackTrace();
}
}

public void addOutputStreamLog(OutputStream os) {
this.streams.add(os);
}

public void addFileLog(String rute) throws FileNotFoundException {
FileOutputStream fos = new FileOutputStream(new File(rute));
this.addOutputStreamLog(fos);
}

public boolean handleRequest(MessageContext arg0) {
SOAPMessageContext messageContext = (SOAPMessageContext) arg0;
try {
for (OutputStream os : streams) {
os.write(new String(«nn<!– REQUEST –>n»).getBytes());
messageContext.getMessage().writeTo(os);
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}

public boolean handleResponse(MessageContext arg0) {
SOAPMessageContext messageContext = (SOAPMessageContext) arg0;
try {
for (OutputStream os : streams) {
os.write(new String(«nn<!– RESPONSE –>n»).getBytes());
messageContext.getMessage().writeTo(os);
}
} catch (Exception e) {
}
return true;
}

public boolean handleFault(MessageContext arg0) {
SOAPMessageContext messageContext = (SOAPMessageContext) arg0;
try {
for (OutputStream os : streams) {
os.write(new String(«nn<!– FAULT –>n»).getBytes());
messageContext.getMessage().writeTo(os);
}
} catch (Exception e) {
}
return true;
}

public void init(HandlerInfo arg0) {
this.handlerInfo = arg0;
}

public void destroy() {
}

public QName[] getHeaders() {
return handlerInfo.getHeaders();
}
}

Esta clase implementa una serie de métodos extra aparte de los obligatorios definidos por el interface. Estos métodos permiten definir una lista de Streams por los que escribir el mensaje.
Estos métodos de logging deben ser invocado desde el constructor, ya que al añadir el Handler a la cadena (como veremos más adelante) no se añade una instancia de un objeto, si no una clase, y es el framework axis el encargado de crear la instancia mediante el método Class.new Instance(), por lo que no es posible definir las formas de logging si no es en el constructor por defecto del handler.

Un a vez codificada la clase, vamos a proceder a añadirla a la cadena. Cada handler debe estar asociado
Tomaremos como ejemplo un cliente WebService para Microsoft Exchange 2007, creado en un post anterior
En primer lugar buscamos la clase que extiende de org.apache.axis.client.Stub, en nuestro caso llamada ExchangeServiceBindingStub, y le añadimos dos campos para almacenar el port por el que se va a realizar la petición soap y el punto de destino.
Estos campos se van a utilizar en el método createCall() a la hora de crear la petición soap.
Por simplicidad crearemos los campos como públicos.

public class ExchangeServiceBindingStub extends org.apache.axis.client.Stub implements com.microsoft.schemas.exchange.services._2006.messages.ExchangeServicePortType {

public javax.xml.namespace.QName portName;
public String endPoint;

// resto del código original de la clase

// metodo a modificar
protected org.apache.axis.client.Call createCall() throws java.rmi.RemoteException {
// código anterior
if (super.cachedPortName != null) {
_call.setPortName(super.cachedPortName);
}

// Tenemos que añadir estas dos lineas
_call.setPortName(this.portName);
_call.setTargetEndpointAddress(this.endPoint);

// código posterior
java.util.Enumeration keys = super.cachedProperties.keys();

}

Cuando instanciamos el objeto org.apache.axis.client.Stub le añadimos el handler:

public class ExchangeWebServiceClient {
private ExchangeServiceBindingStub esb;
private ExchangeServicesLocator locator;

public ExchangeWebServiceClient() {
this.locator = new ExchangeServicesLocator();
this.esb = (ExchangeServiceBindingStub) locator.getExchangeServicePort(new URL(locator.getExchangeServicePortAddress()));

// Añadimos el handler
QName portName = new QName(«http://www.neodoo.es/», locator.getExchangeServicePortWSDDServiceName());
List list = locator.getHandlerRegistry().getHandlerChain(portName);
HandlerInfo handlerInfo = new HandlerInfo();
handlerInfo.setHandlerClass(com.madeinxpain.seamcity.ws.SoapLoggingHandler.class);
list.add(handlerInfo);
this.esb.portName = portName;
this.esb.endPoint = locator.getExchangeServicePortAddress();
}
}

Y con esto y un bizcocho, ya tenemos una forma de depurar las llamadas soap a un webservice desde un cliente hecho con axis.
La optimización del código queda a cargo del desarrollador final ;)

Te puede interesar…

3 Comentarios

  1. Yo

    Pues me da error en la variable streams… al usarlo en los for.
    «Type mismatch: cannot convert from element type Object to OutputStream»

    Alguien seria tan amable de darme una solucion? Estaré eternamente agradecido

    Responder
  2. Daniela

    Muchas gracias, me fue muy útil.

    Responder

Enviar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *