'SOAP Proxy with Spring Integration

I'm trying to wrap my head around spring integration and would preferably like to stay away from XML-based configuration.

What I would like to do is the following:

  1. Accept a SOAP request on a certain endpoint
  2. Forward this request to a downstream SOAP service, without any modification (for now I just want the basics to work)
  3. Return the result to the client

This seems like it should be pretty simple, but I don't have the faintest idea of where to start. Is this even possible with SI? As I understand it, and please correct me if I'm wrong, SI is mainly used for async data flows.

I did check out the integration sample repository, which includes example inbound WS requests, but these are all configured in XML, which, as I said, I'd preferably like to stay far away from.

Any pointers would be much appreciated; I've been reading through documentation for the past two days and I'm none the wiser!



Solution 1:[1]

If your application is just a proxy over other SOAP service, you should consider to use just plain HTTP Inbound Gateway and HTTP Outbound Gateway.

You receive an XML from client and send it into the downstream service. Receive from there an XML again and just push it back to the response for the client.

For this purpose I can suggest HTTP proxy solution via Java DSL:

    @Bean
    public IntegrationFlow httpProxyFlow() {
        return IntegrationFlows
                .from(Http.inboundGateway("/service"))
                .handle(Http.outboundGateway("/service/internal")
                                .expectedResponseType(String.class))
                .get();
    }

The problem with the SimpleWebServiceInboundGateway and SimpleWebServiceOutboundGateway pair that they extract a request and parse a respose to (un)wrap to/from the SOAP envelop. This looks like an overhead for your plain proxy use-case.

Solution 2:[2]

Here is an example using the SimpleWebServiceInboundGateway. In this example we also set the "ExtractPayload" to false so that it sends the RAW soap message. But agree with above, possibly the HTTPInboundRequest is better for your use case. I also didn't find many examples using DSL for the SoapInboundGateway so wanted to share and hope it helps someone else.

@Configuration
@EnableIntegration
public class SoapGatewayConfiguration {

    /**
     * URL mappings used by WS endpoints
     */
    public static final String[] WS_URL_MAPPINGS = {"/services/*", "*.wsdl", "*.xsd"};
    public static final String GATEWAY_INBOUND_CHANNEL_NAME  = "wsGatewayInboundChannel";
    public static final String GATEWAY_OUTBOUND_CHANNEL_NAME = "wsGatewayOutboundChannel";


