I am looking to make a SOAP call from spring reactive webclient. I couldn't find any documentation for it. Wondering what would the approach. Right now I am thinking
What are the downsides and any other approaches?
Steps to Consume a SOAP service :Create spring boot project and Get the WSDL from the provider . Convert the WSDL to Stub. Understand the request ,response and the types ,operations using any tool like SOAP UI. Form the request object by mapping data and call the soap uri with marshal the java objects as XML.
In this tutorial, we're going to examine WebClient, which is a reactive web client introduced in Spring 5.
You need to generate SOAP client as the stub classes with methods for asynchronous. JAX-WS API supports asynchronous invocation. Use wsiimport with enableAsyncMapping for generating method operationAsync(Input request, AsyncHandler asyncHandler);
AsyncHandler create using Mono.create()
Service service = new Service(); ServicePortType portType = service.getPortType(); public Mono<Output> operation(Input input) { return Mono.create(sink -> portType.operation(input, outputFuture -> { try { sink.success(outputFuture.get()); } catch (Exception e) { sink.error(e); } }) ); }
and you get Mono reactivly
I have found suggest in the post https://blog.godatadriven.com/jaxws-reactive-client
Here is a working example with Spring Reactor: https://github.com/gungor/spring-webclient-soap
You need to enclose your generated JAXB classes in a soap envelope with a custom encoder as below then add it to WebClient's exchange strategies.
package webclient.soap.encoding;
import org.reactivestreams.Publisher;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.CodecException;
import org.springframework.core.codec.Encoder;
import org.springframework.core.codec.EncodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.PooledDataBuffer;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.support.DefaultStrategiesHelper;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.MarshalException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class Jaxb2SoapEncoder implements Encoder<Object> {
private final JaxbContextContainer jaxbContexts = new JaxbContextContainer();
@Override
public boolean canEncode(ResolvableType elementType, MimeType mimeType) {
Class<?> outputClass = elementType.toClass();
return (outputClass.isAnnotationPresent(XmlRootElement.class) ||
outputClass.isAnnotationPresent(XmlType.class));
}
@Override
public Flux<DataBuffer> encode(Publisher<?> inputStream, DataBufferFactory bufferFactory, ResolvableType elementType, MimeType mimeType, Map<String, Object> hints) {
return Flux.from(inputStream)
.take(1)
.concatMap(value -> encode(value, bufferFactory, elementType, mimeType, hints))
.doOnDiscard(PooledDataBuffer.class, PooledDataBuffer::release);
}
@Override
public List<MimeType> getEncodableMimeTypes() {
return Arrays.asList( MimeTypeUtils.TEXT_XML );
}
private Flux<DataBuffer> encode(Object value ,
DataBufferFactory bufferFactory,
ResolvableType type,
MimeType mimeType,
Map<String, Object> hints){
return Mono.fromCallable(() -> {
boolean release = true;
DataBuffer buffer = bufferFactory.allocateBuffer(1024);
try {
OutputStream outputStream = buffer.asOutputStream();
Class<?> clazz = ClassUtils.getUserClass(value);
Marshaller marshaller = initMarshaller(clazz);
// here should be optimized
DefaultStrategiesHelper helper = new DefaultStrategiesHelper(WebServiceTemplate.class);
WebServiceMessageFactory messageFactory = helper.getDefaultStrategy(WebServiceMessageFactory.class);
WebServiceMessage message = messageFactory.createWebServiceMessage();
marshaller.marshal(value, message.getPayloadResult());
message.writeTo(outputStream);
release = false;
return buffer;
}
catch (MarshalException ex) {
throw new EncodingException(
"Could not marshal " + value.getClass() + " to XML", ex);
}
catch (JAXBException ex) {
throw new CodecException("Invalid JAXB configuration", ex);
}
finally {
if (release) {
DataBufferUtils.release(buffer);
}
}
}).flux();
}
private Marshaller initMarshaller(Class<?> clazz) throws JAXBException {
Marshaller marshaller = this.jaxbContexts.createMarshaller(clazz);
marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
return marshaller;
}
}
WebClient config
@Bean
public WebClient webClient(){
TcpClient tcpClient = TcpClient.create();
tcpClient
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.doOnConnected(connection -> {
connection.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS));
connection.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS));
});
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder().codecs( clientCodecConfigurer -> {
clientCodecConfigurer.customCodecs().encoder(new Jaxb2SoapEncoder());
}).build();
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient).wiretap(true)))
.exchangeStrategies( exchangeStrategies )
.build();
return webClient;
}
WebClient
public void call(GetCountryRequest getCountryRequest) throws SOAPException, ParserConfigurationException, IOException {
webClient.post()
.uri( soapServiceUrl )
.contentType(MediaType.TEXT_XML)
.body( Mono.just(getCountryRequest) , GetCountryRequest.class )
.retrieve()
.onStatus(
HttpStatus::isError,
clientResponse ->
clientResponse
.bodyToMono(String.class)
.flatMap(
errorResponseBody ->
Mono.error(
new ResponseStatusException(
clientResponse.statusCode(),
errorResponseBody))))
.bodyToMono(GetCountryResponse.class)
.doOnSuccess( (GetCountryResponse response) -> {
//handle success
})
.doOnError(ResponseStatusException.class, error -> {
//handle error
})
.subscribe();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With