gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-04 09:18:49 34 4
gpt4 key购买 nike

我想知道如果 @Controller 方法返回 Callable 接口(interface),如何从请求正文中读取过滤器中的响应。

我的过滤器看起来像这样。响应始终为空。有什么办法解决这个问题吗?仅使用 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 到达调度程序 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()

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

关于java - 读取异步请求中的响应正文,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58607271/

34 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com