8.22.2016

Web Service – JAX-WS, передача бинарных данных, MTOM/XOP.



Давайте познакомимся с терминами SOAP Message Transmission Optimization Mechanism/XML-binary Optimized Packaging (MTOM/XOP). При передаче больших бинарных данных в протоколе SOAP, таких как большие строки, рисунки, музыкальные файлы, они обычно кодируются в два xml schema  типа это  xs:base64Binary или xs:hexBinary.  Кодирование/раскодирование в эти типы отнимает много времени у программ, по этому предложили простой выход, применять оптимизацию передачи двоичных данных, так появился термин MTOM.  MTOM  для оптимизации больших бинарных данных использует  механизм XOP, который описан в официальной спецификации https://www.w3.org/TR/2005/REC-soap12-mtom-20050125/ . Для начала я вам покажу пример без MTOM, в дальнейшем мы подключим этот механизм и посмотрим в чем различие. Берем пример из прошлой статьи и дорабатываем его, добавим метод принимающий бинарные данные void getBinary(byte[] b):

package com.lopanov;

import javax.jws.WebService;

@WebService
public interface HelloWorld {
    Person sayHi(String text);
    void getBinary(byte[] b);
}

Допишем реализацию нашего web сервиса, который получает бинарные данные и выводит их в стандартный вывод System.out:

package com.lopanov;

import org.apache.cxf.annotations.Logging;

import javax.jws.WebService;
import java.io.IOException;
import java.util.GregorianCalendar;

@WebService(endpointInterface = "com.lopanov.HelloWorld")
@Logging
public class HelloWorldImpl implements HelloWorld {

    public Person sayHi(String text) {
        Person person = new Person(text, "Lopanov", new GregorianCalendar(1981, 8, 23));
        return person;
    }

