侧边栏壁纸
博主头像
qingtian博主等级

喜欢是一件细水流长的事,是永不疲惫的双向奔赴~!

  • 累计撰写 104 篇文章
  • 累计创建 48 个标签
  • 累计收到 1 条评论

解决httpServletRequest 中的 body 只能读一次的问题

qingtian
2022-08-12 / 0 评论 / 0 点赞 / 1,032 阅读 / 6,853 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-08-22,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

解决Request 中的 body 只能读一次

  • 解决流只能读取一次的问题,让它可以被多次重复读取;
@RestController
@RequestMapping("/cache")
@Slf4j
public class RequestCacheController {

    @PostMapping("/requestCache")
    public String requestCache(@RequestBody UsernameAndPassword usernameAndPassword) {
        return usernameAndPassword.getPassword();
    }
}

--------------------------------------------------------------------------------------------------------------
@Component
@Slf4j
@Aspect
@Order(1)
public class PostAspect {

    @Pointcut("execution(* com.imooc.ecommerce.controller..*.*(..))")
    public void pointCut() {}

    private HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        Assert.notNull(requestAttributes);
        return requestAttributes.getRequest();
    }

    @Before("pointCut()")
    public void Before(JoinPoint joinPoint) throws IOException {
        HttpServletRequest request = getRequest();
        Assert.notNull(request,"request not null !");
        JSONObject postParams = getPostParams(request);
        log.info("request method : [{}], request url : [{}], request args : [{}]",
                request.getMethod(),
                request.getRequestURL(),
                postParams);
    }

    private JSONObject getPostParams(HttpServletRequest request) throws IOException {
        InputStreamReader inputStreamReader = null;
        BufferedReader reader = null;

        try {
            inputStreamReader = new InputStreamReader(request.getInputStream(),"utf-8");
            reader = new BufferedReader(inputStreamReader);
            StringBuilder stringBuilder = new StringBuilder();
            String inputStr;
            while ((inputStr = reader.readLine()) != null) {
                stringBuilder.append(inputStr);
            }
            JSONObject jsonObject = JSONObject.parseObject(stringBuilder.toString());
            log.info("json object is : [{}]", JSON.toJSONString(jsonObject));
            return jsonObject;
        }catch (Exception e) {
            throw e;
        }finally {
            IOUtils.closeQuietly(inputStreamReader);
            IOUtils.closeQuietly(reader);
        }
    }
}

----------------------------------------------------------------------------------------------------------
@Component
@Slf4j
@Aspect
@Order(2)
public class ControllerAspect {

    @Pointcut("execution(* com.imooc.ecommerce.controller..*.*(..))")
    public void pointCut() {}

    private HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        Assert.notNull(requestAttributes);
        return requestAttributes.getRequest();
    }

    @Before("pointCut()")
    public void Before(JoinPoint joinPoint) throws IOException {
        HttpServletRequest request = getRequest();
        Assert.notNull(request,"request not null !");
        log.info("request method : [{}], request url : [{}], request args : [{}]",
                request.getMethod(),
                request.getRequestURL(),
                joinPoint.getArgs());
    }

}

在这种情况下,如果尝试对一个 request 请求中的 body 进行重复读,会返回报错提示 java.io.IOException: Stream closed

HttpServletRequest 解决方法

/**
 * @author Guank
 * @version 1.0
 * @description: HttpServletRequest 包装类 缓存 request 中的 body
 * @date 2022-08-12 16:19
 */
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

    private final byte[] body;

    public RequestWrapper(HttpServletRequest request) {
        super(request);
        byte[] bytes = null;
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            bytes = IOUtils.toByteArray(inputStream);
        }catch (IOException ex) {
            log.error("request wrapper error");
        }finally {
            IOUtils.closeQuietly(inputStream);
        }
        body = bytes;
    }

    /**
     * 重写 getInputStream() 方法 让其返回我们缓存的 body 
     * @return
     */
    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
        ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;
    }
}

----------------------------------------------------------------------------------------------------------
/**
 * @author Guank
 * @version 1.0
 * @description: 缓存 request 的 Body 全局过滤器 必须第一个执行
 * @date 2022-08-12 15:39
 */
@Component
@Slf4j
@ServletComponentScan
public class RequestCachedBodyFilter implements Filter, Ordered {

    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE + 1;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest request = null;
        if (servletRequest instanceof HttpServletRequest) {
            request = new RequestWrapper((HttpServletRequest) servletRequest);
        }
        if (request != null) {
            filterChain.doFilter(request, servletResponse);
        } else {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

上面的方法有一个坑,使用 MultipartFile进行文件上传的时候会报错,

使用下面的方法改良过滤器

@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest request = null;
        if (servletRequest instanceof HttpServletRequest) {
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            String contentType = req.getContentType();
            String method = "multipart/form-data";
            if (null != contentType && contentType.contains(method)) {
                // 将转化后的 request 放入过滤链中
                req = new StandardServletMultipartResolver().resolveMultipart(req);
            }
            request = new RequestWrapper(req);
        }
        if (request != null) {
            filterChain.doFilter(request, servletResponse);
        } else {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

WebFlux 中 ServerHttpRequest解决方法

@Component
@Slf4j
public class ServerRequestCachedBodyFilter implements WebFilter, Ordered {
    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE + 1;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {

            //确保数据缓冲区的数据不被释放DataBufferUtils.retain
            DataBufferUtils.retain(dataBuffer);
            //defer just创建数据源,得到当前数据的副本
            Flux<DataBuffer> cachedFlux = Flux.defer(() ->
                    Flux.just(dataBuffer.slice(0,dataBuffer.readableByteCount())));
            //重新包装ServerHttp请求,重写getBody方法,能狗返回请求数据
            ServerHttpRequest mutateRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return cachedFlux;
                }
            };
            //将包装后的serverhttpRequest向下传递
            return chain.filter(exchange.mutate().request(mutateRequest).build());
        });
    }
}
------------------------------------------------------------------------------------------------------------
	// 取出缓存的数据
	/**
     * @description:  从post请求中获取到数据
     * @param: request
     * @return: java.lang.String
     * @date: 2021/12/21 23:35
     */
    private String parseBodyFromRequest(ServerHttpRequest request) {

        //获取请求体
        Flux<DataBuffer> body = request.getBody();
        //获取原子引用
        AtomicReference<String> atomRef = new AtomicReference<>();

        //订阅缓冲去去消费请求体中的数据
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            //由于在之前的GlobalCacheRequestBodyFilter中将Body进行了retain所以在这里必须要释放掉
            //否则会造成内存泄露
            DataBufferUtils.release(buffer);
            atomRef.set(charBuffer.toString());
        });

        //获取到request body
        return atomRef.get();
    }
0

评论区