Перейти к основному содержимому

Отправить объект SOAP с Feign Client

· 6 мин. чтения

1. Обзор

Feign абстрагирует вызовы HTTP и делает их декларативными. Таким образом, Feign скрывает детали более низкого уровня, такие как управление HTTP-соединением, жестко заданные URL-адреса и другой шаблонный код. Существенным преимуществом использования клиентов Feign является простота вызовов HTTP и сокращение объема кода. Как правило, мы используем Feign для REST API -типа application/json media type. Однако клиенты Feign хорошо работают с другими типами мультимедиа, такими как text/xml , составные запросы и т. д.

В этом руководстве мы узнаем, как вызывать веб-службу на основе SOAP ( text/xml ) с помощью Feign.

2. Веб-служба SOAP

Предположим, что есть веб-служба SOAP с двумя операциями — getUser и createUser .

Давайте используем cURL для вызова операции createUser :

curl -d @request.xml -i -o -X POST --header 'Content-Type: text/xml'
http://localhost:18080/ws/users

Здесь request.xml содержит полезную нагрузку SOAP:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:feig="http://www.foreach.com/springbootsoap/feignclient">
<soapenv:Header/>
<soapenv:Body>
<feig:createUserRequest>
<feig:user>
<feig:id>1</feig:id>
<feig:name>john doe</feig:name>
<feig:email>john.doe@gmail.com</feig:email>
</feig:user>
</feig:createUserRequest>
</soapenv:Body>
</soapenv:Envelope>

Если все настройки верны, получаем успешный ответ:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns2:createUserResponse xmlns:ns2="http://www.foreach.com/springbootsoap/feignclient">
<ns2:message>Success! Created the user with id - 1</ns2:message>
</ns2:createUserResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Точно так же другая операция, getUser , также может быть вызвана с помощью cURL.

3. Зависимости

Далее давайте посмотрим, как использовать Feign для вызова этой веб-службы SOAP. Давайте разработаем два разных клиента для вызова службы SOAP. Feign поддерживает несколько существующих HTTP-клиентов, таких как Apache HttpComponents , OkHttp , java.net.URL и т. д . Давайте используем Apache HttpComponents в качестве основного HTTP-клиента. Во-первых, давайте добавим зависимости для OpenFeign Apache HttpComponents :

<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hc5</artifactId>
<version>11.8</version>
</dependency>

В следующих разделах мы рассмотрим несколько способов вызова веб-служб SOAP с помощью Feign.

4. Объект SOAP как обычный текст

Мы можем отправить запрос SOAP в виде простого текста с типом содержимого и принять заголовки, установленные в text/xml . Давайте теперь разработаем клиент, демонстрирующий этот подход:

public interface SoapClient {
@RequestLine("POST")
@Headers({"SOAPAction: createUser", "Content-Type: text/xml;charset=UTF-8",
"Accept: text/xml"})
String createUserWithPlainText(String soapBody);
}

Здесь createUserWithPlainText принимает полезную нагрузку String SOAP. Обратите внимание, что мы явно определили заголовки accept и content-type . Это связано с тем, что при отправке тела SOAP в виде текста обязательно указывать заголовки Content-Type и Accept как text/xml.

Недостатком этого подхода является то, что мы должны заранее знать полезную нагрузку SOAP. К счастью, если доступен WSDL, полезная нагрузка может быть сгенерирована с помощью инструментов с открытым исходным кодом, таких как SoapUI . Как только полезная нагрузка будет готова, давайте вызовем веб-службу SOAP с помощью Feign:

@Test
void givenSOAPPayload_whenRequest_thenReturnSOAPResponse() throws Exception {
String successMessage="Success! Created the user with id";
SoapClient client = Feign.builder()
.client(new ApacheHttp5Client())
.target(SoapClient.class, "http://localhost:18080/ws/users/");

assertDoesNotThrow(() -> client.createUserWithPlainText(soapPayload()));

String soapResponse= client.createUserWithPlainText(soapPayload());

assertNotNull(soapResponse);
assertTrue(soapResponse.contains(successMessage));
}

Feign поддерживает регистрацию сообщений SOAP и другой информации, связанной с HTTP. Эта информация имеет решающее значение для отладки. Итак, давайте включим ведение журнала Feign. Для регистрации этих сообщений требуется дополнительная зависимость feign-slf4j :

<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>11.8</version>
</dependency>

Давайте улучшим наш тестовый пример, включив в него информацию журнала:

SoapClient client = Feign.builder()
.client(new ApacheHttp5Client())
.logger(new Slf4jLogger(SoapClient.class))
.logLevel(Logger.Level.FULL)
.target(SoapClient.class, "http://localhost:18080/ws/users/");

Теперь, когда мы запускаем тест, у нас есть журналы, похожие на:

