Spring: вызов службы SOAP асинхронной с использованием WebFlux - PullRequest
2 голосов
/ 20 февраля 2020

У меня есть приложение Reactive Spring, использующее WebFlux с REST API. Всякий раз, когда пользователь вызывает мой API, мне нужно позвонить в службу SOAP, которая предоставляет WSDL, выполнить некоторую операцию и вернуть результат.

Как объединить этот вызов со службой SOAP с платформой Reactive WebFlux?

Как я вижу, я могу сделать это двумя различными способами:

  1. Построить и отправить сообщение SOAP с помощью WebFlux 'WebClient.
  2. Завершение синхронного вызова с использованием WebServiceGatewaySupport в Mono / Flux.

Первый подход имеет мои предпочтения, но я не знаю, как это сделать.

Похоже здесь задавались вопросы: Reactive Spring WebClient - SOAP вызов , который ссылается на это сообщение в блоге (https://blog.godatadriven.com/jaxws-reactive-client). Но я не мог заставить этот пример работать.

Используя wsdl2java в плагине Gradle, я могу создать клиентский интерфейс с асинхронными методами, но я не понимаю, как это использовать. При использовании WebServiceGatewaySupport я вообще не использую этот сгенерированный интерфейс или его методы. Вместо этого я вызываю метод generi c marshalSendAndReceive

public class MySoapClient extends WebServiceGatewaySupport {

    public QueryResponse execute() {
        Query query = new ObjectFactory().createQuery();
        // Further create and set the domain object here from the wsdl2java generated classes       
        return (QueryResponse) getWebServiceTemplate().marshalSendAndReceive(query);
    }
}

Может ли кто-нибудь поделиться полным примером, начиная с контроллера WebFlux и заканчивая вызовом SOAP и асинхронным возвратом? Я чувствую, что упускаю что-то решающее.

Ответы [ 2 ]

0 голосов
/ 16 марта 2020

У меня была та же цель, но без файла WSDL. В качестве входных данных у меня была конечная точка и XSD-файл, который определяет схему запроса, которую я должен отправить. Вот мой кусок кода.

Сначала давайте определим наш компонент SOPA WebClient (чтобы не создавать его каждый раз, когда мы хотим сделать вызов)

@Bean(name = "soapWebClient")
public WebClient soapWebClient(WebClient.Builder webClientBuilder) {
        String endpoint = environment.getRequiredProperty(ENDPOINT);
        log.info("Initializing SOAP Web Client ({}) bean...", endpoint);

        return webClientBuilder.baseUrl(endpoint)
                               .defaultHeader(CONTENT_TYPE, "application/soap+xml")
                               //if you have any time limitation put them here
                               .clientConnector(getWebClientConnector(SOAP_WEBCLIENT_CONNECT_TIMEOUT_SECONDS, SOAP_WEBCLIENT_IO_TIMEOUT_SECONDS))
                               //if you have any request/response size limitation put them here as well
                               .exchangeStrategies(ExchangeStrategies.builder()
                                                                     .codecs(configurer -> configurer.defaultCodecs()
                                                                                                     .maxInMemorySize(MAX_DATA_BUFFER_SIZE))
                                                                     .build())
                               .build();
}

public static ReactorClientHttpConnector getWebClientConnector(int connectTimeoutSeconds, int ioTimeoutSeconds) {
        TcpClient tcpClient = TcpClient.create()
                                       .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutSeconds * 1000)
                                       .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(ioTimeoutSeconds))
                                                                  .addHandlerLast(new WriteTimeoutHandler(ioTimeoutSeconds)));
        return new ReactorClientHttpConnector(HttpClient.from(tcpClient));
}

И теперь вы можете использовать клиент совершает SOAP вызовы, подобные этому:

@Slf4j
@Component
public class SOAPClient {

    private final WebClient soapWebClient;

    public SOAPClient(@Qualifier("soapWebClient") WebClient soapWebClient) {
        this.soapWebClient = soapWebClient;
    }

    public Mono<Tuple2<HttpStatus, String>> send(String soapXML) {
        return Mono.just("Request:\n" + soapXML)
                   .doOnNext(log::info)
                   .flatMap(xml -> soapWebClient.post()
                                                .bodyValue(soapXML)
                                                .exchange()
                                                .doOnNext(res -> log.info("response status code: [{}]", res.statusCode()))
                                                .flatMap(res -> res.bodyToMono(String.class)
                                                                   .doOnNext(body -> log.info("Response body:\n{}", body))
                                                                   .map(b -> Tuples.of(res.statusCode(), b))
                                                                   .defaultIfEmpty(Tuples.of(res.statusCode(), "There is no data in the response"))))
                   .onErrorResume(ConnectException.class, e -> Mono.just(Tuples.of(SERVICE_UNAVAILABLE, "Failed to connect to server"))
                                                                   .doOnEach(logNext(t2 -> log.warn(t2.toString()))))
                   .onErrorResume(TimeoutException.class, e -> Mono.just(Tuples.of(GATEWAY_TIMEOUT, "There is no response from the server"))
                                                                   .doOnEach(logNext(t2 -> log.warn(t2.toString()))));
    }

}

Главное, что здесь следует упомянуть, я считаю, что ваш soapXML должен быть в формате, который определен протоколом SOAP, очевидно. Чтобы быть более точным, c сообщение должно начинаться и заканчиваться тегом soap:Envelope и содержать все остальные данные. Также обратите внимание, какую версию протокола вы собираетесь использовать, поскольку она определяет, какие теги разрешено использовать в конверте, а какие - нет. Мой был 1.1, и вот спецификация для него https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383494

ура

0 голосов
/ 21 февраля 2020

Я сталкиваюсь с той же проблемой в течение недели и все еще не могу найти лучшее решение. Если вы хотите протестировать WebClient, вам просто нужно опубликовать строку с запросом конверта SOAP. Примерно так:

    String _request = "<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">\n" +
              "<soap:Body>\n" +
               "<request>\n" +
                  "<Example>blabla</Example>\n" +
               "</request>\n" +
              "</soap:Body>\n" +
            "</soap:Envelope>";

    WebClient webClient = WebClient.builder().baseUrl("http://example-service").build();

    Mono<String> stringMono = webClient.post()
            .uri("/example-port")
            .body(BodyInserters.fromObject(_request))
            .retrieve()
            .bodyToMono(String.class);

    stringMono.subscribe(System.out::println);

Проблема в том, что вам нужно выяснить, как сериализовать весь SOAP конверт (запрос и ответ) в строку. Это только пример, а не решение.

...