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

В WSO2 Integration Studio создаем новый проект (File – New – Integration Project). Назовем его, например, Rosstat Service. Не забываем создать RegistryResources, поскольку SOAP-сервис в любом случае предусматривает WSDL/XSD, а их нужно где-то хранить

Добавляем в каждый из отмеченных каталогов пустой файл с именем ".gitkeep" для контроля версий:

Далее настраиваем остальные параметры в зависимости от проекта. Все ссылки репозитория WSO2 Nexus во всех файлах "pom.xml" меняем на ваши собственные. У нас, соответственно, https://maven.wso2.org/nexus/content/groups/wso2-public/ меняется на https://nexus.gts.rus.socgen/repository/maven-wso2-public/.

Создаем шаблоны

Создадим шаблон для вызова внешнего сервиса CallAPI.

В типе шаблона нужно выбрать Sequence Template. С точки зрения кода шаблон будет таким же, как и при создании REST API-сервиса, поскольку мы принимаем SOAP, а вызываем REST:

<template name="MOEX_callAPI_Template" xmlns="http://ws.apache.org/ns/synapse">  
<parameter defaultValue="" isMandatory="false" name="setCallEndpointName"/>  
<parameter defaultValue="" isMandatory="false" name="setEndpointURL"/>  
<parameter defaultValue="" isMandatory="false" name="setEndpointURL_Suffix"/>  
<parameter defaultValue="" isMandatory="false" name="setQueryParams"/>  
<sequence>  
    	<property expression="get-property('file', $func:setEndpointURL)" name="ENV_ENDPOINT_URL" scope="default" type="STRING"/>  
    	<filter regex="true" source="boolean($ctx:ENV_ENDPOINT_URL) and string-length($ctx:ENV_ENDPOINT_URL) > 0">  
        	<then/>  
        	<else>  
            	<property expression="get-property('env', $func:setEndpointURL)" name="ENV_ENDPOINT_URL" scope="default" type="STRING"/>  
        	</else>
    	</filter>  
    	<switch source="boolean($func:setQueryParams) and string-length($func:setQueryParams) > 0">  
        	<case regex="true">  
            	<property expression="fn:concat($ctx:ENV_ENDPOINT_URL, $func:setEndpointURL_Suffix, '?', $func:setQueryParams)" name="uri.var.endpointURL_FullAddress" scope="default" type="STRING"/>  
        	</case>  
        	<default>
            	<property expression="fn:concat($ctx:ENV_ENDPOINT_URL, $func:setEndpointURL_Suffix)" name="uri.var.endpointURL_FullAddress" scope="default" type="STRING"/>  
        	</default>
    	</switch>
    	<class description="OutReqLoggerTXT" name="ru.rosbank.mediator.LoggerMediatorTXT">  
        	<property name="logLevel" value="DEBUG"/>  
        	<property name="ReqType" value="OutRequest"/>  
        	<property expression="fn:concat('sending request to endpointURL_FullAddress=', $ctx:uri.var.endpointURL_FullAddress)" name="Payload"/>  
    	</class>  
    	<call>  
        	<endpoint key-expression="$func:setCallEndpointName"/>  
    	</call>  
</sequence>  
</template> 

Теперь создаем шаблон для обработки ошибок SOAPFault. Здесь нужно обратить внимание, какая версия протокола SOAP используется, 1.1 или 1.2. От этого будут зависеть некоторые параметры формирования ошибки, в частности, различаться ContentType. Вот код обработчика для SOAP 1.1:

