[英]Read response body in async request
我想知道如果@Controller 方法返回 Callable 接口,如何從請求正文中讀取過濾器中的響應。
我的過濾器看起來像這樣。 響應總是空的。 有什么解決辦法嗎? 這是否只允許使用 AsyncListener?
@Component
public class ResposeBodyXmlValidator extends OncePerRequestFilter {
private final XmlUtils xmlUtils;
private final Resource xsdResource;
public ResposeBodyXmlValidator(
XmlUtils xmlUtils,
@Value("classpath:xsd/some.xsd") Resource xsdResource
) {
this.xmlUtils = xmlUtils;
this.xsdResource = xsdResource;
}
@Override
protected void doFilterInternal(
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain
) throws ServletException, IOException {
ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(httpServletResponse);
doFilter(httpServletRequest, response, filterChain);
if (MediaType.APPLICATION_XML.getType().equals(response.getContentType())) {
try {
xmlUtils.validate(new String(response.getContentAsByteArray(), response.getCharacterEncoding()), xsdResource.getInputStream());
} catch (IOException | SAXException e) {
String exceptionString = String.format("Chyba při volání %s\nNevalidní výstupní XML: %s",
httpServletRequest.getRemoteAddr(),
e.getMessage());
response.setContentType(MediaType.TEXT_PLAIN_VALUE + "; charset=UTF-8");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().print(exceptionString);
}
}
response.copyBodyToResponse(); // I found this needs to be added at the end of the filter
}
}
Callable 的問題是調度程序 servlet 本身啟動異步處理,並且在實際處理請求之前退出過濾器。
當 Callable 到達 dispatcher servlet 時,它通過釋放所有過濾器(過濾器基本上完成它們的工作)從池中釋放容器線程。 當 Callable 產生結果時,調度器 servlet 會再次使用相同的請求調用,並且響應由 Callable 返回的數據立即完成。 這由AsyncTaskManager
類型的請求屬性處理,該屬性包含有關處理異步請求的一些信息。 這可以使用Filter
和HandlerInterceptor
進行測試。 Filter
只執行一次但HandlerInterceptor
執行了兩次(原始請求和 Callable 完成后的請求)
當您需要讀取請求和響應時,一種解決方案是像這樣重寫 dispatcherServlet:
@Bean
@Primary
public DispatcherServlet dispatcherServlet(WebApplicationContext context) {
return new DispatcherServlet(context) {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
super.service(requestWrapper, responseWrapper);
responseWrapper.copyBodyToResponse();
}
};
}
這樣可以確保您可以多次讀取請求和響應。 另一件事是像這樣添加 HandlerInterceptor (您必須將一些數據作為請求屬性傳遞):
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
Object asyncRequestData = request.getAttribute(LOGGER_FILTER_ATTRIBUTE);
if (asyncRequestData == null) {
request.setAttribute(LOGGER_FILTER_ATTRIBUTE, new AsyncRequestData(request));
}
return true;
}
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex
) throws Exception {
Object asyncRequestData = request.getAttribute(LOGGER_FILTER_ATTRIBUTE);
if (asyncRequestData != null && response instanceof ContentCachingResponseWrapper) {
log(request, (ContentCachingResponseWrapper) response, (AsyncRequestData) asyncRequestData);
}
}
afterCompletion方法僅在異步請求完全處理后調用一次。 preHandle 恰好被調用了兩次,所以你必須檢查你的屬性是否存在。 在afterCompletion中,調用的響應已經存在,如果你確實想替換它,你應該調用response.resetBuffer()
。
這是一種可能的解決方案,並且可能有更好的方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.