簡體   English   中英

在異步請求中讀取響應正文

[英]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類型的請求屬性處理,該屬性包含有關處理異步請求的一些信息。 這可以使用FilterHandlerInterceptor進行測試。 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM