Spring MVC - 获取HttpServletResponse的Body

6

因为两天后我仍然无法弄清如何在HandlerInterceptorAdapter中打印HttpServletResponse的主体,所以我要再问一遍:)

使用HttpServletRequest,我可以轻松地做出类似于request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));的事情,然后我就有了完整的主体,但如何使用HttpServletResponse呢?

我在StackOverflow上找到了很多关于这个问题的问答,但没有一个看起来能够解决问题。

这是处理器:

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
        throws Exception {
    //how to print here the "response" by using the "response" parameter
    super.afterCompletion(request, response, handler, ex);
}

这个答案这个问题很相似,但是它们使用的是ServletResponse而不是HttpServletResponse,并且还涉及到FilterChain,而我在我的afterCompletion处理程序中没有。即使这个看起来最完整的答案在我的情况下也不适用(我想)。请问有人能提供一个带有HttpServletResponse的简单序列化示例吗?


嗨,Faraz Durrani,谢谢您的回复。我正在寻找如何打印正文响应,而不是请求。 - Andrea Grimandi
3个回答

5
在深入搜索后,我发现ResponseBodyAdvice可能适合我的目的。因此,在StackOverflow上寻找示例时,我找到了这位有着相同问题的人,他需要操作Object body
这是我最终的解决方案,以实现我在这里所写的内容。
@ControllerAdvice
public class CSRFHandler implements ResponseBodyAdvice<Object> {

    @Value("${security.csrf.enabled}")
    private String csrfEnabled;

    @Value("${security.csrf.headerName}")
    private String csrfHeaderName;

    @Value("${security.csrf.salt}")
    private String salt;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
            ServerHttpResponse response) {

        if (new Boolean(csrfEnabled).booleanValue()) {
            String csrfValue = SecureUtil.buildCsrfValue(salt, StringUtil.toJson(body));
            response.getHeaders().add(csrfHeaderName, csrfValue);
        }

        return body;
    }

}

3
简单的答案是:“你无法在处理程序拦截器中做到这一点”。官方文档中明确说明了这一点:
“HandlerInterceptor基本上类似于Servlet过滤器,但与后者相比,它仅允许自定义预处理,并提供禁止执行处理程序本身和自定义后处理的选项。过滤器更加强大,例如它们允许交换沿着链传递的请求和响应对象。请注意,过滤器在web.xml中配置,HandlerInterceptor在应用程序上下文中配置。”
作为一个基本的指导方针,细粒度的处理程序相关预处理任务是HandlerInterceptor实现的候选项,特别是因素化的常见处理程序代码和授权检查。另一方面,过滤器非常适合处理请求内容和视图内容,如多部分表单和GZIP压缩。这通常表现在需要将过滤器映射到特定内容类型(例如图片)或所有请求时。
因此,我建议您查看基于过滤器的解决方案,就像您所指出的那样。您可能会对ContentCachingResponseWrapper产生空响应感兴趣,它似乎可以用最少的编码实现您想要的功能。但是一旦您开始使用过滤器,您链接到问题中的任何被广泛接受的答案都可能会完成工作。

嗨GPI,感谢您的有用回答。很明显为什么那不是一个好的方式,但尝试OncePerRequestFilter或Filter似乎在控制器执行后不调用doFilterInternal或doFilter。我应该使用什么? - Andrea Grimandi
我猜测如果你的过滤器方法没有被调用,那很可能是因为Spring没有捕捉到它们并部署它们。所以你应该阅读手册,看看“如何注册过滤器”。如果你使用纯SpringBoot应用程序和嵌入Spring上下文的Servlet Web应用程序,做法是不同的......所以请阅读相关文档。 - GPI

0
我最终用 Kotlin 写出了以下代码:
@Bean
open fun logFilter(): CommonsRequestLoggingFilter {
    val filter = InfoRequestLoggingFilter()
    filter.setIncludeQueryString(true)
    filter.setIncludePayload(true)
    filter.setMaxPayloadLength(10000)
    filter.isIncludeHeaders = false
    return filter
}

import org.apache.commons.io.output.TeeOutputStream
import org.springframework.mock.web.DelegatingServletOutputStream
import org.springframework.web.filter.CommonsRequestLoggingFilter
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.PrintStream
import java.io.PrintWriter
import javax.servlet.FilterChain
import javax.servlet.ServletOutputStream
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.servlet.http.HttpServletResponseWrapper

class InfoRequestLoggingFilter : CommonsRequestLoggingFilter() {

    override fun beforeRequest(request: HttpServletRequest, message: String) {
        logger.info(message)
    }

    override fun afterRequest(request: HttpServletRequest, message: String) {
        // logger.info(message) - NOP, since doFilterInternal is logging it instead
    }

    override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {

        val outputStream = ByteArrayOutputStream()
        val printStream = PrintStream(outputStream)

        filterChain.doFilter(request, object : HttpServletResponseWrapper(response) {
            @Throws(IOException::class)
            override fun getOutputStream(): ServletOutputStream {
                return DelegatingServletOutputStream(TeeOutputStream(super.getOutputStream(), printStream)
                )
            }

            @Throws(IOException::class)
            override fun getWriter(): PrintWriter {
                return PrintWriter(DelegatingServletOutputStream(TeeOutputStream(super.getOutputStream(), printStream))
                )
            }
        })

        logger.info(String.format("%s ;status=%s ;payload=%s", createMessage(request, "", ""), response.status, outputStream.toString()))
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接