request.inputStream()和response.outputStream()只能读一次的问题
程序员文章站
2022-03-10 14:15:49
...
最近接到需求,要记录操作日志,这些操作日志属于同一类,可以统一进行处理,包含新增、修改、和删除操作。当然首先想到的是AOP。在写的过程中发现,HttpServletRequest 中的inputStream只能读取一次。通过网上查找资料发现可以通过继承HttpServletRequestWrapper类,重写方法来实现多次读取的目的。具体代码如下所示:
package com.test.wrapper;
import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {
private byte[] body;
private BufferedReader reader;
private ServletInputStream inputStream;
public ContentCachingRequestWrapper(HttpServletRequest request) throws IOException{
super(request);
loadBody(request);
}
private void loadBody(HttpServletRequest request) throws IOException{
body = IOUtils.toByteArray(request.getInputStream());
inputStream = new RequestCachingInputStream(body);
}
public byte[] getBody() {
return body;
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (inputStream != null) {
return inputStream;
}
return super.getInputStream();
}
@Override
public BufferedReader getReader() throws IOException {
if (reader == null) {
reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));
}
return reader;
}
private static class RequestCachingInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
public RequestCachingInputStream(byte[] bytes) {
inputStream = new ByteArrayInputStream(bytes);
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public boolean isFinished() {
return inputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readlistener) {
}
}
}
package com.test.filter;
import com.test.wrapper.ContentCachingRequestWrapper;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class ContentCachingRequestFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(LogUUIDFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
String body = IOUtils.toString(requestWrapper.getBody(),request.getCharacterEncoding());
logger.info("filter request body is {}",body);
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {
}
}
ContentCachingRequestWrapper中对 BufferedReader和ServletInputStream进行了缓存。然后将filter的chain.doFilter中原先的request替换为我们新写的requestWrapper。至此,通过该filter之后的各层请求链都可以无限制的获取request.getInputStream()了。
既然request.getInputStream()只能取一次,那么response.getOutputStream()同样也只能取一次,解决方法类似,都是缓存起来。下面给出完整例子:
package com.test.wrapper;
import java.io.*;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class ResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream buffer = null;
private ServletOutputStream out = null;
private PrintWriter writer = null;
public ResponseWrapper(HttpServletResponse resp) throws IOException {
super(resp);
buffer = new ByteArrayOutputStream();
out = new WapperedOutputStream(buffer);
writer = new PrintWriter(new OutputStreamWriter(buffer, this.getCharacterEncoding()));
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}
@Override
public PrintWriter getWriter() throws UnsupportedEncodingException {
return writer;
}
@Override
public void flushBuffer() throws IOException {
if (out != null) {
out.flush();
}
if (writer != null) {
writer.flush();
}
}
@Override
public void reset() {
buffer.reset();
}
public byte[] getResponseData() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
private class WapperedOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos = null;
public WapperedOutputStream(ByteArrayOutputStream stream) throws IOException {
bos = stream;
}
@Override
public void write(int b) throws IOException {
bos.write(b);
}
@Override
public void write(byte[] b) throws IOException {
bos.write(b, 0, b.length);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
}
}
切面的代码如下所示:
package com.test.aspect;
import com.test.wrapper.ContentCachingRequestWrapper;
import com.test.wrapper.ResponseWrapper;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
@Aspect
@Component
public class testAspect {
@Pointcut("execution(* com.test.controller.XX.XXX.XXXX(..))")
public void opRecordPoint() {
}
@Around("opRecordPoint()")
public Object aroundManage(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;
String body = IOUtils.toString(requestWrapper.getBody(), request.getCharacterEncoding());
/**
*
* do something before
*
*/
Object result = joinPoint.proceed();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
ResponseWrapper responseWrapper = (ResponseWrapper) response;
byte[] bytes = responseWrapper.getResponseData();
String responseResult = new String(bytes, "UTF-8");
/**
*
* do something after
*
*/
}
}
filter中的response同样需要替换。
package com.test.filter;
import com.test.wrapper.ContentCachingRequestWrapper;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class ContentCachingRequestFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(LogUUIDFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
ResponseWrapper wrapperResponse = new ResponseWrapper((HttpServletResponse)response);//转换成代理类
String body = IOUtils.toString(requestWrapper.getBody(),request.getCharacterEncoding());
logger.info("filter request body is {}",body);
chain.doFilter(requestWrapper, wrapperResponse);
//response的值重新塞进去
String result=new String(wrapperResponse.getResponseData());
response.setContentLength(-1);//解决可能在运行的过程中页面只输出一部分
response.setCharacterEncoding("UTF-8");
PrintWriter out=response.getWriter();
out.write(result);
out.flush();
out.close();
}
@Override
public void destroy() {
}
}
至此,request和response流只能读取一次的情况就解决了,主体思想都是将其缓存起来。
下一篇: 03—构造器模式