    @Override
    public void getBinary(byte[] b) {
        try {
            System.out.write(b);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Перепишем нашего клиента из прошлой статьи:

package com.lopanov;

import com.sun.xml.ws.developer.StreamingAttachmentFeature;
import javax.xml.namespace.QName;
import javax.xml.ws.Endpoint;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;
import java.io.File;
import java.io.FileInputStream;

public class Main1 {
    private static final QName SERVICE_NAME = new QName("http://lopanov.com/", "HelloWorld");
    private static final QName PORT_NAME = new QName("http://lopanov.com/", "HelloWorldPort");

    public static void main(String args[]) throws Exception {
        System.out.println("Starting Server");
        HelloWorldImpl implementor = new HelloWorldImpl();
        String address = "http://localhost:9000/helloWorld";
        Endpoint.publish(address, implementor);

        Service service = Service.create(SERVICE_NAME);
        // Endpoint Address
        String endpointAddress = "http://localhost:9000/helloWorld";
        // Add a port to the Service
        service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
       
        HelloWorld hw = service.getPort(HelloWorld.class);
        File file = new File("c:/11.txt");
        FileInputStream fis = new FileInputStream(file);
        byte[] b = new byte[1024*10];
        fis.read(b,0,b.length);//прочитаем наш файл c:/11.txt в байтовый массив
        hw.getBinary(b);//вызовем операцию web сервиса и передадим ему бинарные данные
        //System.out.println(hw.sayHi("World"));
        System.exit(0);
    }
}

Мы получили такой SOAP лог:
….
[qtp138776324-17] INFO org.apache.cxf.services.HelloWorldImplService.HelloWorldImplPort.HelloWorld - Inbound Message
----------------------------
ID: 1
Address: http://localhost:9000/helloWorld
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=UTF-8
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], content-type=[text/xml; charset=UTF-8], Host=[localhost:9000], Pragma=[no-cache], SOAPAction=[""], transfer-encoding=[chunked], User-Agent=[Apache-CXF/3.1.7]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>
<ns2:getBinary xmlns:ns2="http://lopanov.com/">
<arg0>cm91dGUgYWRkIDEw
….
Вырезал информацию, т.к. много данных
….
09PT09PT09PT09PQ==</arg0>
</ns2:getBinary>
</soap:Body></soap:Envelope>
….

Исследуя полученный лог, мы приходим к выводу что наши бинарные данные передаются в теле SOAP сообщения и “закодированы”, заметьте этот термин, а не просто переданы, в тип xml schema  xs:base64Binary” или “xs:hexBinary”. Для того что бы точно узнать заглянем в wsdl  файл и уточним тип:
<xs:complexType name="getBinary">
   <xs:sequence>
    <xs:element minOccurs="0" name="arg0" type="xs:base64Binary"/>
  </xs:sequence>
</xs:complexType>

При любом кодировании или раскодировании, теряется драгоценное время, программа становиться медленней, поэтому нужно стараться избегать таких проблем. Давайте добавим в наш пример использование MTOM, которое поможет нам избавиться от лишнего кодирования/раскодирования бинарных данных в формат xs:base64Binary. Что делает MTOM/XOP, он любые бинарные данные включает в сообщение SOAP без какой-либо перекодировки. Задействовать механизм можно двумя способами, на клиенте с помощью класса MTOMFeature(), который принимает два параметра, первый параметр enabled имеет тип boolean, выставив его в true вы включаете механизм MTOM, второй параметр  имеет название threshold  вы указываете количество байт, так сказать пороговое значение, при превышении  которого,  будет включен механизм MTOM, если  вы пересылаете меньше байт, то будет произведено кодирование xs:base64Binary. Перепишем клиента:

    public static void main(String args[]) throws Exception {
        System.out.println("Starting Server");
        HelloWorldImpl implementor = new HelloWorldImpl();
        String address = "http://localhost:9000/helloWorld";
        Endpoint.publish(address, implementor);

        Service service = Service.create(SERVICE_NAME);
        // Endpoint Address
        String endpointAddress = "http://localhost:9000/helloWorld";
        // Add a port to the Service
        service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
       
        HelloWorld hw = service.getPort(HelloWorld.class, new MTOMFeature(true,0));//подключаем MTOM
        File file = new File("c:/11.txt");
        FileInputStream fis = new FileInputStream(file);
        byte[] b = new byte[1024*10];
        fis.read(b,0,b.length);//прочитаем наш файл c:/11.txt в байтовый массив
        hw.getBinary(b);//вызовем операцию web сервиса и передадим ему бинарные данные
        //System.out.println(hw.sayHi("World"));
        System.exit(0);
    }

Получаем такой SOAP лог:

[qtp138776324-15] INFO org.apache.cxf.services.HelloWorldImplService.HelloWorldImplPort.HelloWorld - Inbound Message
----------------------------
ID: 1
Address: http://localhost:9000/helloWorld
Encoding: ISO-8859-1
Http-Method: POST
Content-Type: multipart/related; type="application/xop+xml"; boundary="uuid:4499dc67-2a40-48d3-9095-9880caebb91c"; start="<root.message@cxf.apache.org>"; start-info="text/xml"
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], content-type=[multipart/related; type="application/xop+xml"; boundary="uuid:4499dc67-2a40-48d3-9095-9880caebb91c"; start="<root.message@cxf.apache.org>"; start-info="text/xml"], Host=[localhost:9000], Pragma=[no-cache], SOAPAction=[""], transfer-encoding=[chunked], User-Agent=[Apache-CXF/3.1.7]}
Payload: --uuid:4499dc67-2a40-48d3-9095-9880caebb91c
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:getBinary xmlns:ns2="http://lopanov.com/">
<arg0><xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:57fbcfca-9232-4f3b-b9c0-86a3c57b3902-1@cxf.apache.org"/></arg0>
</ns2:getBinary></soap:Body></soap:Envelope>
--uuid:4499dc67-2a40-48d3-9095-9880caebb91c
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
Content-ID: <57fbcfca-9232-4f3b-b9c0-86a3c57b3902-1@cxf.apache.org>

route add 10.0.0.0 10.152.30.1
….
Бинарный текст пропущен
….
          0.0.0.0          0.0.0.0      10.152.30.1  ® 㬮«ç ­¨î
======================================
--uuid:4499dc67-2a40-48d3-9095-9880caebb91c—

И так мы видим что в SOAP теле появилось включение бинарных данных, посредством ссылки XOP:  <xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:57fbcfca-9232-4f3b-b9c0-86a3c57b3902-1@cxf.apache.org"/> Осталось показать как включать MTOM на стороне web  сервиса, происходит это при помощи аннотации @MTOM, у нее есть два параметра, enabled если выставлено в true, значит включен MTOM, и threshold – порог  в байтах, при превышении которого используется XOP  передача данных, аналогично классу MTOMFeature(). Смотрим на код web  сервиса:

package com.lopanov;

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

@WebService
@MTOM(enabled = true,threshold = 0)
public interface HelloWorld {
    Person sayHi(String text);
    void getBinary(byte[] b);
}

Если вы запустите клиента, то вы получите прошлый SOAP лог, но только с другим Content-ID:.

Комментариев нет:

Отправить комментарий