Преобразовать SOAP в виртуальный сервис Restful?

Есть ли способ использовать мой веб-сервис SOAP (spring-ws, java) как RESTful-сервис на основе XML?

Я не хочу переписывать весь мой веб-сервис SOAP в RESTful с нуля в Java, но мне нужно получить к нему доступ через iphone, используя REST, который они уже имеют простую встроенную поддержку.

XMLGateway, Proxys..? или какой-то дополнительный код Java? Так как мой запрос и ответ SOAP - это просто файл XML, почему я не могу изменить его для использования службой REST?

Или, не меняя логику и парсинг xml в моем приложении, так легко добавить аннотации jax-rs и создать xml-запрос на отдых?

мой файл конфигурации весны выглядит так:

<bean id="webServicePluginDescriptor"
    class="com.mysite.ws.configuration.MyWebservicePluginDescriptor" />

<bean id="payloadMapping"
    class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
    <property name="defaultEndpoint" ref="inferenceEndPoint" />
    <property name="interceptors">
        <list>
            <ref local="validatingInterceptor" />
            <ref local="payLoadInterceptor" />
        </list>
    </property>
</bean>

<bean id="payLoadInterceptor"
    class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor" />

<bean id="validatingInterceptor"
    class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
    <property name="schema"
        value="classpath:/wsdl/Request.xsd" />
    <property name="validateRequest" value="true" />
    <property name="validateResponse" value="false" />
</bean>

<bean id="PropertyResource" class="com.mysite.ws.im.PropertyResource">
    <property name="resource"
        value="/WEB-INF/client-specific/InferenceMachine.properties" />
</bean>

<bean id="inferenceEndPoint" class="com.mysite.ws.web.InferenceEndPoint">
    <property name="messageWebService" ref="messageWebService" />
</bean>
<bean id="messageWebService" class="com.mysite.ws.service.MessageWebService"
    scope="request">
    <aop:scoped-proxy />
    <property name="inferenceService" ref="inferenceService" />
</bean>

<bean id="Request" class="org.springframework.xml.xsd.SimpleXsdSchema">
    <property name="xsd" value="classpath:/wsdl/Request.xsd" />
</bean>

<bean id="Response" class="org.springframework.xml.xsd.SimpleXsdSchema">
    <property name="xsd" value="classpath:/wsdl/Response.xsd" />
</bean>

<bean id="Error" class="org.springframework.xml.xsd.SimpleXsdSchema">
    <property name="xsd" value="classpath:/wsdl/Error.xsd" />
</bean>

<bean id="mwsid"
    class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
    <constructor-arg value="classpath:/wsdl/mtchwsdl.wsdl" />
</bean>

<bean id="inferenceService" class="com.mysite.ws.im.InferenceService"
    scope="request">
    <aop:scoped-proxy />
    <property name="webServiceConfiguration" ref="wsPlayerConfiguration" />

    <property name="properties">
        <bean class="com.mysite.ws.im.PropertyResource">
            <property name="resource"
                value="/WEB-INF/client-specific/InferenceMachine.properties" />
        </bean>
    </property>
</bean>

<!-- ~~~~~~~ Application beans ~~~~~~~ -->
<bean id="wsPlayerConfiguration"
    class="com.mysite.ws.configuration.WebServiceConfiguration"
    scope="request">
    <aop:scoped-proxy />
    <property name="playerConfiguration" ref="playerConfiguration"></property>
    <property name="configurationSetup" ref="configurationSetup"></property>
</bean>

и это мой класс конечной точки:

/**
 * The EndPoint of the Web Service Application. This class gets the raw
 * SOAP-body message from the Spring Payload Dispatcher and sends the message to
 * the @see MessageService class. After it has gotten the response XML message
 * it returns this back to the Spring Payload Dispatcher.
 */
public class InferenceEndPoint extends AbstractJDomPayloadEndpoint {

    private MessageWebService messageWebService;
    public InferenceEndPoint() {
    }

