Search code examples
javaspringjaxbjaxb2

Injecting HttpRequest into SOAP Endpoint


I'm looking for a way to forward http header information along with the SOAP message to the Spring Endpoint in order to get access to details like IP address, etc.

Relevant web.xml:

<servlet>
    <servlet-name>SoapHost</servlet-name>
    <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    <init-param>
        <param-name>transformWsdlLocations</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>SoapHost</servlet-name>
    <url-pattern>/ws/*</url-pattern>
</servlet-mapping>

SoapHost-servlet.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:sws="http://www.springframework.org/schema/web-services"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/web-services                       
       http://www.springframework.org/schema/web-services/web-services-2.0.xsd">

    <!-- To detect @Endpoint -->
    <sws:annotation-driven />

    <!-- To detect @Service, @Component etc -->
    <context:component-scan base-package="za.co.mycee.soaphost" />

    <!-- To generate dynamic wsdl for SoapHost Services -->
    <sws:dynamic-wsdl 
        id="SoapHost"
        portTypeName="SoapHost" 
        locationUri="/ws/SoapHost"
        targetNamespace="http://www.mycee.co.za/SoapHost"> 
        <sws:xsd location="/WEB-INF/SoapHost.xsd" />
    </sws:dynamic-wsdl>

    <!-- Validate Request and Response -->
    <sws:interceptors>
        <bean id="MyCeeSoapHost" class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
            <property name="schema" value="/WEB-INF/SoapHost.xsd" />
            <property name="validateRequest" value="true" />
            <property name="validateResponse" value="true" />
        </bean>
    </sws:interceptors> 
</beans>

Endpoint:

@Endpoint
public class ...Endpoint {

    @Autowired
    ...Service ...Service;

    @Autowired
    ...Service ...Service;

    @Autowired
    ...Service ...Service;

    @ResponsePayload
    @PayloadRoot(localPart = "...Request", namespace = "http://www.mycee.co.za/SoapHost")
    public ...Response do...(@RequestPayload ...Request request) {

        ...

        // get IP address here
            // get some other info from headers here

        ...


    }
}

I've tried adding it as one of the parameters:

    @ResponsePayload
    @PayloadRoot(localPart = "...Request", namespace = "http://www.mycee.co.za/SoapHost")
public ...Response do...(
        @RequestPayload ...Request request,  
        HttpServletRequest httpServletRequest) {

but that returns an error in the XML response: "No adapter for endpoint" and "Is your endpoint annotated with @Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint"


Solution

  • Sorry for not replying directly to your question, but why don't you put filter right before servlet? In this filter you can store IP or any other information in some context object, like ThreadLocal or similar.

    I believe, that SOAP should know nothing about IP address. These are different abstraction layers. SOAP, by design, operates on SOAP Headers if some meta information is required.

    Reply to comment from topic-starter:

    No, it won't break Spring-WS flow because it just absolutely normal to put servlets behind filters. Filter is a plain javax.servlet.Filter

    Example as requested:

    Endpoint that is slightly modified version of Spring's example

    @Endpoint
    public class FilteredEndpoint {
        @ResponsePayload
        @PayloadRoot(localPart = "HolidayRequest", namespace = "http://mycompany.com/hr/schemas")
        public void doRespose(@RequestPayload Element request) {
            System.out.println(Context.ip.get());
        }
    }
    

    Info is stored in Context object. Context uses ThreadLocal to hold data. It works because each request is handled by separate thread

    public class Context {
        public static ThreadLocal<String> ip = new ThreadLocal<String>();
    }
    

    Finally filter

    public class MyHttpFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {}
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            Context.ip.set(request.getRemoteAddr());
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {}
    }
    

    And web.xml

    <filter>
        <filter-name>httpfilter</filter-name>
        <filter-class>so.example.MyHttpFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>httpfilter</filter-name>
        <url-pattern>/ws/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>SoapHost</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
        <init-param>
            <param-name>transformWsdlLocations</param-name>
            <param-value>true</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>SoapHost</servlet-name>
        <url-pattern>/ws/*</url-pattern>
    </servlet-mapping>