猿问

读取异步请求中的响应正文

我想知道如果 @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

    }

}


摇曳的蔷薇
浏览 114回答 1
1回答

德玛西亚99

Callable 的问题在于调度程序 servlet 本身会启动异步处理,并且过滤器会在实际处理请求之前退出。当 Callable 到达调度程序 servlet 时,它通过释放所有过滤器(过滤器基本上完成其工作)来从池中释放容器线程。当 Callable 产生结果时,会使用相同的请求再次调用调度程序 servlet,并且响应会立即由 Callable 返回的数据完成。这是由类型的 request 属性处理的,AsyncTaskManager该属性保存有关异步请求处理的一些信息。这可以用Filter和进行测试HandlerInterceptor。Filter只执行一次但HandlerInterceptor执行了两次(原始请求和Callable完成工作后的请求)当您需要读取请求和响应时,解决方案之一是像这样重写dispatcherServlet:@Bean@Primarypublic 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().这是一种可能的解决方案,并且可能有更好的方法。
随时随地看视频慕课网APP

相关分类

Java
我要回答