在Spring Boot Filter中获取响应主体

2025/03/22

1. 简介

在本文中,我们将探讨如何从Spring Boot过滤器中的ServletResponse中检索响应主体。

本质上,我们将定义问题,然后使用缓存响应主体的解决方案,使其在Spring Boot过滤器中可用。

2. 理解问题

首先,让我们了解我们要解决的问题。

使用Spring Boot过滤器时,从ServletResponse访问响应主体比较棘手。这是因为响应主体不易获得,因为它是在过滤器链完成执行后写入输出流的

但是有些操作,比如生成哈希签名,需要先获取响应体的内容,再发送给客户端,所以我们需要想办法读取响应体的内容。

3. 在过滤器中使用ContentCachingResponseWrapper

为了解决前面定义的问题,我们将创建一个自定义过滤器并使用Spring框架提供的ContentCachingResponseWrapper类:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    ContentCachingResponseWrapper responseCacheWrapperObject = new ContentCachingResponseWrapper((HttpServletResponse) servletResponse);
    filterChain.doFilter(servletRequest, responseCacheWrapperObject);
    byte[] responseBody = responseCacheWrapperObject.getContentAsByteArray();
    MessageDigest md5Digest = MessageDigest.getInstance("MD5");
    byte[] md5Hash = md5Digest.digest(responseBody);
    String md5HashString = DatatypeConverter.printHexBinary(md5Hash);
    responseCacheWrapperObject.getResponse().setHeader("Response-Body-MD5", md5HashString);
    // ...
}

简而言之,包装类允许我们包装HttpServletResponse来缓存响应主体内容并调用doFilter()将请求传递给下一个过滤器。

请记住,我们一定不能忘记此处的doFilter()调用。否则,传入的请求将不会进入Spring过滤器链中的下一个过滤器,应用程序将无法按我们预期的方式处理该请求。事实上,不调用doFilter()违反了Servlet规范

此外,我们一定不要忘记使用responseCacheWrapperObject调用doFilter()。否则,响应主体将不会被缓存。简而言之,ContentCachingResponseWrapper将过滤器放在响应输出流和发出HTTP请求的客户端之间。因此,在创建响应主体输出流时(在本例中是在doFilter()调用之后),内容可以在过滤器内部进行处理。

使用包装器后,可以使用getContentAsByteArray()方法在过滤器中获取响应主体,我们使用此方法计算MD5哈希值。

首先,我们使用MessageDigest类创建响应主体的MD5哈希值。其次,我们将字节数组转换为十六进制字符串。第三,我们使用setHeader()方法将生成的哈希字符串设置为响应对象的标头。

如果需要,我们可以将字节数组转换为字符串,并使主体的内容更加明确。

最后,在退出doFilter()方法之前调用copyBodyToResponse()将更新后的响应主体复制回原始响应,这一点至关重要:

responseCacheWrapperObject.copyBodyToResponse();

在退出doFilter()方法之前调用copyBodyToResponse()至关重要。否则,客户端将无法收到完整的响应

4. 配置过滤器

现在,我们需要在Spring Boot中添加过滤器

@Bean
public FilterRegistrationBean loggingFilter() {
    FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(new MD5Filter());
    return registrationBean;
}

在这里,我们配置创建一个FilterRegistrationBean,并使用我们之前创建的过滤器的实现。

5. 测试MD5

最后,我们可以使用Spring中的集成测试来测试一切是否按预期工作:

@Test
void whenExampleApiCallThenResponseHasMd5Header() throws Exception {
    String endpoint = "/api/example";
    String expectedResponse = "Hello, World!";
    String expectedMD5 = getMD5Hash(expectedResponse);

    MvcResult mvcResult = mockMvc.perform(get(endpoint).accept(MediaType.TEXT_PLAIN_VALUE))
            .andExpect(status().isOk())
            .andReturn();

    String md5Header = mvcResult.getResponse()
            .getHeader("Response-Body-MD5");
    assertThat(md5Header).isEqualTo(expectedMD5);
}

在这里,我们调用/api/example控制器,该控制器在正文中返回“Hello, World!”文本。我们定义了getMD5Hash()方法,该方法将响应转换为类似于我们在过滤器中使用的MD5:

private String getMD5Hash(String input) throws NoSuchAlgorithmException {
    MessageDigest md5Digest = MessageDigest.getInstance("MD5");
    byte[] md5Hash = md5Digest.digest(input.getBytes(StandardCharsets.UTF_8));
    return DatatypeConverter.printHexBinary(md5Hash);
}

6. 总结

在本文中,我们学习了如何使用ContentCachingResponseWrapper类从Spring Boot过滤器中的ServletResponse中检索响应主体,我们使用此机制展示了如何在HTTP响应标头中实现主体的MD5编码。

Show Disqus Comments
0 comments
Anonymous
Error: Not Found.
Markdown is supported

Be the first guy leaving a comment!

Post Directory

1. 简介
2. 理解问题
3. 在过滤器中使用ContentCachingResponseWrapper
4. 配置过滤器
5. 测试MD5
6. 总结
扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章