Как вернуть пользовательскую ошибку SOAP из Spring Boot Endpoint Service? - PullRequest
0 голосов
/ 07 декабря 2018

Я установил приложение Webservice, которое получает и регистрирует SOAP-запросы от третьих лиц.После регистрации определенный ответ должен быть возвращен.Это работает без проблем, если нет ошибок и полученные запросы SOAP соответствуют WSDL.К сожалению, третья сторона также ожидает правильного ответа SOAP при отправке недопустимого содержимого или даже случайных данных.

В случае, если запрос содержит случайные данные (например, «zewrzasjkfklj»), моя служба возвращает неверный запрос HTTP / 400 спустое тело.Если запрос содержит x XML, но НЕ Soap (например, ""), служба возвращает ошибку HTTP / 500 Server с телом 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) {
...}

(но этот метод никогда не достигается в случае недопустимых запросов).

Я уже пытался внедрить / предоставить Перехватчики, Диспетчеры, ErrorMappers, ... но результаты не изменились.Похоже, что в последнем случае (допустимый XML, но без SOAP) происходит сбой при попытке извлечь конверт в SOAPPartImpl.lookForEnvelope () и происходит сбой с генерированием нового SOAPExceptionImpl («Невозможно создать конверт из данного источника, поскольку корневой элемент не являетсяназванный \ "Конверт \" ");Точка останова на этой ошибке дает следующий стек:

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 по умолчанию (или ответ HTML, которыйсодержит текстовое содержимое сообщения SOAP), если запрос даже не достиг логики обработки SOAP.

1 Ответ

0 голосов
/ 12 декабря 2018

В конце концов, это должна была быть комбинация нескольких хуков, так как, похоже, не было ни единого места или конфигурации, где бы все ошибки / проблемы, связанные с проходом конечной точки, И позволяли генерировать пользовательский ответ.

Следующее было моим решением, которое я в конце концов нашел:

Основным местом, где я мог связать генерацию настроенного ответа, является настроенный 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;
    }
}
...

, который настроен в моем классе конфигурации (который теперь должен расширяться от WsConfigurerAdapter) через

...
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);
    }
...

Во-вторых, это теперь выброшенное исключение CustomSoapValidationException все равно приведет к стандартной ошибке SOAP в логике разрешения конечной точкиВот почему мы также создаем пользовательский EndpointExceptionResolver.Это вызывается во время обработки Исключения и снова модифицирует нашу ошибку проверки перехватчика в «живое» исключение, которое затем может отскочить обратно стека вызовов в наш CustomSoapErrorMessageDispatcherServlet с первого шага.

...
// 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;
    }
}
...

Это НЕ требует дополнительной настройки, но теперь автоматически выбирается диспетчером сообщений Spring Boot.

Со всеми этими шагами все возникающие ошибки / исключения / сбои / ... заканчиваютсятак или иначе в нашем CustomSoapErrorMessageDispatcherServlet.doService (), где мы выбираем исключения или исследуем еще не отправленный HttpServletResponse и можем создать собственный SOAP-выглядящий ответ для удовлетворения наших требований.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...