<template name="Rosstat_SOAPFault_Template" xmlns="http://ws.apache.org/ns/synapse"> 
	<parameter defaultValue="" isMandatory="false" name="MessageID"/> 
	<parameter defaultValue="" isMandatory="false" name="ErrorText"/> 
	<parameter defaultValue="" isMandatory="false" name="Exception"/> 
	<sequence> 
    	<payloadFactory description="ExceptionPayload" media-type="xml"> 
        	<format> 
            	<m:Exception xmlns:m="http://wso2.rosbank.ru/types/Fault"> 
                	<msgID xmlns="">$1</msgID> 
                	<text xmlns="">$2</text> 
                	<exception xmlns="">$3</exception> 
            	</m:Exception> 
        	</format> 
        	<args> 
            	<arg evaluator="xml" expression="$func:MessageID"/> 
            	<arg evaluator="xml" expression="$func:ErrorText"/> 
            	<arg evaluator="xml" expression="$func:Exception"/> 
        	</args> 
    	</payloadFactory> 
    	<enrich description="body to property"> 
        	<source clone="true" type="body"/> 
        	<target property="MESSAGE_PAYLOAD" type="property"/> 
    	</enrich> 
    	<makefault description="SoapFault" version="soap11"> 
        	<code value="soap11Env:Server" xmlns:soap11Env="http://schemas.xmlsoap.org/soap/envelope/"/> 
        	<reason value="Error message processing"/> 
        	<detail expression="get-property('MESSAGE_PAYLOAD')"/> 
    	</makefault> 
    	<propertyGroup description="removeHeaders"> 
        	<property action="remove" name="TRANSPORT_HEADERS" scope="axis2"/> 
        	<property action="remove" name="EXCESS_TRANSPORT_HEADERS" scope="axis2"/> 
    	</propertyGroup> 
    	<propertyGroup description="setHeaders"> 
        	<property name="HTTP_SC" scope="axis2" type="STRING" value="500"/> 
        	<property name="HTTP_SC_DESC" scope="axis2" type="STRING" value="Internal Server Error"/> 
        	<property name="MESSAGE_FORMAT" scope="default" type="STRING" value="soap11"/> 
        	<property name="messageType" scope="axis2" type="STRING" value="text/xml"/> 
        	<property name="ContentType" scope="axis2" type="STRING" value="text/xml"/> 
        	<property name="Content-Type" scope="transport" type="STRING" value="text/xml;charset=UTF-8"/> 
        	<property name="RESPONSE" scope="default" type="STRING" value="true"/> 
    	</propertyGroup> 
	</sequence> 
</template>

При возникновении ошибки этот обработчик передает XML-сообщение с уникальным id, по которому можно будет легко ее найти в логах. Также не забудьте проставить свою версию SOAP для SOAPFault: description="SoapFault" version="soap11".

А вот код обработчика для SOAP 1.2:

<template name="Rosstat_SOAPFault_Template" xmlns="http://ws.apache.org/ns/synapse"> 
    <parameter defaultValue="" isMandatory="false" name="MessageID"/>
    <parameter defaultValue="" isMandatory="false" name="ErrorText"/>
    <parameter defaultValue="" isMandatory="false" name="Exception"/>
    <sequence>
        <payloadFactory description="ExceptionPayload" media-type="xml">
            <format>
                <m:Exception xmlns:m="http://wso2.rosbank.ru/types/Fault">
                    <msgID xmlns="">$1</msgID>
                    <text xmlns="">$2</text>
                    <exception xmlns="">$3</exception>
                </m:Exception>
            </format>
            <args>
                <arg evaluator="xml" expression="$func:MessageID"/>
                <arg evaluator="xml" expression="$func:ErrorText"/>
                <arg evaluator="xml" expression="$func:Exception"/>
            </args>
        </payloadFactory>
        <enrich description="body to property">
            <source clone="true" type="body"/>
            <target property="MESSAGE_PAYLOAD" type="property"/>
        </enrich>
        <makefault description="SoapFault" version="soap12">
            <code value="soap12Env:Receiver" xmlns:soap12Env="http://www.w3.org/2003/05/soap-envelope"/>
            <reason value="Error message processing"/>
            <detail expression="get-property('MESSAGE_PAYLOAD')"/>
        </makefault>
        <propertyGroup description="removeHeaders">
            <property action="remove" name="TRANSPORT_HEADERS" scope="axis2"/>
            <property action="remove" name="EXCESS_TRANSPORT_HEADERS" scope="axis2"/>
        </propertyGroup>
        <propertyGroup description="setHeaders">
            <property name="HTTP_SC" scope="axis2" type="STRING" value="500"/>
            <property name="HTTP_SC_DESC" scope="axis2" type="STRING" value="Internal Server Error"/>
            <property name="MESSAGE_FORMAT" scope="default" type="STRING" value="soap12"/>
            <property name="messageType" scope="axis2" type="STRING" value="application/soap+xml"/>
            <property name="ContentType" scope="axis2" type="STRING" value="application/soap+xml"/>
            <property name="Content-Type" scope="transport" type="STRING" value="application/soap+xml;charset=UTF-8"/>
            <property name="RESPONSE" scope="default" type="STRING" value="true"/>
        </propertyGroup>
    </sequence>
