为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

摇曳的蔷薇 2024-01-05 14:47:58
我想知道如果 @Controller 方法返回 Callable 接口,如何从请求正文中读取过滤器中的响应。我的过滤器看起来像这样。响应始终为空。有什么办法解决这个问题吗?仅使用 AsyncListener 才允许这样做吗?@Componentpublic 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 回答

?
德玛西亚99

TA贡献1770条经验 获得超3个赞

Callable 的问题在于调度程序 servlet 本身会启动异步处理,并且过滤器会在实际处理请求之前退出。


当 Callable 到达调度程序 servlet 时,它通过释放所有过滤器(过滤器基本上完成其工作)来从池中释放容器线程。当 Callable 产生结果时,会使用相同的请求再次调用调度程序 servlet,并且响应会立即由 Callable 返回的数据完成。这是由类型的 request 属性处理的,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().


这是一种可能的解决方案,并且可能有更好的方法。


查看完整回答
反对 回复 2024-01-05
  • 1 回答
  • 0 关注
  • 113 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信