应用场景

如果你打算通过 javax.servlet.Filter 来拦截 POST 请求,并想通过 ServletInputStream 获取 body 的内容,等请求到达实际API时会报错:stream closed :::info 那是因为在Java中的 Stream 流只能使用一次,再次使用时该流已经被关闭了。 :::

解决方案1-Spring自带Wrapper

直接使用 org.springframework.web.util 包下的 ContentCachingRequestWrapper 类即可。

BodyReadFilter

  1. import java.io.IOException;
  2. import javax.servlet.Filter;
  3. import javax.servlet.FilterChain;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.ServletRequest;
  6. import javax.servlet.ServletResponse;
  7. import javax.servlet.http.HttpServletRequest;
  8. import org.springframework.stereotype.Component;
  9. import org.apache.commons.io.IOUtils;
  10. import org.slf4j.Logger;
  11. import org.slf4j.LoggerFactory;
  12. import org.springframework.web.util.ContentCachingRequestWrapper;
  13. @Component
  14. public class BodyReadFilter implements Filter {
  15. private static final Logger LOGGER = LoggerFactory.getLogger(BodyReadFilter.class);
  16. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
  17. throws IOException, ServletException {
  18. ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);
  19. LOGGER.info("The body of the request was {}", IOUtils.toString(wrappedRequest.getInputStream()));
  20. filterChain.doFilter(wrappedRequest, servletResponse);
  21. }
  22. }

解决方案2-自己实现

MultiReadHttpServletRequest

  1. import java.io.BufferedReader;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.ByteArrayOutputStream;
  4. import java.io.IOException;
  5. import java.io.InputStreamReader;
  6. import javax.servlet.ReadListener;
  7. import javax.servlet.ServletInputStream;
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpServletRequestWrapper;
  10. import org.apache.commons.io.IOUtils;
  11. /*
  12. via https://stackoverflow.com/a/36619972/2257038 and https://stackoverflow.com/a/30748533/2257038
  13. */
  14. public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
  15. private ByteArrayOutputStream cachedBytes;
  16. public MultiReadHttpServletRequest(HttpServletRequest request) {
  17. super(request);
  18. }
  19. @Override
  20. public ServletInputStream getInputStream() throws IOException {
  21. if (cachedBytes == null) cacheInputStream();
  22. return new CachedServletInputStream(cachedBytes.toByteArray());
  23. }
  24. @Override
  25. public BufferedReader getReader() throws IOException {
  26. return new BufferedReader(new InputStreamReader(getInputStream()));
  27. }
  28. private void cacheInputStream() throws IOException {
  29. /* Cache the inputstream in order to read it multiple times. For
  30. * convenience, I use apache.commons IOUtils
  31. */
  32. cachedBytes = new ByteArrayOutputStream();
  33. IOUtils.copy(super.getInputStream(), cachedBytes);
  34. }
  35. /* An inputstream which reads the cached request body */
  36. public static class CachedServletInputStream extends ServletInputStream {
  37. private final ByteArrayInputStream buffer;
  38. public CachedServletInputStream(byte[] contents) {
  39. this.buffer = new ByteArrayInputStream(contents);
  40. }
  41. @Override
  42. public int read() {
  43. return buffer.read();
  44. }
  45. @Override
  46. public boolean isFinished() {
  47. return buffer.available() == 0;
  48. }
  49. @Override
  50. public boolean isReady() {
  51. return true;
  52. }
  53. @Override
  54. public void setReadListener(ReadListener listener) {
  55. throw new RuntimeException("Not implemented");
  56. }
  57. }
  58. }

BodyReadFilter

  1. import java.io.IOException;
  2. import javax.servlet.Filter;
  3. import javax.servlet.FilterChain;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.ServletRequest;
  6. import javax.servlet.ServletResponse;
  7. import javax.servlet.http.HttpServletRequest;
  8. import org.springframework.stereotype.Component;
  9. import org.apache.commons.io.IOUtils;
  10. import org.slf4j.Logger;
  11. import org.slf4j.LoggerFactory;
  12. @Component
  13. public class BodyReadFilter implements Filter {
  14. private static final Logger LOGGER = LoggerFactory.getLogger(BodyReadFilter.class);
  15. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
  16. throws IOException, ServletException {
  17. MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest((HttpServletRequest) servletRequest);
  18. LOGGER.info("The body of the request was {}", IOUtils.toString(wrappedRequest.getInputStream()));
  19. filterChain.doFilter(wrappedRequest, servletResponse);
  20. }
  21. }

参考文章:Reading a Servlet/Spring Request Body Multiple Times
其他相关文章:https://juejin.im/post/6858037733776949262