读取异步请求中的响应正文 [英] Read response body in async request

查看:80
本文介绍了读取异步请求中的响应正文的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道如果@Controller方法返回Callable接口,如何从请求主体中读取过滤器中的响应.

I am wondering how to read response in filter from request body if @Controller method returns Callable interface.

我的过滤器看起来像这样.响应始终为空.有什么解决办法吗?只能使用AsyncListener允许吗?

My filter looks like this. Response is always empty. Any solution to this? Is this allowed only using 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本身开始异步处理,并且在实际处理请求之前退出过滤器.

The problem of Callable is that the dispatcher servlet itself starts async processing and the filter is exited before actually processing of a request.

当Callable到达调度程序servlet时,它通过释放所有过滤器来使容器线程从池中释放出来(过滤器基本上完成了它们的工作).当Callable产生结果时,将使用相同的请求再次调用分派器servlet,并且从Callable返回的数据将立即满足响应.这由类型为 AsyncTaskManager 的请求属性处理,该属性包含有关异步请求处理的一些信息.可以使用 Filter HandlerInterceptor 进行测试. Filter 仅执行一次,而 HandlerInterceptor 仅执行两次(原始请求和Callable完成工作后的请求)

When Callable arrives to dispatcher servlet, it frees container thread from pool by releasing all filters (filters basically finish their work). When Callable produces results, the dispatcher servlet is called again with the same request and the response is immidiately fulfilled by the data return from Callable. This is handled by request attribute of type AsyncTaskManager which holds some information about processing of async request. This can be tested with Filter and HandlerInterceptor. Filter is executed only once but HandlerInterceptor is executed twice (original request and the request after Callable completes its job)

当您需要读取请求和响应时,解决方案之一是像这样重写dispatcherServlet:

When you need to read request and response, one of the solution is to rewrite dispatcherServlet like 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(您必须将一些数据作为请求属性传递):

This way you ensure that you can read request and response multiple times. Other thing is to add HandlerInterceptor like this (you have to pass some data as request attribute):

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

afterCompletion method is called only once after async request has been completely processed. preHandle is called exactly twice so you have to check existance of your attribute. In afterCompletion, the response from the call is already present and if you do want to replace it, you should call response.resetBuffer().

这是一种可能的解决方案,可能会有更好的方法.

This is one possible solution and there could be better ways.

这篇关于读取异步请求中的响应正文的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