Log timeout JAX-WS (Handlers)

22 Aug 2019 » java, jax-ws, webservices, log (aproximadamente 7 minutos de leitura)

Introdução

Dando seguimento aos posts sobre JAX-WS (o primeiro foi sobre configuração de timeout e pode ser visto no link) hoje será apresentado um recurso bastante importante para troubleshooting, guardar o request/response que o serviço recebeu/enviou.

O grande ponto aqui é poder monitorar e acompanhar todos os dados que entram e saem para o serviço que está sendo consumido. É possível escrever SOAP Handler no lado client e no lado server. Esse artigo cobre no lado client.

A Oracle disponibiliza documentação onde é possível entender melhor toda essa estrutura de handlers para a especificação JAX-WS.

Aplicabilidade

A idéia de handlers não é unicamente tratar de logs, o principal objetivo é fornecer mecanismo para que seja possível fazer algum processamento adicional na mensagem SOAP. Por isso, os handlers são mecanismos para interceptar as mensagens SOAP tanto no request, quanto no response do serviço.

  • Algumas outras idéias de uso:
  • Mensuração de performance do serviço
  • Criação de mecanismo de cache (ao invés de ir no banco, buscar dados do cache)

Tipos de Handlers

  • Soap Handlers: Podem acessar a mensagem SOAP por inteiro. Headers + Body.
  • Logical Handlers: Podem acessar apenas o payload da mensagem e não pode modificar qualquer informação especifica do protocolo (exemplo: headers).

O mundo Java tem duas especificações para trabalhar com a parte de Webservices. Jax-WS e JAX-RS. JAX-WS foca na criação de serviços e clientes que seguem atendem a comunicação de serviços usando o protocolo SOAP.

Caso de uso

O projeto precisava documentar todos requests/responses para tentarmos identificar o problema que era consumido a partir do barramento de integração. Como o projeto já possuía uma estrutura padrão de persistir registros de logs em uma tabela específica, essa estrutura foi utilizada e o código funcionou bem.

Na próxima seção é apresentada a classe completa, porém o trecho abaixo ajudou a identificar o que é um request e o que é um response:

boolean isOutboundMessage = (Boolean) arg0.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
		TipoLogEventoSistemaModel tipoLogEventoSistemaModel = TipoLogEventoSistemaModel.TIPO_SAIDA_MENSAGENS_SOAP;
		if (isOutboundMessage) {
			tipoLogEventoSistemaModel = TipoLogEventoSistemaModel.TIPO_ENTRADA_MENSAGENS_SOAP;
		}
		return tipoLogEventoSistemaModel;

Exemplo de como integrar o Handler

Implementação do handler usado no projeto

@Stateless
public class JaxWsSoapHandler implements SOAPHandler<SOAPMessageContext> {

	@EJB
	LogEventoSistemaEJB logEventoSistemaEJB;

	@EJB
	ParametrosEJB parametrosEJB;
	
	@Override
	public void close(MessageContext arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public boolean handleFault(SOAPMessageContext arg0) {
		TipoLogEventoSistemaModel tipoLogEventoSistemaModel = getTipoEvento(arg0);
		SOAPMessage message = arg0.getMessage();
		try {
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			message.writeTo(out);
			String messageContent = new String(out.toByteArray());
			logEventoSistemaEJB.inserirAsynchronousJaxWs(new LogEventoSistemaModel(
					null,
					null, 
					InetAddress.getLocalHost().getHostName(),
					InetAddress.getLocalHost().getHostAddress(),
					logWsdl(arg0), 
					messageContent, 
					tipoLogEventoSistemaModel,
					StatusLogEventoSistemaModel.STATUS_LOG_FALHA, 
					parametrosEJB.getDataAtual(),
					null
					));
		} catch (SOAPException | IOException e1) {
			e1.printStackTrace();
		}
		return true;
	}

	@Override
	public boolean handleMessage(SOAPMessageContext arg0) {
		SOAPMessage message = arg0.getMessage();
		TipoLogEventoSistemaModel tipoLogEventoSistemaModel = getTipoEvento(arg0);

		try {
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			message.writeTo(out);
			String messageContent = new String(out.toByteArray());

		
			logEventoSistemaEJB.inserirAsynchronousJaxWs(new LogEventoSistemaModel(
					null,
					null, 
					InetAddress.getLocalHost().getHostName(),
					InetAddress.getLocalHost().getHostAddress(),
					logWsdl(arg0), 
					messageContent, 
					tipoLogEventoSistemaModel,
					StatusLogEventoSistemaModel.STATUS_LOG_OK, 
					parametrosEJB.getDataAtual(),
					null
					));

		} catch (SOAPException | IOException e) {
			try {
				logEventoSistemaEJB.inserirAsynchronousJaxWs(new LogEventoSistemaModel(
						null,
						null, 
						InetAddress.getLocalHost().getHostName(),
						InetAddress.getLocalHost().getHostAddress(),
						logWsdl(arg0), 
						e.getMessage(), 
						tipoLogEventoSistemaModel,
						StatusLogEventoSistemaModel.STATUS_LOG_FALHA, 
						parametrosEJB.getDataAtual(),
						null
						));
			} catch (UnknownHostException e1) {
				e1.printStackTrace();
			}
		} 
		return true;
	}

	private TipoLogEventoSistemaModel getTipoEvento(SOAPMessageContext arg0) {
		boolean isOutboundMessage = (Boolean) arg0.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
		TipoLogEventoSistemaModel tipoLogEventoSistemaModel = TipoLogEventoSistemaModel.TIPO_SAIDA_MENSAGENS_SOAP;
		if (isOutboundMessage) {
			tipoLogEventoSistemaModel = TipoLogEventoSistemaModel.TIPO_ENTRADA_MENSAGENS_SOAP;
		}
		return tipoLogEventoSistemaModel;
	}
	
	
	
	private String logWsdl(SOAPMessageContext arg0) {
		return new StringBuilder().append("Service: ").append(arg0.get(MessageContext.WSDL_SERVICE))
		.append("\nOperation: ").append(arg0.get(MessageContext.WSDL_OPERATION))
		.append("\nDescription: ").append(arg0.get(MessageContext.WSDL_DESCRIPTION)).toString();
	}

	@Override
	public Set<QName> getHeaders() {
		// TODO Auto-generated method stub
		return null;
	}

}

Implementação no cliente do serviço para associar o handler criado ao cliente.

 EmployeeRecordsManagementPort customerPort = getPort(service);
		
		BindingProvider bindProv = (BindingProvider) customerPort;
		java.util.List<Handler> handlers = bindProv.getBinding().getHandlerChain();
		handlers.add(jaxWsSoapHandler);
		bindProv.getBinding().setHandlerChain(handlers);

Conclusão

Importante conhecer mecanismos de auditoria para os serviços. Para análise futuras de performance ou até mesmo ajudar na investigação de um problema. Embora no contexto específico desse projeto a persistência tenha sido em uma tabela do banco de dados, recomendaria utilização de ferramentas como MongoDB ou até mesmo Elastic Search para persistência desses logs, uma vez que quando houver problemas, buscas textuais serão feitas, então, como normalmente o volume de dados é alto e sabemos que busca com like em tabelas de grande volume não tem as melhores performance em banco de dados relacionais, persistir em outros tipos de banco pode auxiliar.

Um outro ponto importante é colocar o mecanismo de persistência assincrono, evitando que a performance do consumo do serviço seja comprometida.

Outras Fontes