    /**
     * Register the servlet mapper, note that it uses MessageDispatcher
     */
    @Bean
    public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
        MessageDispatcherServlet servlet = new MessageDispatcherServlet();
        servlet.setApplicationContext(applicationContext);
        servlet.setTransformWsdlLocations(true);
        servlet.setTransformSchemaLocations(true);
        servlet.setPublishEvents(true);
        ServletRegistrationBean servletDef = new ServletRegistrationBean(servlet, WS_URL_MAPPINGS);
        servletDef.setLoadOnStartup(1);
        return servletDef;
    }

    /**
     * Create a new Direct channels to handle the messages
     */
    @Bean
    public MessageChannel wsGatewayInboundChannel() {
        return MessageChannels.direct(GATEWAY_INBOUND_CHANNEL_NAME).get();
    }
    @Bean
    public MessageChannel wsGatewayOutboundChannel() {
        return MessageChannels.direct(GATEWAY_OUTBOUND_CHANNEL_NAME).get();
    }

    /**
     * Startup the WebServiceInboundGateway Endpoint, this will handle the incoming SOAP requests
     *  and place them onto the request channel
     */
    @Bean
    public SimpleWebServiceInboundGateway webServiceInboundGateway(
            @Value("${spring.ws.request.timeout:1000}") long requestTimeout,
            @Value("${spring.ws.reply.timeout:1000}") long replyTimeout,
            @Value("${spring.ws.should.track:true}") boolean shouldTrack
    ) {
        SimpleWebServiceInboundGateway wsg = new SimpleWebServiceInboundGateway();
        wsg.setRequestChannel(wsGatewayInboundChannel());
        wsg.setReplyChannel(wsGatewayOutboundChannel());
        wsg.setExtractPayload(false);  // Send the full RAW SOAPMessage and not just payload
        wsg.setLoggingEnabled(true);
        wsg.setShouldTrack(shouldTrack);
        wsg.setReplyTimeout(replyTimeout); // Do not believe this prop supported currently
        wsg.setRequestTimeout(requestTimeout); // Do not believe this prop is supported currently
        wsg.setCountsEnabled(true);
        return wsg;
    }

    /**
     * You must enable debug logging on org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor
     *  to see the logs from this interceptor
     */
    @Bean
    public EndpointInterceptor soapMessageLoggingInterceptor() {
        SoapEnvelopeLoggingInterceptor li = new SoapEnvelopeLoggingInterceptor();
        li.setLogRequest(true);
        li.setLogResponse(true);
        li.setLogFault(true);
        return li;
    }


    /**
     * Validate the incoming web service against the schema
     */
    @Bean
    public EndpointInterceptor payloadValidatingInterceptor(XsdSchema xsdSchema
            , @Value("${spring.ws.soap.validate.request:true}") boolean soapValidateRequest
            , @Value("${spring.ws.soap.validate.reply:true}") boolean soapValidateResponse
            , @Value("${spring.ws.soap.validate.addErrorDetail:true}") boolean soapAddValidationErrorDetail

    ) {
        PayloadValidatingInterceptor interceptor = new PayloadValidatingInterceptor();
        interceptor.setXsdSchema(xsdSchema);
        interceptor.setValidateRequest(soapValidateRequest);
        interceptor.setValidateResponse(soapValidateResponse);
        interceptor.setAddValidationErrorDetail(soapAddValidationErrorDetail);
        return interceptor;
    }

    /**
     * Map the allowable service Uri's.
     */
    @Bean
    public EndpointMapping uriEndpointMapping(
             PayloadValidatingInterceptor payloadValidatingInterceptor
            , SimpleWebServiceInboundGateway webServiceInboundGateway
            , SoapEnvelopeLoggingInterceptor loggingInterceptor) {
        UriEndpointMapping mapping = new UriEndpointMapping();
        mapping.setUsePath(true);
        mapping.setDefaultEndpoint(webServiceInboundGateway);
        mapping.setInterceptors(new EndpointInterceptor[]{loggingInterceptor, payloadValidatingInterceptor});
        return mapping;
    }

    /**
     * Expose the wsdl at http://localhost:8080/services/myService.wsdl
     **/    
    @Bean
    public Wsdl11Definition myService() {
        SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition();
        wsdl11Definition.setWsdl(new ClassPathResource("META-INF/myService.wsdl"));
        return wsdl11Definition;
    }
    
    /**
     * Expose the xsd at http://localhost:8080/services/mySchema.xsd
     **/
    @Bean
    public XsdSchema mySchema() {
        return new SimpleXsdSchema(new ClassPathResource("META-INF/mySchema.xsd"));
    }    

    @Bean
    public IntegrationFlow itemLookupFlow() {
        return IntegrationFlows.from("wsGatewayInboundChannel")
                .log(LoggingHandler.Level.INFO)
                .handle(myBeanName, "execute")
                .log(LoggingHandler.Level.TRACE, "afterExecute")
                .get();
    }

}

Solution 3:[3]

I got it working thanks to Artem's answer, with a small tweak. Not sure as to why the channels are required, but at least it's now working.

package com.example.integration;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.http.Http;
import org.springframework.integration.http.config.EnableIntegrationGraphController;
import org.springframework.messaging.MessageChannel;

@SpringBootApplication
@EnableIntegration
@EnableIntegrationGraphController(allowedOrigins = "*")
public class IntegrationApplication {

    public static void main(String[] args) {
        SpringApplication.run(IntegrationApplication.class, args);
    }

    @Bean
    public DirectChannel directChannel() {
        return new DirectChannel();
    }

    @Bean
    public IntegrationFlow httpProxyFlow(MessageChannel directChannel) {
        return IntegrationFlows
                .from(Http.inboundGateway("/thing").requestChannel(directChannel).replyChannel(directChannel))
                .enrichHeaders(h -> h.header("Content-Type", "application/soap+xml; charset=utf-8"))
                .handle(Http.outboundGateway("http://www.webservicex.net/geoipservice.asmx").expectedResponseType(String.class))
                .channel(directChannel)
                .get();
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Artem Bilan
Solution 2 Guus
Solution 3 iLikeBreakfast