</template>

Создаем обработчики

Теперь создаем общие шаги — Sequence — для обработки входящих ошибок:

FaultSequence перехватывает исключения, которые могут произойти при обработке запроса, и вызывает шаблон SOAPFault, который мы только что сделали: 

Создадим обработчик Inbound для приема входящих запросов:

<sequence name="Rosstat_InboundSequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse"> 
	<propertyGroup description="setProperty"> 
    	<property name="ENV_SERVICE_NAME" scope="default" type="STRING" value="RosstatService"/> 
    	<property expression="$axis2:TransportInURL" name="ENV_REST_URL_POSTFIX" scope="default" type="STRING"/> 
    	<property expression="fn:substring-after(get-property('MessageID'), 'uuid:')" name="ENV_MESSAGE_ID" scope="default" type="STRING"/> 
    	<property expression="local-name(//soapenv:Envelope/soapenv:Body/*[1])" name="ENV_METHOD_NAME" scope="default" type="STRING" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"/> 
    	<property expression="fn:substring-after($axis2:TransportInURL, '?')" name="ENV_QUERY_PARAMS" scope="default" type="STRING"/> 
    	<property expression="string('')" name="ENV_EMPTY_STRING" scope="default" type="STRING"/> 
    	<property expression="$axis2:REMOTE_ADDR" name="ENV_IP_ADDRESS" scope="default" type="STRING"/> 
	</propertyGroup> 
	<class description="InReqLoggerTXT" name="ru.rosbank.mediator.LoggerMediatorTXT"> 
    	<property name="logLevel" value="DEBUG"/> 
    	<property name="ReqType" value="Begin"/> 
    	<property name="Payload" value="Request accepted..."/> 
	</class> 
</sequence>

В параметре ENV_SERVICE_NAME указываем название сервиса. Это же имя будет использоваться в LoggerService для записи логов в отдельный файл. В параметре "onError" здесь и далее в инструкции указываем ссылку на ранее созданный обработчик ошибок: onError="Rosstat_FaultSequence".

Здесь есть небольшие отличия от REST. Сгенирировав uuid, мы идем в SOAP Envelope – Body и оттуда получаем название метода. В протоколе SOAP название метода передается именно в теле.

Теперь сделаем Outbound — обработчик, который будет заносить в лог факт обработки запроса.

<sequence name="Rosstat_OutboundSequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse"> 
	<class description="InResLoggerTXT" name="ru.rosbank.mediator.LoggerMediatorTXT"> 
    	<property name="logLevel" value="DEBUG"/> 
    	<property name="ReqType" value="End"/> 
    	<property name="Payload" value="Request processed success"/> 
	</class> 
</sequence>

И наконец, новый обработчик, которого не было в REST API. UnsupportedSequence используется для обработки ошибки, когда метод был объявлен в WSDL, но не был реализован (или мы сами не хотим его выставлять). 

Здесь тоже используется шаблон SOAPFault. В сообщении ошибки мы выдаем «Unsupported service»:

<sequence name="Rosstat_UnsupportedService_Sequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse"> 
	<class description="InReqLoggerSOAP" name="ru.rosbank.mediator.LoggerMediatorSOAP"> 
    	<property name="logLevel" value="INFO"/> 
    	<property name="ReqType" value="InRequest"/> 
    	<property name="isLogHeader" value="true"/> 
    	<property name="isLogPayload" value="true"/> 
	</class> 
	<call-template description="SoapFault" target="Rosstat_SOAPFault_Template"> 
    	<with-param name="MessageID" value="{$ctx:ENV_MESSAGE_ID}"/> 
    	<with-param name="ErrorText" value="Unsupported service"/> 
    	<with-param name="Exception" value="{$ctx:ENV_EMPTY_STRING}"/> 
	</call-template> 
	<class description="InResLoggerSOAP" name="ru.rosbank.mediator.LoggerMediatorSOAP"> 
    	<property name="logLevel" value="INFO"/> 
    	<property name="ReqType" value="InResponse"/> 
    	<property name="isLogHeader" value="true"/> 
    	<property name="isLogPayload" value="true"/> 
	</class> 
