Spring Web Services - Exception Skipping ExceptionResolver

前端 未结 8 952
别那么骄傲
别那么骄傲 2020-12-19 04:33

I have a SOAP service, the request and responses work as expected with good input, if I specify bad input for an XML element

in request body:

...
<         


        
相关标签:
8条回答
  • 2020-12-19 05:05

    Since the doService no longer throws an error for invalid XML and simply sets status to 400, I had to explore further on Chen's answer and implemented the following.

    public class CustomWebServiceMessageReceiverHandlerAdapter extends WebServiceMessageReceiverHandlerAdapter {
    
      @Override
      protected void handleInvalidXmlException(HttpServletRequest httpServletRequest,
                                               HttpServletResponse httpServletResponse,
                                               Object handler,
                                               InvalidXmlException ex) throws IOException {
        String error = ex.getMessage();
        String errorXml = "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
            "<SOAP-ENV:Header/>" +
            "<SOAP-ENV:Body>" +
            "<SOAP-ENV:Fault>" +
            "<faultcode>SOAP-ENV:Client</faultcode>" +
            "<faultstring xml:lang=\"en\"></faultstring>" + error +
            "</SOAP-ENV:Fault>" +
            "</SOAP-ENV:Body>" +
            "</SOAP-ENV:Envelope>";
        httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        httpServletResponse.setContentType("text/xml");
        httpServletResponse.getWriter().write(errorXml);
        httpServletResponse.getWriter().flush();
      }
    }
    

    On the bean configuration updated messageDispatcherServlet as below by setting messageReceiverHandlerAdapterBeanName to use the custom WebServiceMessageReceiverHandlerAdapter.

      @Bean
      public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
        MessageDispatcherServlet servlet = new MessageDispatcherServlet();
        servlet.setApplicationContext(applicationContext);
        servlet.setMessageReceiverHandlerAdapterBeanName("customMessageReceiverHandlerAdapter");
        return new ServletRegistrationBean(servlet, "/ws/*");
      }
    
      @Bean
      public CustomWebServiceMessageReceiverHandlerAdapter customMessageReceiverHandlerAdapter() {
        CustomWebServiceMessageReceiverHandlerAdapter customWebServiceMessageReceiverHandlerAdapter =
            new CustomWebServiceMessageReceiverHandlerAdapter();
        customWebServiceMessageReceiverHandlerAdapter.setMessageFactory(new DomPoxMessageFactory());
        return customWebServiceMessageReceiverHandlerAdapter;
      }
    
    0 讨论(0)
  • 2020-12-19 05:11

    I've looked more closely at your question and I think that I know what's happening. You exception handler is not called because it is in the higher level in the soap processing. You see, WebServiceMessageReceiverHandlerAdapter tries to decode the incoming string to an XML before sending it to the marshaller to be processed. Since the XML is invalid the call fails. And since WebServiceMessageReceiverHandlerAdapter does not support an exception handler, it just rethrows the exception "SaajSoapMessageException".

    Now what you can do is create a new class that extends WebServiceMessageReceiverHandlerAdapter, but that also wraps handleConnection() in a try/catch that uses your exception handler when a exception is throw.


    By the way, when debugging this kind of problem my approach is to output both method name and line number in log4j. As well as downloading the Spring sources.

    0 讨论(0)
  • 2020-12-19 05:13

    In the latest Spring WS version you can use custom webservice message listener

    public class CustomWebServiceMessageListener extends WebServiceMessageListener {
    
    private static final Logger LOG = LoggerFactory.getLogger(CustomWebServiceMessageListener.class);
    
    @Override
    public void onMessage(Message message, Session session) throws JMSException {
        try {
            this.handleMessage(message, session);
        } catch (JmsTransportException jmsTransportException) {
            LOG.error(jmsTransportException.getMessage(), jmsTransportException);
        } catch (Exception e) {
            JMSException jmsException = new JMSException(e.getMessage());
            jmsException.setLinkedException(e);
            LOG.error(e.getMessage(), e);
        }
      }
    }
    

    And inject it like this:

      <bean id="messageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="concurrentConsumers" value="#{properties.concurrentListeners}"/>
    
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="queue"/>
        <property name="messageListener" ref="messageListener"/>
        <property name="sessionTransacted" value="true"/>
    
    </bean>
    
    <bean id="messageListener" class="com.yourproject.transport.CustomWebServiceMessageListener">
        <property name="messageFactory" ref="saajMessageFactory"/>
        <property name="messageReceiver" ref="messageDispatcher"/>
    </bean>
    
    0 讨论(0)
  • 2020-12-19 05:17

    I remember running into this kind of problem when working with spring-security. I also had issues with my exception resolver not being invoked under some condititions.

    The problem then was that there is a filter chain that handles each request. The code that invokes the exception resolvers is a filter in this chain, but it is close to the end of the chain. Hence, if an exception ocurred somewhere within the filters before the exception resolver invoking filter, my resolver would never be invoked.

    I am guessing you suffer from something similar here, where envelope parse errors happen before the exception resolver invoking filter. If that is the case, you will have to resort to some other way of handling those exceptions, for example a vanilla servlet filter.

    0 讨论(0)
  • 2020-12-19 05:19

    I think what @thierry-dimitri-roy explained is correct, however I had a lot of issues actually implementing it. For example, just wrapping the handleconnection method isn't enough due to noendpointfoundexception not being thrown. As this is more general issue of handling exceptions nicely, I'm putting my code here to ease the pain for future generations. This is tested with spring-ws 2.1.3 and JBoss AS7.

    My message handler converts all problems to soap faults with response code 200.

    package fi.eis.applications.spring.soap.server.transport.http;
    
    import java.io.IOException;
    import java.net.URISyntaxException;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.lang3.StringEscapeUtils;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.core.Ordered;
    import org.springframework.web.servlet.HandlerAdapter;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.ws.FaultAwareWebServiceMessage;
    import org.springframework.ws.InvalidXmlException;
    import org.springframework.ws.NoEndpointFoundException;
    import org.springframework.ws.WebServiceMessage;
    import org.springframework.ws.WebServiceMessageFactory;
    import org.springframework.ws.context.DefaultMessageContext;
    import org.springframework.ws.context.MessageContext;
    import org.springframework.ws.support.DefaultStrategiesHelper;
    import org.springframework.ws.transport.EndpointAwareWebServiceConnection;
    import org.springframework.ws.transport.FaultAwareWebServiceConnection;
    import org.springframework.ws.transport.WebServiceConnection;
    import org.springframework.ws.transport.WebServiceMessageReceiver;
    import org.springframework.ws.transport.context.DefaultTransportContext;
    import org.springframework.ws.transport.context.TransportContext;
    import org.springframework.ws.transport.context.TransportContextHolder;
    import org.springframework.ws.transport.http.HttpServletConnection;
    import org.springframework.ws.transport.http.HttpTransportConstants;
    import org.springframework.ws.transport.http.MessageDispatcherServlet;
    import org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter;
    import org.springframework.ws.transport.support.TransportUtils;
    
    /**
     * Adapter to map XML parsing and other low-level errors to SOAP faults instead of
     * server standard error pages. Also, this class will always use return code HTTP_OK
     * (status 200) to requests, even if there are errors.
     *  
     */
    public class MyWebServiceMessageReceiverHandlerAdapter
        extends WebServiceMessageReceiverHandlerAdapter
        implements HandlerAdapter, Ordered, InitializingBean, ApplicationContextAware {
    
        private ApplicationContext context;
        @Override
        public void setApplicationContext(ApplicationContext applicationContext)
                throws BeansException {
            this.context = applicationContext;
        }
    
        @Override
        public void afterPropertiesSet() {
            DefaultStrategiesHelper defaultStrategiesHelper = new DefaultStrategiesHelper(MessageDispatcherServlet.class);
            WebServiceMessageFactory factory = defaultStrategiesHelper
                    .getDefaultStrategy(WebServiceMessageFactory.class, context);
            setMessageFactory(factory);
        }
    
        public ModelAndView handle(HttpServletRequest httpServletRequest,
                                   HttpServletResponse httpServletResponse,
                                   Object handler) throws Exception {
            if (HttpTransportConstants.METHOD_POST.equals(httpServletRequest.getMethod())) {
                WebServiceConnection connection = new MyWebServiceConnection(httpServletRequest, httpServletResponse);
                try {
                    overriddenHandleConnection(connection, (WebServiceMessageReceiver) handler);
                } catch (InvalidXmlException ex) {
                    handleInvalidXmlException(httpServletRequest, httpServletResponse, handler, ex);
                } catch (Exception ex) {
                    handleGeneralException(httpServletRequest, httpServletResponse, handler, ex);
                }
            }
            else {
                handleNonPostMethod(httpServletRequest, httpServletResponse, handler);
            }
            return null;
        }
    
    
        /**
         * Overridden version of handleConnection from WebServiceMessageReceiverObjectSupport to be able to handle
         * missing endpoint ourselves. That method is final, so we need to use another method here.
         * 
         * This has been reported as https://jira.springsource.org/browse/SWS-850
         * 
         * @param connection
         * @param receiver
         * @throws Exception
         */
        protected void overriddenHandleConnection(WebServiceConnection connection, WebServiceMessageReceiver receiver)
                throws Exception {
            logUri(connection);
            TransportContext previousTransportContext = TransportContextHolder.getTransportContext();
            TransportContextHolder.setTransportContext(new DefaultTransportContext(connection));
    
            try {
                WebServiceMessage request = connection.receive(getMessageFactory());
                MessageContext messageContext = new DefaultMessageContext(request, getMessageFactory());
                receiver.receive(messageContext);
                if (messageContext.hasResponse()) {
                    WebServiceMessage response = messageContext.getResponse();
                    if (response instanceof FaultAwareWebServiceMessage &&
                            connection instanceof FaultAwareWebServiceConnection) {
                        FaultAwareWebServiceMessage faultResponse = (FaultAwareWebServiceMessage) response;
                        FaultAwareWebServiceConnection faultConnection = (FaultAwareWebServiceConnection) connection;
                        faultConnection.setFault(faultResponse.hasFault());
                    }
                    connection.send(messageContext.getResponse());
                }
            }
            catch (NoEndpointFoundException ex) {
                if (connection instanceof EndpointAwareWebServiceConnection) {
                    ((EndpointAwareWebServiceConnection) connection).endpointNotFound();
                }
                throw ex;
            }
            finally {
                TransportUtils.closeConnection(connection);
                TransportContextHolder.setTransportContext(previousTransportContext);
            }
        }
    
        private void logUri(WebServiceConnection connection) {
            if (logger.isDebugEnabled()) {
                try {
                    logger.debug("Accepting incoming [" + connection + "] at [" + connection.getUri() + "]");
                }
                catch (URISyntaxException e) {
                    // ignore
                }
            }
        }
    
    
    
        private void handleGeneralException(
                HttpServletRequest httpServletRequest,
                HttpServletResponse response, Object handler,
                Exception ex) throws IOException {
            writeErrorResponseWithMessage(response, ex.getClass().getName() + ": " + ex.getMessage());
        }
    
        /**
         * By default, sets SC_BAD_REQUEST as response in Spring, so overwritten to
         * provide HTTP_OK and reasonable SOAP fault response.
         */
        protected void handleInvalidXmlException(
                HttpServletRequest httpServletRequest,
                HttpServletResponse response, Object handler, InvalidXmlException ex)
                throws IOException {
            writeErrorResponseWithMessage(response, ex.getClass().getName() + ": " + ex.getMessage());
        }
    
        /**
         * By default, sets SC_METHOD_NOT_ALLOWED as response in Spring, so overwritten to
         * provide HTTP_OK and reasonable SOAP fault response.
         */
        protected void handleNonPostMethod(HttpServletRequest httpServletRequest,
                                           HttpServletResponse response,
                                           Object handler) throws Exception {
            writeErrorResponseWithMessage(response, "HTTP Method not allowed");
        }
    
        private void writeErrorResponseWithMessage(HttpServletResponse response, String message)
                throws IOException {
            String errorXml = String.format(
                     "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
                    +"    <SOAP-ENV:Header/>"
                    +"    <SOAP-ENV:Body>"
                    +"        <SOAP-ENV:Fault>"
                    +"            <faultcode>SOAP-ENV:Client</faultcode>"
                    +"            <faultstring xml:lang=\"en\">%s</faultstring>"
                    +"        </SOAP-ENV:Fault>"
                    +"    </SOAP-ENV:Body>"
                    +"</SOAP-ENV:Envelope>",
                    StringEscapeUtils.escapeXml(message)
                    );
    
            response.setStatus(HttpServletResponse.SC_OK);
            response.setContentType("text/xml");
            response.getWriter().write(errorXml);
            response.getWriter().flush();
        }
        @Override
        public int getOrder() {
            return 1;
        }
    
        /**
         * This class is needed as org.springframework.ws.transport.http.HttpServletConnection will throw an
         * exception if it is used outside Spring framework files. However, extending it and using the same
         * implementation seems to be fine.
         *
         */
        static class MyWebServiceConnection extends HttpServletConnection {
            protected MyWebServiceConnection(HttpServletRequest httpServletRequest,
                    HttpServletResponse httpServletResponse) {
                super(httpServletRequest, httpServletResponse);
            }
        }    
    }
    

    This needs to be also configured correctly. This is the thing that is needed on spring context:

    <!-- 'messageReceiverHandlerAdapter' is a magic name spring-ws
         org.springframework.ws.transport.http.MessageDispatcherServlet
         will bind to -->
    <bean id="messageReceiverHandlerAdapter"
        class="fi.eis.applications.spring.soap.server.transport.http.MyWebServiceMessageReceiverHandlerAdapter">
    </bean>
    
    0 讨论(0)
  • 2020-12-19 05:23

    The solution I could come up with is to override the doService method in the MessageDispatcherServlet and capture the exception, then render a custom response. This might not be the best solution for you, but it works for me, hope this helps!

    public class CustomMessageDispatcherServlet extends MessageDispatcherServlet {
    
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            super.doService(request, response);
        } catch (Exception e) {
            String error = e.getMessage();
            String errorXml = "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
                                    "<SOAP-ENV:Header/>" +
                                        "<SOAP-ENV:Body>" +
                                            "<SOAP-ENV:Fault>" +
                                                "<faultcode>SOAP-ENV:Client</faultcode>" +
                                                "<faultstring xml:lang=\"en\"></faultstring>" + error +
                                            "</SOAP-ENV:Fault>" +
                                        "</SOAP-ENV:Body>" +
                                    "</SOAP-ENV:Envelope>";
            response.setStatus(HttpServletResponse.SC_OK);
            response.setContentType("text/xml");
            response.getWriter().write(errorXml);
            response.getWriter().flush();
        }
    }
    

    }

    You might want to catch only the exceptions you want to handle.

    Replace org.springframework.ws.transport.http.MessageDispatcherServlet with your CustomMessageDispatcherServlet in the web.xml file.

    <servlet>
        <servlet-name>web-services</servlet-name>
        <servlet-class>CustomMessageDispatcherServlet</servlet-class>
    </servlet>
    
    0 讨论(0)
提交回复
热议问题