解决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();
}
评论区