spring boot - 缓存请求之 ContentCachingRequestWrapper

spring boot - 缓存请求之 ContentCachingRequestWrapper

ContentCachingRequestWrapper 是 spring boot 自带的请求体重复利用的工具,不过他也有缺点

That means if the request:

  • content is not consumed, then the content is not cached
  • and cannot be retrieved via {@link #getContentAsByteArray()}

也就是说,用ContentCachingRequestWrapper的前提:

  • 你要消费这个请求数据
  • 能过 getContentAsByteArray 方法获取缓存内容

如何使用呢?

CachingContentFilter

public class CachingContentFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        if (request instanceof HttpServletRequest) {
            if (request.getContentType() != null && !request.getContentType().contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
                ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
                //测试
                String requestBody = getRequestBody(wrapper);
                System.out.println("repeat:" + requestBody);
                chain.doFilter(wrapper, response);
                return;
            }
        }
        chain.doFilter(request, response);
    }

    private String getRequestBody(HttpServletRequest request) {
        String requestBody = "";
        ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if (wrapper != null) {
            try {
                requestBody = IOUtils.toString(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
            } catch (IOException e) {
            }
        }
        return requestBody;
    }

}

配置

@Bean
public FilterRegistrationBean someFilterRegistration() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(new CachingContentFilter());
    registration.addUrlPatterns("/*");
    registration.setName("cacheRequestFilter");
    registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
    return registration;
}

测试:

@PostMapping("/form1")
public String post1(HttpServletRequest request, @RequestBody String body) throws IOException {
    System.out.println("param: " + request.getParameter("name"));
    String s1 = new String(StreamUtils.copyToByteArray(request.getInputStream()));
    String s2 = new String(StreamUtils.copyToByteArray(request.getInputStream()));
    System.out.println("s1: " + s1);
    System.out.println("s2" + s2);
    ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
    String s3 = new String(wrapper.getContentAsByteArray(), "UTF-8");
    String s4 = new String(wrapper.getContentAsByteArray(), "UTF-8");
    System.out.println("s3: " + s3);
    System.out.println("s4" + s4);
    return "success";
}

结果:

s1: 
s2
s3: {
    "a":1
}
s4{
    "a":1
}

首先通过 @RequestBody 消费了请求体,再次使用 getContentAsByteArray 方式重复利用

<think>好的,用户遇到了关于ContentCachingRequestWrapper的cachedContent的body参数没有设置进去的问题。首先,我需要回忆一下Spring框架中这个类的作用。ContentCachingRequestWrapper主要用于缓存请求体中的数据,方便后续多次读取。通常,用户可能会在过滤器或拦截器中尝试读取请求体,但如果没有正确配置,可能会导致缓存内容为空。 接下来,我应该考虑可能的原因。首先,用户是否正确地包装了原始的HttpServletRequest?在过滤器中,必须将请求替换为ContentCachingRequestWrapper的实例,否则后续的处理无法获取到缓存的内容。另外,用户是否在读取缓存内容之前已经消费了输入流?因为一旦输入流被读取,缓存的内容才会被填充,所以如果在读取之前没有触发消费,缓存可能为空。 然后,考虑用户是否在正确的时机读取缓存内容。缓存是在请求被处理之后才会填充的,因此如果在处理之前读取,内容自然为空。例如,在过滤器中,如果在chain.doFilter()之前尝试获取缓存内容,此时还没有数据。 另外,可能需要检查ContentCachingRequestWrapper的配置是否正确,比如缓存大小是否足够大。如果请求体超过了默认的缓存大小,可能会导致数据被截断或者未完全缓存。用户可能需要调整缓存大小参数。 此外,用户是否使用了正确的API来获取缓存内容?应该使用getContentAsByteArray()方法,而不是直接读取输入流。同时,字符编码是否一致,如果请求的编码和读取时使用的编码不同,可能会出现乱码或者数据错误。 最后,可能需要考虑是否与其他组件冲突,比如某些过滤器或拦截器已经读取了输入流,导致无法再次读取。或者Spring Boot版本的问题,不同版本的实现可能有差异,需要确认用户使用的版本是否有已知的问题。 总结下来,解决步骤应该是:确保正确包装请求,确保在请求处理后读取缓存,检查缓存大小,使用正确的方法获取内容,处理字符编码,排除其他组件的干扰。如果用户按照这些步骤排查,应该能找到问题所在并解决。</think>关于Spring框架中`ContentCachingRequestWrapper`的`cachedContent`参数未正确设置的问题,以下是逐步分析及解决方案: --- ### **问题现象** 使用`ContentCachingRequestWrapper`时,发现`getContentAsByteArray()`返回空数据或`cachedContent`未正确填充请求体内容。 --- ### **核心原理** 1. `ContentCachingRequestWrapper`的作用是缓存HTTP请求的输入流(body),以便后续多次读取。 2. **缓存触发条件**:必须通过`getInputStream()`或`getReader()`读取输入流后,才会触发缓存机制。 --- ### **常见原因及解决方案** #### 1. **未正确包装原始请求** - **错误表现**:直接使用原始`HttpServletRequest`而非包装后的对象。 - **解决方案**:在过滤器中替换请求对象: ```java public class CachingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); filterChain.doFilter(wrappedRequest, response); // 传递包装后的对象 } } ``` --- #### 2. **过早读取缓存内容** - **错误表现**:在请求处理流程完成前尝试读取缓存- **解决方案**:确保在请求处理完成后读取缓存: ```java // 在拦截器或过滤器的postHandle方法中读取 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { ContentCachingRequestWrapper wrappedRequest = (ContentCachingRequestWrapper) request; byte[] body = wrappedRequest.getContentAsByteArray(); // 此时缓存已填充 } ``` --- #### 3. **输入流未被消费** - **错误表现**:未触发输入流的读取操作。 - **解决方案**:强制读取输入流以填充缓存: ```java ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); wrappedRequest.getParameterMap(); // 触发输入流读取 // 或 ServletInputStream inputStream = wrappedRequest.getInputStream(); while (inputStream.read() != -1) {} // 手动消费流 ``` --- #### 4. **缓存大小限制** - **错误表现**:请求体超过默认缓存大小(默认值:2KB)。 - **解决方案**:通过构造函数指定更大的缓存容量: ```java ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request, 1024 * 1024); // 1MB ``` --- #### 5. **字符编码问题** - **错误表现**:读取缓存时未指定正确的字符集。 - **解决方案**:显式指定编码格式: ```java String body = new String(wrappedRequest.getContentAsByteArray(), wrappedRequest.getCharacterEncoding()); ``` --- ### **验证步骤** 1. 检查过滤器链是否包装请求: ```java System.out.println(request.getClass()); // 应输出ContentCachingRequestWrapper ``` 2. 在请求完成后打印缓存内容: ```java log.debug("Cached body: {}", new String(wrappedRequest.getContentAsByteArray())); ``` 3. 使用Postman发送测试请求,观察日志输出。 --- ### **代码示例** 完整过滤器实现: ```java public class ContentCachingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); chain.doFilter(wrappedRequest, response); // 请求完成后读取缓存 byte[] body = wrappedRequest.getContentAsByteArray(); if (body.length > 0) { String bodyStr = new String(body, wrappedRequest.getCharacterEncoding()); System.out.println("Cached body: " + bodyStr); } } } ``` --- ### **关键结论** - 缓存填充需要**触发输入流读取**。 - 必须在**请求处理完成后**访问缓存- 需通过`ContentCachingRequestWrapper`的实例方法获取数据。 通过以上步骤排查,可解决`cachedContent`未正确设置的问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值