应用场景
如果你打算通过 javax.servlet.Filter
来拦截 POST
请求,并想通过 ServletInputStream
获取 body
的内容,等请求到达实际API时会报错:stream closed
:::info
那是因为在Java中的 Stream
流只能使用一次,再次使用时该流已经被关闭了。
:::
解决方案1-Spring自带Wrapper
直接使用 org.springframework.web.util
包下的 ContentCachingRequestWrapper
类即可。
BodyReadFilter
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.util.ContentCachingRequestWrapper;
@Component
public class BodyReadFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(BodyReadFilter.class);
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);
LOGGER.info("The body of the request was {}", IOUtils.toString(wrappedRequest.getInputStream()));
filterChain.doFilter(wrappedRequest, servletResponse);
}
}
解决方案2-自己实现
MultiReadHttpServletRequest
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
/*
via https://stackoverflow.com/a/36619972/2257038 and https://stackoverflow.com/a/30748533/2257038
*/
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null) cacheInputStream();
return new CachedServletInputStream(cachedBytes.toByteArray());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
/* Cache the inputstream in order to read it multiple times. For
* convenience, I use apache.commons IOUtils
*/
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
/* An inputstream which reads the cached request body */
public static class CachedServletInputStream extends ServletInputStream {
private final ByteArrayInputStream buffer;
public CachedServletInputStream(byte[] contents) {
this.buffer = new ByteArrayInputStream(contents);
}
@Override
public int read() {
return buffer.read();
}
@Override
public boolean isFinished() {
return buffer.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
}
}
BodyReadFilter
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class BodyReadFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(BodyReadFilter.class);
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest((HttpServletRequest) servletRequest);
LOGGER.info("The body of the request was {}", IOUtils.toString(wrappedRequest.getInputStream()));
filterChain.doFilter(wrappedRequest, servletResponse);
}
}
参考文章:Reading a Servlet/Spring Request Body Multiple Times
其他相关文章:https://juejin.im/post/6858037733776949262