</sequence>

Еще один обработчик, которого не было в REST API — обработчик конкретного метода (операции) GetOrganizationInfo. В REST API мы это делали на уровне API с помощью <resource>, здесь это устроено иначе.

 Логируем запрос, вызываем HTPP-сервис и логируем ответ: 

<sequence name="Rosstat_GetOrganizationInfo_Sequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse"> 
	<class description="InReqLoggerSOAP" name="ru.rosbank.mediator.LoggerMediatorSOAP"> 
    	<property name="logLevel" value="INFO"/> 
    	<property name="ReqType" value="InRequest"/> 
    	<property name="isLogHeader" value="true"/> 
    	<property name="isLogPayload" value="true"/> 
	</class> 
	<sequence key="Rosstat_GetOrganizationInfo_HTTPRequest_Sequence"/> 
	<class description="InResLoggerSOAP" name="ru.rosbank.mediator.LoggerMediatorSOAP"> 
    	<property name="logLevel" value="INFO"/> 
    	<property name="ReqType" value="InResponse"/> 
    	<property name="isLogHeader" value="true"/> 
    	<property name="isLogPayload" value="true"/> 
	</class> 
</sequence>

Теперь нам нужно создать обработчик запросов.

Здесь мы удаляем все транспортные заголовки, превращаем SOAP в JSON, задаем формат из трех полей и  ContentType, логируем запрос, вызываем CallTemplate и проверяем, что вернулся код 200. Если да, то мы вызываем sequence для обратной трансформации, если нет — логируем ошибку, вызываем шаблон SOAPfault:

<sequence name="Rosstat_GetOrganizationInfo_HTTPRequest_Sequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse"> 
	<propertyGroup description="removeHeaders"> 
    	<property action="remove" name="TRANSPORT_HEADERS" scope="axis2"/> 
    	<property action="remove" name="EXCESS_TRANSPORT_HEADERS" scope="axis2"/> 
	</propertyGroup> 
	<payloadFactory description="MappingIn" media-type="json"> 
    	<format>{"okpo":"$1", "inn":"$2", "ogrn":"$3"}</format> 
    	<args> 
        	<arg evaluator="xml" expression="//p:okpo/text()" xmlns:p="http://ru.rosbank.servicemix/cd/client"/> 
        	<arg evaluator="xml" expression="//p:inn/text()" xmlns:p="http://ru.rosbank.servicemix/cd/client"/> 
        	<arg evaluator="xml" expression="//p:ogrn/text()" xmlns:p="http://ru.rosbank.servicemix/cd/client"/> 
    	</args> 
	</payloadFactory> 
	<propertyGroup description="setHeader"> 
    	<property name="messageType" scope="axis2" type="STRING" value="application/json"/> 
	</propertyGroup> 
	<class description="OutReqLoggerJSON" name="ru.rosbank.mediator.LoggerMediatorJSON"> 
    	<property name="logLevel" value="INFO"/> 
    	<property name="ReqType" value="OutRequest"/> 
    	<property name="isLogHeader" value="true"/> 
    	<property name="isLogPayload" value="true"/> 
	</class> 
	<call-template description="callRosstat" onError="Rosstat_FaultSequence" target="Rosstat_callAPI_Template"> 
    	<with-param name="setCallEndpointName" value="Rosstat_GetOrganizationInfo_EP"/> 
    	<with-param name="setEndpointURL" value="webServiceURL_GetRosstatOrganizationInfo"/> 
    	<with-param name="setEndpointURL_Suffix" value=""/> 
    	<with-param name="setQueryParams" value=""/> 
	</call-template> 
	<filter regex="200" source="$axis2:HTTP_SC"> 
    	<then> 
        	<class description="OutResLoggerJSON" name="ru.rosbank.mediator.LoggerMediatorJSON"> 
            	<property name="logLevel" value="INFO"/> 
            	<property name="ReqType" value="OutResponse"/> 
            	<property name="isLogHeader" value="true"/> 
            	<property name="isLogPayload" value="true"/> 
        	</class> 
        	<sequence key="Rosstat_GetOrganizationInfo_MappingOUT_Sequence"/> 
    	</then> 
    	<else> 
        	<class description="OutResLoggerBINARY" name="ru.rosbank.mediator.LoggerMediatorBINARY"> 
            	<property name="logLevel" value="INFO"/> 
            	<property name="ReqType" value="OutResponse"/> 
            	<property name="isLogHeader" value="true"/> 
            	<property name="isLogPayload" value="true"/> 
        	</class> 
        	<call-template description="SoapFault" target="Rosstat_SOAPFault_Template"> 
            	<with-param name="MessageID" value="{$ctx:ENV_MESSAGE_ID}"/> 
            	<with-param name="ErrorText" value="GetOrganizationInfo returns non success response"/> 
            	<with-param name="Exception" value="{fn:concat('Error: ', $axis2:HTTP_SC, ' - ', $axis2:HTTP_SC_DESC)}"/> 
        	</call-template> 
    	</else> 
	</filter> 
