Kos*_*801 5 soap web-services custom-error-handling spring-boot jakarta-ee
我已经设置了一个 Web 服务应用程序,它接收并仅记录来自第三方的 SOAP 请求。记录后必须返回定义的响应。如果没有错误并且接收到的 SOAP 请求与 WSDL 匹配,则此操作不会出现任何问题。不幸的是,第三方在发送无效内容甚至随机数据时也期望正确的 SOAP 响应。
如果请求包含随机数据(例如“zewrzasjkfklj”),我的服务将返回带有空正文的 HTTP/400 错误请求。如果请求包含 XML 但不包含 Soap(例如“”),则服务将返回带有 JSON 正文的 HTTP/500 服务器错误
{"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"}
Run Code Online (Sandbox Code Playgroud)
这让我特别困惑,因为我在项目中没有任何与 JSON 相关的跟踪或配置。
端点是一个用@Endpoint注释的类,它实现了
... @PayloadRoot(namespace = NAMESPACE_URI, localPart = "notify")
@ResponsePayload
public JAXBElement<NotifyResponse> notify(@RequestPayload Notify request) {
...}
Run Code Online (Sandbox Code Playgroud)
(但如果请求无效,则永远不会到达此方法)。
我已经尝试实现/提供拦截器、调度程序、ErrorMappers,...但结果没有改变。似乎在后一种情况下(有效的 XML 但没有 SOAP),尝试在 SOAPPartImpl.lookForEnvelope() 处提取信封时失败,并失败并抛出 throw new SOAPExceptionImpl("Unable to create Envelope from给定源,因为根元素不是命名为“信封”);该错误处的断点给出以下堆栈:
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)
Run Code Online (Sandbox Code Playgroud)
我将不胜感激任何提示或进一步的建议,如果请求甚至没有到达 SOAP 处理逻辑,我可以在其中找到有关如何设置默认 SOAP 响应(或具有 SOAP 消息文本内容的 HTML 响应)的更多信息。
最后,它必须是多个钩子的组合,因为似乎没有可用的单个点或配置可以使与端点相关的所有错误/问题通过并允许生成自定义响应。
以下是我最终想出的解决方案:
我可以捆绑生成自定义响应的主要位置是自定义的 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();
}
...
}
...
Run Code Online (Sandbox Code Playgroud)
我通过我的配置类激活它
...
@Autowired
private CustomSoapErrorMessageDispatcherServlet dispatcherServlet;
@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
dispatcherServlet.setApplicationContext(applicationContext);
dispatcherServlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(dispatcherServlet, "/NotificationServicePort/*");
}
...
Run Code Online (Sandbox Code Playgroud)
仅此自定义调度程序就只能捕获包含(有效和无效)XML 的请求,但不完全是 SOAP 或包含随机数据的请求。为了还覆盖无效的 SOAP 请求,还需要采取更多步骤。
首先是一个自定义拦截器,它执行模式验证并抛出自定义异常(而不是像 PayloadValidatingInterceptor 那样立即响应 SOAP 错误):
...
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;
}
}
...
Run Code Online (Sandbox Code Playgroud)
这是在我的配置类中配置的(现在必须从 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);
}
...
Run Code Online (Sandbox Code Playgroud)
其次,现在抛出的 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;
}
}
...
Run Code Online (Sandbox Code Playgroud)
这不需要额外的配置,但现在由 Spring Boot MessageDispatcher 自动获取。
通过所有这些步骤,所有发生的错误/异常/失败/...最终都会以某种方式结束在我们的 CustomSoapErrorMessageDispatcherServlet.doService() 中,我们在其中获取异常或调查尚未发送的 HttpServletResponse 并可以构建自定义 SOAP-寻找满足我们要求的回应。
| 归档时间: |
|
| 查看次数: |
13365 次 |
| 最近记录: |