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.
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) ;
}
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;
}
}
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();
}
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)
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.