</sequence>

Следующий шаг — MappingOUT — обратная трансформация из JSON в SOAP:

Здесь мы сохраняем в локальные переменные из JSON интересные нам id, type и name и генерируем payload для xml-ки SOAPResponse:

<sequence name="Rosstat_GetOrganizationInfo_MappingOUT_Sequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse"> 
	<propertyGroup description="Group of properties that extract data from response"> 
    	<property expression="json-eval($.[0].id)" name="id" scope="default" type="STRING"/> 
    	<property expression="json-eval($.[0].type)" name="type" scope="default" type="STRING"/> 
    	<property expression="json-eval($.[0].name)" name="name" scope="default" type="STRING"/> 
        <!-- остальные параметры протокола обмена удалены, для упрощения чтения --> 
	</propertyGroup> 
	<payloadFactory description="SOAPResponse" media-type="xml"> 
    	<format> 
        	<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> 
            	<soapenv:Body> 
                	<p:RosstatOrganizationInfoResponse xmlns:p="http://ru.rosbank.servicemix/cd/client"> 
                    	<p:id>$1</p:id> 
                    	<p:type>$2</p:type> 
                    	<p:name>$3</p:name> 
                	</p:RosstatOrganizationInfoResponse> 
            	</soapenv:Body> 
        	</soapenv:Envelope> 
    	</format> 
    	<args> 
        	<arg evaluator="xml" expression="get-property('id')"/> 
        	<arg evaluator="xml" expression="get-property('type')"/> 
        	<arg evaluator="xml" expression="get-property('name')"/> 
    	</args> 
	</payloadFactory> 
	<propertyGroup description="removeHeaders"> 
    	<property action="remove" name="TRANSPORT_HEADERS" scope="axis2"/> 
    	<property action="remove" name="EXCESS_TRANSPORT_HEADERS" scope="axis2"/> 
	</propertyGroup> 
	<propertyGroup description="setHeaders"> 
    	<property name="HTTP_SC" scope="axis2" type="STRING" value="200"/> 
    	<property name="HTTP_SC_DESC" scope="axis2" type="STRING" value="OK"/> 
    	<property name="MESSAGE_FORMAT" scope="default" type="STRING" value="soap11"/> 
    	<property name="messageType" scope="axis2" type="STRING" value="text/xml"/> 
    	<property name="ContentType" scope="axis2" type="STRING" value="text/xml"/> 
    	<property name="Content-Type" scope="transport" type="STRING" value="text/xml;charset=UTF-8"/> 
    	<property name="RESPONSE" scope="default" type="STRING" value="true"/> 
	</propertyGroup> 
</sequence>

Конечно, удаляем все ненужные транспортные заголовки из REST API, как и в прошлом случае.

Обязательно нужно сделать и последовательность для HealthCheck — ​​проверку готовности сервиса принимать траффик (Readness Probe). Он пригодится, когда мы будем деплоить всё в Kubernetes или OpenShift.

