如何多次读取request.getInputStream()

我有这个代码:

@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { logger.info("Filter start..."); HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; String ba = getBaId(getBody(httpRequest)); if (ba == null) { logger.error("Wrong XML"); httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); } else { if (!clients.containsKey(ba)) { clients.put(ba, 1); logger.info("Client map : init..."); } else { clients.put(ba, clients.get(ba).intValue() + 1); logger.info("Threads for " + ba + " = " + clients.get(ba).toString()); } chain.doFilter(request, response); } } 

和这个web.xml(包缩短了,名称改变了,但看起来是一样的)

 <?xml version="1.0" encoding="ISO-8859-1"?> <web-app> <filter> <filter-name>TestFilter</filter-name> <filter-class>pkg.TestFilter</filter-class> </filter> <filter-mapping> <filter-name>TestFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>Name</servlet-name> <display-name>Name</display-name> <servlet-class>pkg.Name</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Name</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> </web-app> 

我想在Filter之后调用Servlet。 我希望chain.doFilter(...)可以做的伎俩,但我总是得到这个错误的行chain.doFilter(...)

 java.lang.IllegalStateException: getInputStream() can't be called after getReader() at com.caucho.server.connection.AbstractHttpRequest.getInputStream(AbstractHttpRequest.java:1933) at org.apache.cxf.transport.http.AbstractHTTPDestination.setupMessage(AbstractHTTPDestination.java:249) at org.apache.cxf.transport.servlet.ServletDestination.invoke(ServletDestination.java:82) at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:283) at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:166) at org.apache.cxf.transport.servlet.AbstractCXFServlet.invoke(AbstractCXFServlet.java:174) at org.apache.cxf.transport.servlet.AbstractCXFServlet.doPost(AbstractCXFServlet.java:152) at javax.servlet.http.HttpServlet.service(HttpServlet.java:153) at javax.servlet.http.HttpServlet.service(HttpServlet.java:91) at com.caucho.server.dispatch.ServletFilterChain.doFilter(ServletFilterChain.java:103) at pkg.TestFilter.doFilter(TestFilter.java:102) at com.caucho.server.dispatch.FilterFilterChain.doFilter(FilterFilterChain.java:87) at com.caucho.server.webapp.WebAppFilterChain.doFilter(WebAppFilterChain.java:187) at com.caucho.server.dispatch.ServletInvocation.service(ServletInvocation.java:265) at com.caucho.server.http.HttpRequest.handleRequest(HttpRequest.java:273) at com.caucho.server.port.TcpConnection.run(TcpConnection.java:682) at com.caucho.util.ThreadPool$Item.runTasks(ThreadPool.java:743) at com.caucho.util.ThreadPool$Item.run(ThreadPool.java:662) at java.lang.Thread.run(Thread.java:619) 

你可能会开始使用getReader()来使用HttpServletRequest:

 String ba = getBaId(getBody(httpRequest)); 

你的servlet试图在相同的请求上调用getInputStream() ,这是不允许的。 你需要做的是使用一个ServletRequestWrapper来制作一个请求体的副本,所以你可以用多种方法来读取它。 我没有时间find一个完整的例子知道…抱歉…

工作代码基于接受的答案。

 public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper { private static final Logger logger = Logger.getLogger(CustomHttpServletRequestWrapper.class); private final String body; public CustomHttpServletRequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; try { InputStream inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { logger.error("Error reading the request body..."); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException ex) { logger.error("Error closing bufferedReader..."); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream () throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); ServletInputStream inputStream = new ServletInputStream() { public int read () throws IOException { return byteArrayInputStream.read(); } }; return inputStream; } } 

inputStream在servlet请求中只能使用一次,因为它是stream的,可以存储它然后从一个字节数组中获取,这个可以解决。

 public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper { private final byte[] body; public HttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = StreamUtil.readBytes(request.getReader(), "UTF-8"); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return byteArrayInputStream.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener arg0) { } }; } 

}

在filter中:

 ServletRequest requestWrapper = new HttpServletRequestWrapper(request); 

这对我有效。 它实现了getInputStream

 private class MyHttpServletRequestWrapper extends HttpServletRequestWrapper { private byte[] body; public MyHttpServletRequestWrapper(HttpServletRequest request) { super(request); try { body = IOUtils.toByteArray(request.getInputStream()); } catch (IOException ex) { body = new byte[0]; } } @Override public ServletInputStream getInputStream() throws IOException { return new ServletInputStream() { ByteArrayInputStream bais = new ByteArrayInputStream(body); @Override public int read() throws IOException { return bais.read(); } }; } } 

然后你在你的方法中使用:

 //copy body servletRequest = new MyHttpServletRequestWrapper(servletRequest); 

request.getInputStream()被允许只读一次。 为了多次使用这个方法,我们需要对HttpServletReqeustWrapper类做额外的自定义任务。 请参阅下面的示例包装类。

 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(); } @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 class CachedServletInputStream extends ServletInputStream { private ByteArrayInputStream input; public CachedServletInputStream() { /* create a new input stream from the cached request body */ input = new ByteArrayInputStream(cachedBytes.toByteArray()); } @Override public int read() throws IOException { return input.read(); } } } 

在我的情况下,我跟踪所有传入的请求到日志中。 我创build了一个filter

public class TracerRequestFilter implements Filter {private static final Logger LOG = LoggerFactory.getLogger(TracerRequestFilter.class);

 @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { final HttpServletRequest req = (HttpServletRequest) request; try { if (LOG.isDebugEnabled()) { final MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(req); // debug payload info logPayLoad(wrappedRequest); chain.doFilter(wrappedRequest, response); } else { chain.doFilter(request, response); } } finally { LOG.info("end-of-process"); } } private String getRemoteAddress(HttpServletRequest req) { String ipAddress = req.getHeader("X-FORWARDED-FOR"); if (ipAddress == null) { ipAddress = req.getRemoteAddr(); } return ipAddress; } private void logPayLoad(HttpServletRequest request) { final StringBuilder params = new StringBuilder(); final String method = request.getMethod().toUpperCase(); final String ipAddress = getRemoteAddress(request); final String userAgent = request.getHeader("User-Agent"); LOG.debug(String.format("============debug request==========")); LOG.debug(String.format("Access from ip:%s;ua:%s", ipAddress, userAgent)); LOG.debug(String.format("Method : %s requestUri %s", method, request.getRequestURI())); params.append("Query Params:").append(System.lineSeparator()); Enumeration<String> parameterNames = request.getParameterNames(); for (; parameterNames.hasMoreElements();) { String paramName = parameterNames.nextElement(); String paramValue = request.getParameter(paramName); if ("password".equalsIgnoreCase(paramName) || "pwd".equalsIgnoreCase(paramName)) { paramValue = "*****"; } params.append("---->").append(paramName).append(": ").append(paramValue).append(System.lineSeparator()); } LOG.debug(params.toString()); /** request body */ if ("POST".equals(method) || "PUT".equals(method)) { try { LOG.debug(IOUtils.toString(request.getInputStream())); } catch (IOException e) { LOG.error(e.getMessage(), e); } } LOG.debug(String.format("============End-debug-request==========")); } @Override public void init(FilterConfig arg0) throws ServletException { } 

}

它适用于我的Servlet 2.5和3.0。 我看到所有的请求参数都forms编码和请求json体。