JsonCachingRequestWrapper
背景
因为Java的inputstream
只能读一次,所以ServletRequest
中的inputstream
一旦中途被读过一次,controller
层在组装参数时因为读不到数据就会报错(因为再读inputstream
时发现为空)
而spring提供了一个ContentCachingRequestWrapper
,但是只支持application/x-www-form-urlencoded
类型的请求,对于JSON是不支持的。并且它不支持多次读inputstream
,只是把读完之后的内容cache了起来,然后我们可以通过getContentAsByteArray()
方法获取cache住的内容,而我们要实现的是一个可以多次读流的request。
实现 JsonCachingRequestWrapper
实现思路
实现思路很简单,将inputstream
读入一个byte[]
,后续每次需要使用inputstream
时,通过byte[]
实例化一个新的inputstream
。当然这里还有一个知识点,servlet提供了一个HttpServletRequestWrapper
基础类,让我们可以通过继承这个类来改造request的行为。下面是完整的代码(可以直接复制粘贴使用):
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
* @author xchen
*/
public class JsonCachingRequestWrapper extends HttpServletRequestWrapper {
private static final String CONTENT_TYPE_JSON = "application/json";
private static final String POST_STR = "POST";
private byte[] cache;
public JsonCachingRequestWrapper(HttpServletRequest request) {
super(request);
int contentLength = request.getContentLength();
writeRequestParametersToCachedContent(contentLength);
}
public static boolean isSupport(ServletRequest request) {
String contentType = request.getContentType();
String scheme = request.getScheme();
if (null == contentType || scheme == null) {
return false;
}
if (scheme.equals("http") || scheme.equals("https")) {
HttpServletRequest req = (HttpServletRequest) request;
if (!POST_STR.equals(req.getMethod())) {
return false;
}
return contentType.toLowerCase().startsWith(CONTENT_TYPE_JSON);
}
return false;
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new BodyInputStream(this.cache);
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
private void writeRequestParametersToCachedContent(int contentLength) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(contentLength);
byte[] buffer = new byte[256];
ServletInputStream inputStream = super.getInputStream();
while (!inputStream.isFinished()) {
int nums = inputStream.read(buffer);
if (nums > 0) {
outputStream.write(buffer, 0, nums);
}
}
this.cache = outputStream.toByteArray();
} catch (IOException ex) {
throw new IllegalStateException("Failed to write request parameters to cached content", ex);
}
}
public byte[] getContentAsByteArray() {
return this.cache;
}
private static class BodyInputStream extends ServletInputStream {
private final InputStream delegate;
private boolean isFinished = false;
public BodyInputStream(byte[] body) {
this.delegate = new ByteArrayInputStream(body);
}
@Override
public boolean isFinished() {
return isFinished;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
@Override
public int read() throws IOException {
int read = this.delegate.read();
if (read == -1) {
isFinished = true;
}
return read;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int read = this.delegate.read(b, off, len);
if (read == -1) {
isFinished = true;
}
return read;
}
@Override
public int read(byte[] b) throws IOException {
int read = this.delegate.read(b);
if (read == -1) {
isFinished = true;
}
return read;
}
@Override
public long skip(long n) throws IOException {
return this.delegate.skip(n);
}
@Override
public int available() throws IOException {
return this.delegate.available();
}
@Override
public void close() throws IOException {
this.delegate.close();
}
@Override
public synchronized void mark(int readlimit) {
this.delegate.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
this.delegate.reset();
}
@Override
public boolean markSupported() {
return this.delegate.markSupported();
}
}
}
如何使用
细心的朋友会发现上面有一个方法JsonCachingRequestWrapper.isSupport(ServletRequest request)
.这个方法可以用来判断是否是JSON类型的请求,如果是表单类型
或者是上传文件
,显然用JsonCachingRequestWrapper
是不合适的。
- 初始化,通常我们可以这么初始化(一般放在filter中):
public class RequestCommonFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (JsonCachingRequestWrapper.isSupport(servletRequest)) {
servletRequest = new JsonCachingRequestWrapper((HttpServletRequest) servletRequest);
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
- 使用(这里以打印日志为例)
if (JsonCachingRequestWrapper.isSupport(request)) {
JsonCachingRequestWrapper requestWrapper = (JsonCachingRequestWrapper) request;
String reqParams = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
log.info("Req-log: {} {}", requestWrapper.getRequestURI(), reqParams);
}
写在最后
JavaIO的知识点还是挺多的,一开始学也比较抽象,要学的好还得懂一点操作系统,值得大家找资料仔细学习下。