<sequence name="Rosstat_HealthRequest_Sequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse"> 
	<propertyGroup description="removeHeaders"> 
    	<property action="remove" name="TRANSPORT_HEADERS" scope="axis2"/> 
    	<property action="remove" name="EXCESS_TRANSPORT_HEADERS" scope="axis2"/> 
	</propertyGroup> 
	<payloadFactory description="SOAPResponse" media-type="xml"> 
    	<format> 
        	<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> 
            	<soapenv:Body> 
                	<p:HealthResponse xmlns:p="http://ru.rosbank.servicemix/cd/client"> 
                    	<p:status>OK</p:status> 
                	</p:HealthResponse> 
            	</soapenv:Body> 
        	</soapenv:Envelope> 
    	</format> 
    	<args/> 
	</payloadFactory> 
	<propertyGroup description="setHeaders"> 
    	<property name="HTTP_SC" scope="axis2" type="STRING" value="200"/> 
    	<property name="HTTP_SC_DESC" scope="axis2" type="STRING" value="OK"/> 
    	<property name="MESSAGE_FORMAT" scope="default" type="STRING" value="soap11"/> 
    	<property name="messageType" scope="axis2" type="STRING" value="text/xml"/> 
    	<property name="ContentType" scope="axis2" type="STRING" value="text/xml"/> 
    	<property name="Content-Type" scope="transport" type="STRING" value="text/xml;charset=UTF-8"/> 
    	<property name="RESPONSE" scope="default" type="STRING" value="true"/> 
	</propertyGroup> 
	<class description="InResLoggerSOAP" name="ru.rosbank.mediator.LoggerMediatorSOAP"> 
    	<property name="logLevel" value="INFO"/> 
    	<property name="ReqType" value="InResponse"/> 
    	<property name="isLogHeader" value="true"/> 
    	<property name="isLogPayload" value="true"/> 
	</class> 
</sequence>

Далее создадим endpoint для вызова внешних сервисов. Здесь всё так же, как и с REST API.

<endpoint name="Rosstat_GetOrganizationInfo_EP" xmlns="http://ws.apache.org/ns/synapse"> 
	<http method="post" uri-template="{uri.var.endpointURL_FullAddress}"> 
    	<timeout> 
        	<duration>40000</duration> 
        	<responseAction>fault</responseAction> 
    	</timeout> 
    	<suspendOnFailure> 
        	<errorCodes>101500,101501,101506,101507,101508</errorCodes> 
        	<initialDuration>1000</initialDuration> 
        	<progressionFactor>2.0</progressionFactor> 
        	<maximumDuration>60000</maximumDuration> 
    	</suspendOnFailure> 
    	<markForSuspension> 
        	<errorCodes>101503,101504,101505</errorCodes> 
        	<retriesBeforeSuspension>3</retriesBeforeSuspension> 
        	<retryDelay>100</retryDelay> 
    	</markForSuspension> 
	</http> 
</endpoint>

Добавляем WSDL-файл и XSD-схемы

В модуле RosstatServiceRegistryResources (в нашем примере) выбираем New – RegistryResource и в появившемся окне — From existing template.

 Далее выставляем параметры. 

ResourceName: RosstatOrganizationInfo (указываем имя файла WSDL без расширения) 
ArtifactName: RosstatOrganizationInfoWSDL (то же самое, что и для ResourceName, только добавляем в конце суффикс WSDL)
Template: выбираем из списка значение WSDL File
Registry: выбираем из списка значение conf
RegistryPath: resources/rosstat/wsdl

Аналогичным образом добавляем XSD-схемы.

ResourceName: RosstatOrganizationInfo (указываем имя файла XSD без расширения)
ArtifactName: RosstatOrganizationInfoXSD (то же самое, что и для ResourceName, только добавляем в конце суффикс XSD)
Template: выбираем из списка значение XSD File
Registry: выбираем из списка значение conf
RegistryPath: resources/rosstat/wsdl

Создаем ProxyService

С SOAP и другими протоколами, кроме REST API, используется ProxyService. Сделаем новый ProxyService, используя ранее созданные Template, Sequence, Endpoint.