    @Override
    protected Element invokeInternal(Element inferenceRequest) throws Exception {
        Element ret = messageWebService.handleRequest(inferenceRequest);
        return ret;
    }

    /**
     * @param messageWebService
     */
    public void setMessageWebService(MessageWebService messageWebService) {
        this.messageWebService = messageWebService;
    }
}

есть идеи?

3 ответа

Решение

Ничто не мешает вам отправить POST, заполненный XML, чтобы получить результаты, заполненные XML обратно.

Самое простое, что можно сделать, это каким-то образом перехватить запросы SOAP, идущие туда-сюда, и просто превратить запрос в шаблон с пробелами, являющимися вашими параметрами, а затем использовать XPath в полученном XML для получения ваших результатов.

Единственное, что вам может понадобиться - заголовок SOAPAction в вашем POST, но, скорее всего, нет.

Это действительно ничего страшного. Если у вас есть десятки методов, это больше боли. Кроме того, если вы используете какую-либо часть шифрования SOAP, то это больше проблем. Но если их всего несколько, то в конце концов это всего лишь XML, и большая часть этого XML является шаблонной, так что, если посмотреть с точки зрения, это довольно просто.

Дополнения:

Если у вас есть внутренняя логика, которую вы хотите использовать с более дружественным HTTP-сервисом, то JAX-RS может сделать это довольно легко, но для этого потребуется кодирование на стороне сервера.

Если у вас есть существующий веб-сервис SOAP, который вы хотите использовать, то забудьте всю часть уравнения SOAP и просто рассматривайте его как веб-сервис HTTP, который использует полезные нагрузки XML. Это все еще SOAP, но вы не используете никаких инструментов SOAP на стороне клиента. Вы просто собираете полезные нагрузки XML по клиентским запросам (из шаблонов было бы проще всего, ИМХО) и в результате потребляете полезные нагрузки XML и торгуете ими по HTTP.

В зависимости от того, сколько различных методов в существующем веб-сервисе вы намереваетесь вызывать, вы получите представление о масштабах выполняемой работы. Это простая работа (как только вы можете легко просматривать полезные данные), но она все еще работает. Если у вас есть только несколько методов, особенно если интерфейс стабилен и не меняется, гораздо проще просто работать с необработанным XML, чем изучать и бороться с какой-то новой незнакомой средой.

Глядя на ваш код, очень сложно сказать, каким будет лучший подход. REST и SOAP- это действительно разные способы представить, как может работать интерфейс веб-сервиса: SOAP- это вызовы методов, а REST - это ресурсы, представления и ссылки. Чтобы преобразовать, вы должны начать с вашего основного абстрактного API.

Если ваш базовый API - это "Я даю вам документ, а вы мне ответный документ", и ничего другого, кроме этого, нет, это очень SOAP-ориентированная модель. Вы можете смоделировать это в REST, разместив документ и получив ответ, но это совсем не элегантно. Если вы вместо этого можете думать о своем интерфейсе в терминах "вот общий ресурс, со свойствами, которые я могу установить, и некоторыми операциями, которые я могу выполнять", то это гораздо проще отображает REST (общий ресурс представлен в виде документа, полного ссылок для отдельных ресурсов ресурсов и операций, а отдельные свойства могут быть GET и PUT - и, возможно, DELETEd - по мере необходимости). Какой стиль у вас есть... ну, похоже, что у вас первый, но я только догадываюсь, потому что для его определения на самом деле потребуется рассмотреть больше кода, чем вы показали.

Spring-WS просто добавляет некоторые аннотации к вашим bean-компонентам, и тогда у вас есть bean-компоненты Spring, выполняющие большую часть тяжелой работы. Предположительно у вас есть некоторые классы, аннотированные @Endpoint, @PayloadRoot и тому подобное. Вы должны быть в состоянии повторно использовать все это одним из трех способов.

Если ваши классы конечных точек Spring-WS имеют стиль шаблона адаптера (например, ваш класс Endpoint внедряется с помощью службы POJO, которая выполняет настоящую работу), то вы можете создать аналогичный контроллер MVC Spring-style в стиле адаптера (где REST существует в Spring 3.0).

Если у вас есть свои аннотации непосредственно в вашем классе бизнес-логики, то теоретически вы можете просто добавить несколько аннотаций (может выглядеть немного занятым).

Если у вас есть bean-компоненты Spring-WS, которые являются POX (а не SOAP), то вам, возможно, удастся сойти с рук с некоторыми причудливыми сопоставлениями URL, чтобы дать им более RESTful выглядящие URL

Чтобы перейти на поддержку Spring 3 для поддержки REST, добавьте соответствующий @RequestMapping и другие аннотации, чтобы представить их как службы REST для соответствия определенным URL-адресам. Пока вы добавляете, вы также можете удалить старые @PayloadRoots и @Endpoint, но это может не иметь большого значения. Конечно, если вы оставите старые аннотации Spring-WS рядом, вам все равно понадобятся jar-файлы Spring-WS на вашем пути к классам, но если вы не используете ни сервлет Spring-WS, ни какие-либо другие bean-компоненты в своем файле Spring - вы должно быть в порядке (в теории...).

Самые большие ошибки будут:

  • Не забудьте удалить компоненты Spring-WS из вашего файла Spring
  • Не забудьте добавить bean-компоненты Spring MVC в ваш Spring-файл и, что важнее всего, другой сервлет Dispatcher.
  • Безопасность REST в Spring будет обеспечиваться Spring Security, а не теми перехватчиками SOAP, которые есть в Spring-WS, так что это будет полный пересмотр. Хорошей новостью является то, что с Spring Security на самом деле довольно легко работать

Вот как я решил эту проблему, используя Spring Boot + Spring Integration.

  • Имея WSDL для SOAP WS, я использовал maven-jaxb2-plugin для создания моих Java POJO во время сборки.
  • При желании вы можете создавать преобразования, чтобы адаптировать эти классы и атрибуты к ожиданиям клиента REST.
  • Используя Spring Integration, я сопоставил каждую конечную точку REST с шлюзом SOAP следующим образом:
@Bean
public IntegrationFlow myFlow(GenericTransformer reqTransformer, GenericTransformer resTransformer) {
    return IntegrationFlows
        .from(this.getRestGateway(POST, "/api/entity", MyRestResponse.class))
        .transform(reqTransformer)
        .handle(this.getSoapGateway("gwBean"))
        .enrichHeaders(h -> h.header(HttpHeaders.STATUS_CODE, HttpStatus.OK))
        .transform(resTransformer)
        .logAndReply();
}

private HttpRequestHandlingMessagingGateway getRestGateway(HttpMethod method, String path, Class payloadType) {
    HttpRequestHandlingMessagingGateway httpGateway = new HttpRequestHandlingMessagingGateway();
    RequestMapping requestMapping = new RequestMapping();
    requestMapping.setMethods(method);
    requestMapping.setPathPatterns(path);
    httpGateway.setRequestMapping(requestMapping);
    httpGateway.setReplyTimeout(timeout);
    httpGateway.setRequestPayloadTypeClass(payloadType);
    httpGateway.setMessageConverters(asList(jsonConverter));
    return httpGateway;
}

private MarshallingWebServiceOutboundGateway getSoapGateway(String nameBean) {
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setContextPath(generatedClassesPackage);
    SaajSoapMessageFactory messageFactory = new SaajSoapMessageFactory();
    messageFactory.setSoapVersion(SoapVersion.SOAP_12);
    messageFactory.afterPropertiesSet();
    MarshallingWebServiceOutboundGateway webServiceGateway = new MarshallingWebServiceOutboundGateway(soapUri, marshaller);
    webServiceGateway.setMessageFactory(messageFactory);
    webServiceGateway.setBeanName(nombreBean);
    return webServiceGateway;
}
Другие вопросы по тегам