代码之家  ›  专栏  ›  技术社区  ›  Kosi2801

如何从Spring引导端点服务返回自定义SOAP错误?

  •  0
  • Kosi2801  · 技术社区  · 6 年前

    我已经设置了一个WebService应用程序,它接收并只记录来自第三方的SOAP请求。记录后,必须返回定义的响应。如果没有错误,并且收到的SOAP请求与WSDL匹配,那么这将毫无问题地工作。 不幸的是,当第三方发送无效内容甚至随机数据时,它也期望得到一个正确的SOAP响应。

    如果请求包含随机数据(例如“zewrzasjkfklj”),我的服务将返回一个HTTP/400错误请求,请求的主体为空。 如果请求包含XML而不是SOAP(例如“”),那么服务将返回HTTP/500服务器错误和JSON主体。

    {"timestamp":"2018-12-06T16:16:29.375+0000","status":500,"error":"Internal Server Error","message":"Could not create message from InputStream: Unable to create envelope from given source: ; nested exception is com.sun.xml.internal.messaging.saaj.SOAPExceptionImpl: Unable to create envelope from given source: ","path":"/NotificationServicePort"}
    

    这对我来说尤其令人困惑,因为我在与JSON相关的项目中的任何地方都没有跟踪或配置。

    端点是一个用@endpoint注释的类,它实现

    ...    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "notify")
        @ResponsePayload
        public JAXBElement<NotifyResponse> notify(@RequestPayload Notify request) {
    ...}
    

    (但在无效请求的情况下永远无法达到此方法)。

    我已经尝试实现/提供拦截器、调度器、错误映射器……但结果没有改变。 在后一种情况下(有效的XML,但没有SOAP),当尝试在soappartimpl.lookforenvelope()提取信封时,它似乎失败了,并使用 throw new soapexceptionimpl(“无法从给定源创建信封,因为根元素没有命名为\“envelope\”); 该错误处的断点提供以下堆栈:

    lookForEnvelope:153, SOAPPartImpl (com.sun.xml.internal.messaging.saaj.soap)
    getEnvelope:121, SOAPPartImpl (com.sun.xml.internal.messaging.saaj.soap)
    createEnvelope:110, EnvelopeFactory (com.sun.xml.internal.messaging.saaj.soap)
    createEnvelopeFromSource:69, SOAPPart1_1Impl (com.sun.xml.internal.messaging.saaj.soap.ver1_1)
    getEnvelope:128, SOAPPartImpl (com.sun.xml.internal.messaging.saaj.soap)
    createWebServiceMessage:189, SaajSoapMessageFactory (org.springframework.ws.soap.saaj)
    createWebServiceMessage:60, SaajSoapMessageFactory (org.springframework.ws.soap.saaj)
    receive:92, AbstractWebServiceConnection (org.springframework.ws.transport)
    handleConnection:87, WebServiceMessageReceiverObjectSupport (org.springframework.ws.transport.support)
    handle:61, WebServiceMessageReceiverHandlerAdapter (org.springframework.ws.transport.http)
    doService:293, MessageDispatcherServlet (org.springframework.ws.transport.http)
    processRequest:974, FrameworkServlet (org.springframework.web.servlet)
    doPost:877, FrameworkServlet (org.springframework.web.servlet)
    service:661, HttpServlet (javax.servlet.http)
    service:851, FrameworkServlet (org.springframework.web.servlet)
    service:742, HttpServlet (javax.servlet.http)
    internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilterInternal:246, AbstractRequestLoggingFilter (org.springframework.web.filter)
    doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    filterAndRecordMetrics:158, WebMvcMetricsFilter (org.springframework.boot.actuate.metrics.web.servlet)
    filterAndRecordMetrics:126, WebMvcMetricsFilter (org.springframework.boot.actuate.metrics.web.servlet)
    doFilterInternal:111, WebMvcMetricsFilter (org.springframework.boot.actuate.metrics.web.servlet)
    doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilterInternal:90, HttpTraceFilter (org.springframework.boot.actuate.web.trace.servlet)
    doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:320, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    invoke:127, FilterSecurityInterceptor (org.springframework.security.web.access.intercept)
    doFilter:91, FilterSecurityInterceptor (org.springframework.security.web.access.intercept)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilter:119, ExceptionTranslationFilter (org.springframework.security.web.access)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilter:137, SessionManagementFilter (org.springframework.security.web.session)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilter:111, AnonymousAuthenticationFilter (org.springframework.security.web.authentication)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilter:170, SecurityContextHolderAwareRequestFilter (org.springframework.security.web.servletapi)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilter:63, RequestCacheAwareFilter (org.springframework.security.web.savedrequest)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilterInternal:158, BasicAuthenticationFilter (org.springframework.security.web.authentication.www)
    doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilter:116, LogoutFilter (org.springframework.security.web.authentication.logout)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilterInternal:66, HeaderWriterFilter (org.springframework.security.web.header)
    doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilter:105, SecurityContextPersistenceFilter (org.springframework.security.web.context)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilterInternal:56, WebAsyncManagerIntegrationFilter (org.springframework.security.web.context.request.async)
    doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
    doFilter:334, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
    doFilterInternal:215, FilterChainProxy (org.springframework.security.web)
    doFilter:178, FilterChainProxy (org.springframework.security.web)
    invokeDelegate:357, DelegatingFilterProxy (org.springframework.web.filter)
    doFilter:270, DelegatingFilterProxy (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilterInternal:99, RequestContextFilter (org.springframework.web.filter)
    doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilterInternal:109, HttpPutFormContentFilter (org.springframework.web.filter)
    doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilterInternal:93, HiddenHttpMethodFilter (org.springframework.web.filter)
    doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    doFilterInternal:200, CharacterEncodingFilter (org.springframework.web.filter)
    doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
    internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
    doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
    invoke:198, StandardWrapperValve (org.apache.catalina.core)
    invoke:96, StandardContextValve (org.apache.catalina.core)
    invoke:496, AuthenticatorBase (org.apache.catalina.authenticator)
    invoke:140, StandardHostValve (org.apache.catalina.core)
    invoke:81, ErrorReportValve (org.apache.catalina.valves)
    invoke:87, StandardEngineValve (org.apache.catalina.core)
    service:342, CoyoteAdapter (org.apache.catalina.connector)
    service:803, Http11Processor (org.apache.coyote.http11)
    process:66, AbstractProcessorLight (org.apache.coyote)
    process:790, AbstractProtocol$ConnectionHandler (org.apache.coyote)
    doRun:1468, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
    run:49, SocketProcessorBase (org.apache.tomcat.util.net)
    runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
    run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
    run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
    run:748, Thread (java.lang)
    

    如果请求甚至没有到达SOAP处理逻辑,我将非常感谢任何提示或进一步的建议,在这些提示或建议中,我可以找到关于如何设置默认SOAP响应(或具有SOAP消息文本内容的HTML响应)的更多信息。

    1 回复  |  直到 6 年前
        1
  •  0
  •   Kosi2801    6 年前

    最后,它必须是多个钩子的组合,因为在与端点传递相关的所有错误/问题以及允许生成自定义响应的地方,似乎没有单一的点或配置可用。

    以下是我最终提出的解决方案:

    我可以捆绑生成自定义响应的主要位置是自定义的MessageDispatcherServlet:

    ...
    // this custom dispatcher is responsible for sending back a faked "SOAP" like response upon any type of
    // misformatted request or error.
    @Component
    public class CustomSoapErrorMessageDispatcherServlet extends MessageDispatcherServlet {
    
        @Override
        protected void doService(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
                throws Exception {
            Exception thrownException = null;
    
            try {
                super.doService(httpServletRequest, httpServletResponse);
            } catch (CustomSoapValidationException | SoapMessageCreationException e) {
                LOG.warn("Processing resulted in exception: " + e.getMessage()); //
                thrownException = e;
                httpServletResponse.setStatus(400);
            } catch (Exception e) {
                LOG.warn("Processing resulted in generic exception: " + e.getMessage()); //
                thrownException = e;
                httpServletResponse.setStatus(500);
            }
    
            int responseStatus = httpServletResponse.getStatus();
    
            // Response in HTTP OK Range? Do nothing.
            if (responseStatus >= 200 && responseStatus <= 299) {
                return;
            }
    
            /*
            In any case of any error send a SOAP-like response. 
             */
            String errorCode, errorMessage;
    
            // failure during SOAP interpretion? ie. XML received but not SOAP or invalid structure, ....
            if(thrownException instanceof SoapMessageException) {
                errorCode = "110";
                errorMessage = "Generic SOAP Exception: " + thrownException.getMessage();
            }
            // did our structure validation fail?
            else if (thrownException instanceof CustomSoapValidationException) {
                errorCode = "110";
                errorMessage = "Structure error in request: " + thrownException.getMessage();
            }
            // another exception unrelated to Soap Processing?
            else if (thrownException != null) {
                errorCode = "999";
                errorMessage = "Internal error: " + thrownException.getMessage();
            }
            // generic internal error, but not throwing exception?
            else if (responseStatus >= 400 && responseStatus <= 499) {
                errorCode = String.valueOf(responseStatus);
                errorMessage = "Generic unspecific request processing error.";
            }
            // something completely unexpected
            else {
                errorCode = "500";
                errorMessage = "Unexpected condition.";
            }
    
            String responseBody = generateSoapErrorContent(errorCode, errorMessage);
            ServletOutputStream outputStream = httpServletResponse.getOutputStream();
            outputStream.print(responseBody);
            outputStream.flush();
        }   
        ...
    }
    ...
    

    通过我的配置类

    ...
        @Autowired
        private CustomSoapErrorMessageDispatcherServlet dispatcherServlet;
    
        @Bean
        public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
            dispatcherServlet.setApplicationContext(applicationContext);
            dispatcherServlet.setTransformWsdlLocations(true);
            return new ServletRegistrationBean(dispatcherServlet, "/NotificationServicePort/*");
        }
    ...
    

    仅此自定义调度器就能够捕获包含(有效和无效)XML但不完全是SOAP或包含随机数据的请求。为了覆盖无效的SOAP请求,还需要执行更多的步骤。

    首先是一个自定义拦截器,它执行模式验证并抛出一个自定义异常(而不是立即响应一个SOAP错误,如payloadvalidatingInterceptor):

    ...
    public class CustomValidatingInterceptor extends PayloadValidatingInterceptor {
    
        @Override
        protected boolean handleRequestValidationErrors(MessageContext messageContext, SAXParseException[] errors)
                throws TransformerException {
    
            // if any validation errors, convert them to a string and throw on as Exception to be handled by CustomSoapErrorMessageDispatcherServlet
            if (errors.length > 0) {
                String validationErrorsString = Arrays.stream(errors)
                        .map(error -> "[" + error.getLineNumber() + "," + error.getColumnNumber() + "]: " + error.getMessage())
                        .collect(Collectors.joining(" -- "));
                throw new CustomSoapValidationException("Validation Errors: " + validationErrorsString);
            }
            return true;
        }
    }
    ...
    

    它在我的配置类中配置(现在必须从wsconfigureAdapter扩展)通过

    ...
    public class WebServiceConfig extends WsConfigurerAdapter {
    ...
        @Override
        public void addInterceptors(List<EndpointInterceptor> interceptors) {
            // validate requests and responses
            // cannot use PayloadValidatingInterceptor because that one would generate an unwanted/unavoidable SoapFault
            CustomValidatingInterceptor validatingInterceptor = new CustomValidatingInterceptor();
            validatingInterceptor.setValidateRequest(true);
            validatingInterceptor.setValidateResponse(false);
            validatingInterceptor.setXsdSchema(customApiSchema());
            interceptors.add(validatingInterceptor);
        }
    ...
    

    其次,现在抛出的customsapvalidationexception仍然会导致端点解析逻辑中出现标准SOAP错误,这就是为什么我们还要创建一个自定义的endpointexceptionresolver。这是在异常处理期间调用的,并将拦截器验证错误再次修改为“活动”异常,然后可以从第一步将调用堆栈弹回到CustomSapErrorMessageDispatcherServlet中。

    ...
    // class is automatically picked up by MessageDispatcher during request handling when an exception occurs after dispatching
    @Component
    public class CustomizedSoapFaultDefinitionExceptionResolver implements EndpointExceptionResolver {
        public boolean resolveException(MessageContext messageContext, Object endpoint, Exception ex) {
            if (ex instanceof CustomSoapValidationException) {
                throw (CustomSoapValidationException) ex;
            }
            return false;
        }
    }
    ...
    

    这不需要额外的配置,但现在由SpringBootMessageDispatcher自动获取。

    所有这些步骤都会发生错误/异常/故障。在customsapErrorMessageDispatcherservlet.doService()中以某种方式结束,在这里我们获取异常或调查尚未发送的httpServletResponse,并可以构建自定义的SOAP外观响应以满足我们的要求。