Прочитать тело ответа в асинхронном запросе - PullRequest
1 голос
/ 29 октября 2019

Мне интересно, как прочитать ответ в фильтре из тела запроса, если метод @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
    }
}

1 Ответ

0 голосов
/ 31 октября 2019

Проблема Callable заключается в том, что сам сервлет диспетчера запускает асинхронную обработку и фильтр закрывается до фактической обработки запроса.

Когда Callable прибывает в сервлет диспетчера, он освобождает поток контейнера из пула, освобождая всефильтры (фильтры в основном заканчивают свою работу). Когда Callable дает результаты, сервлет диспетчера вызывается снова с тем же запросом, и ответ незамедлительно выполняется при возврате данных из Callable. Это обрабатывается атрибутом запроса типа AsyncTaskManager, который содержит некоторую информацию об обработке асинхронного запроса. Это может быть проверено с Filter и HandlerInterceptor. Filter выполняется только один раз, но HandlerInterceptor выполняется дважды (исходный запрос и запрос после Callable завершает свою работу)

Когда вам нужно прочитать запрос и ответ, одно из решений - переписать dispatcherServlet, какthis:

@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().

Это одно из возможных решений, и могут быть более эффективные способы. .

...