Nesse artigo vamos examinar um mecanismo eficiente de envio de dados binários a Webservices conhecido por MTOM (Message Transmission Optimization Mechanism) e XOP (XML-Binary Optimization Packaging) e como é o suporte a essa funcionalidade dentro do framework Apache CXF.

Processamento utilizando MTOM
Figura 1. Processamento utilizando MTOM

Se você olhar as informações sobre MTOM no site do W3C pode ficar realmente confuso com a quantidade de especificações e documentações. Mas como sempre, basta dar uma olhada com calma para perceber que é mais simples do que parece. Resumidamente, vamos identificar as 3 especificações que definem o que é MTOM e como ele deve funcionar:

  • SOAP Transmission Optmization Feature: descreve de forma abstrata como se otimizar a transmissão e o formato de uma mensagem SOAP através da codificação seletiva de partes de suas partes sem falar exatamente qual o formato da serialização em si. Se uma mensagem é transmitida entre vários nós, os participantes podem escolher formas diferentes de otimizar a transmissão.
  • Optmized MIME Multipart/Related Serialization of SOAP Messages: é a implementação da funcionalidade descrita acima, mas sem estar amarrado a um binding específico (SOAP com HTTP ou outros). Para tanto, o XOP é utilizado. O XOP nada mais é do que uma convenção que define a melhor maneira de se serializar um XML Infoset. Mais detalhes aqui
  • HTTP SOAP Transmission Optimization Feature. usa a implementação de “Optmized MIME Multipart/Related Serialization of SOAP Messages” para descrever como é o “SOAP Transmission Optmization Feature” em HTTP. Em termos simples, descreve como transmitir dados binários utilizando SOAP e HTTP.

Agora ficou fácil demais! Fica claro que os profissionais do W3C se preocuparam em criar definições cujas implementações pudessem ser evoluídas com o tempo, em novos protocolos ou formatos mais otimizados daí essa separação em várias camadas.

Agora vamos ver o que o framework Apache CXF tem para nós com relação ao MTOM. Para maiores detalhes, vale a pena dar uma olhada nessa página.

Antes de mais nada, vamos definir o nosso WebService utilizando JAX-WS.


package br.com.devmedia.helloworldservice;

import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.ws.soap.MTOM;

@WebService(targetNamespace = HelloService.NS)
@MTOM(enabled=true)
@org.apache.cxf.interceptor.InInterceptors(interceptors = { 
"org.apache.cxf.interceptor.LoggingInInterceptor" })
@org.apache.cxf.interceptor.OutInterceptors(interceptors = { 
"org.apache.cxf.interceptor.LoggingOutInterceptor" })
public interface HelloService {
	String NS = "http://devmedia.sayhello";

	@WebResult(name = "response")
	public abstract PersonInfo digaOla(PersonInfo info) ;
}
Listagem 1. Serviço utilizando suporte MTOM

A primeira coisa que nos chama a atenção é a anotação MTOM. Ao adiciona-la, informamos ao Apache CXF que esse serviço suporta o uso do padrão. Na nossa operação digaOla, recebemos um objeto complexo por parâmetro do tipo PersonInfo. É esse objeto que encapsula as informações binárias. Vamos ver como isso é implementado utilizando o JAXB (listagem 2).


import javax.activation.DataHandler;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.XmlType;


@XmlType
public class PersonInfo {
	
	private DataHandler binaryData;

	@XmlMimeType("application/octet-stream")
	public DataHandler getBinaryData() {
		return binaryData;
	}

	public void setBinaryData(DataHandler binaryData) {
		this.binaryData = binaryData;
	}
}
Listagem 2. Objeto que carrega informações binárias

A classe PersonInfo é também muito simples e carrega apenas um atributo do tipo DataHandler. A classe DataHandler do pacote javax.activation é simplesmente uma abstração que representa uma transferência de dados entre várias origens e em qualquer formato. Dessa forma, programadores não precisam se preocupar como será utilizado o XOP ou qualquer outra codificação, pois isso fica por conta do Apache CXF.

O nosso serviço já consegue receber e transmitir grandes volumes de informação binária. Vamos examinar na listagem 3 para ver como é a implementação de um cliente simples para o nosso serviço.


HelloService hello = service.getPort(portName, HelloService.class);
SOAPBinding binding = (SOAPBinding) ((BindingProvider)hello).getBinding();
binding.setMTOMEnabled(true);

PersonInfo info = new PersonInfo();
File f;
try {
	f = new File(SimpleNormalHelloService.class.getResource("/java.png").toURI());
	DataSource ds = new FileDataSource(f);
	DataHandler dh = new DataHandler(ds);
	info.setBinaryData(dh);
	PersonInfo result = hello.digaOla(info);
	result.getBinaryData();
	Assert.assertNotNull(result);
} catch (URISyntaxException e) {
	e.printStackTrace();
}
Listagem 3. Cliente para serviço HelloService

No nosso cliente, vemos dois aspectos interessantes: primeiro o MTOM habilitado no Binding. Isso fará com que o cliente mande requisições com o formato otimizado. Em segundo, de forma bem simples criamos um DataHandler que encapsula as informações binárias que desejamos enviar para o serviço. Para finalizar, vamos dar uma olhada como ficou a requisição SOAP na listagem 4.


Address: http://localhost:9292/hello
Encoding: UTF-8
Http-Method: POST
Content-Type: multipart/related; type="application/xop+xml"; 
boundary="uuid:0b2e9c8c-3b23-49bc-a925-b2b0435616af"; 
start="<root.message@cxf.apache.org>"; start-info="text/xml"
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: 
--uuid:0b2e9c8c-3b23-49bc-a925-b2b0435616af
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml";
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:digaOla xmlns:ns2="http://devmedia.sayhello">
<arg0>
<binaryData>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" 
href="cid:784823ae-1a42-4ed1-a07e-153fb671c7ef-1@cxf.apache.org"/>
</binaryData>
</arg0>
</ns2:digaOla></soap:Body>
</soap:Envelope>
--uuid:0b2e9c8c-3b23-49bc-a925-b2b0435616af
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
Content-ID: <784823ae-1a42-4ed1-a07e-153fb671c7ef-1@cxf.apache.org>
Content-Disposition: attachment;name="java.png"

?PNG (binary data)
Listagem 4. Requisição SOAP otimizada com MTOM

Vemos claramente na requisição que o conteúdo representado pelo cabeçalho Content-Type é o xop+xml. No payload SOAP, vemos na seção body a tag binaryData com um ID: 784823ae-1a42-4ed1-a07e-153fb671c7ef. Esse ID é uma referência ao conteúdo binário que está um pouco mais abaixo do cabeçalho “Content-ID”.

Se é tão fácil e tão otimizado, por que não mandar tudo utilizando isso? O overhead do uso do MTOM é consideravelmente alto já que o conteúdo binário é codificado em base64, que faz com que o tamanho da mensagem aumente em cerca de 30%. Isso o torna ineficaz para mensagens pequenas. Além do mais, existe o XML FastInfoset que já é um padrão eficiente para mensagens menores.

Pessoal, com esse artigo conseguimos de uma maneira muito simples entender o que é MTOM e ver como é extremamente simples enviar conteúdo binário para um WebService. Não se esqueçam de dar uma olhada no projeto completo no GitHub.