18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> "SOAPAction: createUser[\r][\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> "<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:feig="http://www.foreach.com/springbootsoap/feignclient">[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <soapenv:Header/>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <soapenv:Body>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:createUserRequest>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:user>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:id>1</feig:id>[\n]"
18:01:58.295 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:name>john doe</feig:name>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " <feig:email>john.doe@gmail.com</feig:email>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " </feig:user>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " </feig:createUserRequest>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> " </soapenv:Body>[\n]"
18:01:58.296 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 >> "</soapenv:Envelope>"
18:01:58.300 [main] DEBUG org.apache.hc.client5.http.wire - http-outgoing-0 << "<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:createUserResponse xmlns:ns2="http://www.foreach.com/springbootsoap/feignclient"><ns2:message>Success! Created the user with id - 1</ns2:message></ns2:createUserResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>"

5. Подделка кодека SOAP

Более чистый и лучший подход к вызову веб-службы SOAP — использование кодека SOAP Feign . Кодек помогает упорядочивать сообщения SOAP (от Java к SOAP)/десортировать (от SOAP к Java). Однако для кодека требуется дополнительная зависимость feign-soap . Поэтому объявим эту зависимость:

<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-soap</artifactId>
<version>11.8</version>
</dependency>

Кодек Feign SOAP кодирует и декодирует объекты SOAP, используя JAXB и SoapMessage, а JAXBContextFactory предоставляет необходимые маршалеры и демаршалеры.

Далее, на основе созданного нами XSD, давайте создадим классы предметной области. JAXB требует, чтобы эти доменные классы маршализировали и демаршалировали сообщения SOAP. Во-первых, давайте добавим плагин в наш pom.xml :

<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.14.0</version>
<executions>
<execution>
<id>feign-soap-stub-generation</id>
<phase>compile</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<schemaDirectory>target/generated-sources/jaxb</schemaDirectory>
<schemaIncludes>
<include>*.xsd</include>
</schemaIncludes>
<generatePackage>com.foreach.feign.soap.client</generatePackage>
<generateDirectory>target/generated-sources/jaxb</generateDirectory>
</configuration>
</execution>
</executions>
</plugin>

Здесь мы настроили плагин для запуска на этапе компиляции . Теперь давайте сгенерируем заглушки:

mvn clean compile

После успешной сборки целевая папка содержит исходники:

./b92ce5ebf4ba92cbcea0f4ab55c3ce5d.png

Далее воспользуемся этими заглушками и Feign для вызова веб-службы SOAP. Но сначала добавим в наш SoapClient новый метод :

@RequestLine("POST")
@Headers({"Content-Type: text/xml;charset=UTF-8"})
CreateUserResponse createUserWithSoap(CreateUserRequest soapBody);

Далее давайте протестируем веб-службу SOAP:

@Test
void whenSoapRequest_thenReturnSoapResponse() {
JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
.withMarshallerJAXBEncoding("UTF-8").build();
SoapClient client = Feign.builder()
.encoder(new SOAPEncoder(jaxbFactory))
.decoder(new SOAPDecoder(jaxbFactory))
.target(SoapClient.class, "http://localhost:18080/ws/users/");
CreateUserRequest request = new CreateUserRequest();
User user = new User();
user.setId("1");
user.setName("John Doe");
user.setEmail("john.doe@gmail");
request.setUser(user);
CreateUserResponse response = client.createUserWithSoap(request);

assertNotNull(response);
assertNotNull(response.getMessage());
assertTrue(response.getMessage().contains("Success"));
}

Давайте улучшим наш тестовый пример, чтобы регистрировать сообщения HTTP и SOAP:

SoapClient client = Feign.builder()
.encoder(new SOAPEncoder(jaxbFactory))
.errorDecoder(new SOAPErrorDecoder())
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.decoder(new SOAPDecoder(jaxbFactory))
.target(SoapClient.class, "http://localhost:18080/ws/users/");

Этот код генерирует аналогичные журналы, которые мы видели ранее.

Наконец, давайте обработаем ошибки SOAP. Feign предоставляет SOAPErrorDecoder , который возвращает SOAP Fault как SOAPFaultException . Итак, давайте установим этот SOAPErrorDecoder в качестве декодера ошибок Feign и обработаем ошибки SOAP:

SoapClient client = Feign.builder()
.encoder(new SOAPEncoder(jaxbFactory))
.errorDecoder(new SOAPErrorDecoder())
.decoder(new SOAPDecoder(jaxbFactory))
.target(SoapClient.class, "http://localhost:18080/ws/users/");
try {
client.createUserWithSoap(request);
} catch (SOAPFaultException soapFaultException) {
assertNotNull(soapFaultException.getMessage());
assertTrue(soapFaultException.getMessage().contains("This is a reserved user id"));
}

Здесь, если веб-служба SOAP выдает ошибку SOAP, она будет обработана SOAPFaultException .

6. Заключение

В этой статье мы научились вызывать веб-службу SOAP с помощью Feign. Feign — это декларативный HTTP-клиент, упрощающий вызов веб-служб SOAP/REST. Преимущество использования Feign в том, что он сокращает количество строк кода. Меньшие строки кода приводят к меньшему количеству ошибок и меньшему количеству модульных тестов.

Как всегда, полный исходный код доступен на GitHub .