Выбираем тип Custom Proxy и указываем транспортные протоколы http и https: 

 В режиме Design ProxyService будет выглядеть вот так: 

Атрибут name у корневого элемента proxy является именем SOAP-сервиса. Значение должно совпадать с тем, что будет в URL после /services/: https://localhost:8253/services/RosstatService?wsdl. Мы также добавили в код валидацию запроса по XSD:

<proxy name="RosstatService" startOnLoad="true" transports="http https" xmlns="http://ws.apache.org/ns/synapse"> 
	<target> 
    	<inSequence> 
        	<sequence key="Rosstat_InboundSequence"/> 
        	<validate cache-schema="true"> 
            	<schema key="conf:resources/rosstat/wsdl/RosstatOrganizationInfo.xsd"/> 
            	<on-fail> 
                	<sequence key="Rosstat_FaultSequence"/> 
            	</on-fail> 
        	</validate> 
        	<switch source="$ctx:ENV_METHOD_NAME"> 
            	<case regex="RosstatOrganizationInfoRequest"> 
                	<sequence key="Rosstat_GetOrganizationInfo_Sequence"/> 
            	</case> 
            	<case regex="HealthRequest"> 
                	<sequence key="Rosstat_HealthRequest_Sequence"/> 
            	</case> 
            	<default> 
                	<sequence key="Rosstat_UnsupportedService_Sequence"/> 
            	</default> 
        	</switch> 
        	<sequence key="Rosstat_OutboundSequence"/> 
        	<respond/> 
    	</inSequence> 
    	<outSequence/> 
    	<faultSequence> 
        	<sequence key="Rosstat_FaultSequence"/> 
    	</faultSequence> 
	</target> 
	<publishWSDL key="conf:resources/rosstat/wsdl/RosstatOrganizationInfo.wsdl" preservePolicy="true"> 
    	<resource key="conf:resources/rosstat/wsdl/RosstatOrganizationInfo.xsd" location="RosstatOrganizationInfo.xsd"/> 
	</publishWSDL> 
	<parameter name="disableREST">true</parameter> 
	<parameter name="disableSOAP12">true</parameter> 
</proxy>

В блоке "publishWSDL" нужно обязательно указать ссылки на WSDL-файл и XSD-схемы (если их несколько), которые были добавлены в проект на предыдущем шаге. Это необходимо как раз для валидации и получения корректных файлов WSDL/XSD через адрес https://localhost:8253/services/RosstatService?wsdl.  

Если сервис должен работать по SOAP 1.1/1.2, необходимо отключить REST и ненужную версию протокола SOAP.

Для SOAP 1.1:

<parameter name="disableREST">true</parameter>
<parameter name="disableSOAP12">true</parameter> 

Для SOAP 1.2:

<parameter name="disableREST">true</parameter> 
<parameter name="disableSOAP11">true</parameter>

Собираем сервис

Открывает pom-файл в модуле RosstatServiceCompositeExporter и выбираем все созданные артефакты в модулях RosstatServiceConfig и RosstatServiceRegistryResources:

Сборка сервиса через Maven организована так же, как и в случае с REST API.

Дальнейшие действия зависят от конкретного проекта. Как я указывал в прошлой статье, мы пишем интеграционные тесты с использованием Postman и заглушки для BackendService с Wiremock. 

Если у вас появились вопросы по разработке на WSO2, буду рад ответить на них в комментариях. Мы планируем продолжать серию публикаций по этой платформе, так что подписывайтесь, если интересно, и пишите, что еще хотели бы о ней узнать.

Комментарии (2)


  1. Qulac
    04.02.2022 17:50
    -1

    1. Не используйте SOAP если вы не госконтора.


    1. krivser Автор
      05.02.2022 04:44

      На самом деле не важно госконтора или нет. В ИТ-ландшафте могут быть и как показала практика есть такие enterprise legacy-системы, которые просто не могут взаимодействовать с внешним миром по другим протоколам/форматам. И в этом случае вам ничего не останется как делать адаптер для такой системы, который внутри уже реализует трансформацию из SOAP/XML -> REST/JSON