简体   繁体   English

如何多次读取 request.getInputStream()

[英]How to read request.getInputStream() multiple times

I have this code:我有这个代码:

@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);
    }
}

and this web.xml (packages are shortened and names changed, but it looks the same)和这个 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>

I want to invoke the Servlet after the Filter.我想在过滤器之后调用 Servlet。 I was hoping chain.doFilter(...) could do the trick, but i always get this error on the line with chain.doFilter(...) :我希望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)

Working code based on the accepted answer.基于接受的答案的工作代码。

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;  
} 
}

You probably start consuming the HttpServletRequest using getReader() in :您可能开始使用 HttpServletRequest 使用getReader()在:

String ba = getBaId(getBody(httpRequest)); 

Your servlet tries to call getInputStream() on the same request, which is not allowed.您的 servlet 尝试对同一请求调用getInputStream() ,这是不允许的。 What you need to do is use a ServletRequestWrapper to make a copy of the body of the request, so you can read it with multiple methods.您需要做的是使用ServletRequestWrapper制作请求正文的副本,以便您可以使用多种方法读取它。 I dont have the time to find a complete example right know ... sorry ...我没有时间找到一个完整的例子正确知道......对不起......

This worked for me.这对我有用。 It implements getInputStream .它实现了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();
            }
        };
    }

}

Then you use in your method:然后你在你的方法中使用:

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

For Servlet 3.1对于 Servlet 3.1

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 DelegatingServletInputStream(new ByteArrayInputStream(body));


    }

}


public class DelegatingServletInputStream extends ServletInputStream {

    private final InputStream sourceStream;

    private boolean finished = false;


    /**
     * Create a DelegatingServletInputStream for the given source stream.
     *
     * @param sourceStream the source stream (never {@code null})
     */
    public DelegatingServletInputStream(InputStream sourceStream) {
        this.sourceStream = sourceStream;
    }

    /**
     * Return the underlying source stream (never {@code null}).
     */
    public final InputStream getSourceStream() {
        return this.sourceStream;
    }


    @Override
    public int read() throws IOException {
        int data = this.sourceStream.read();
        if (data == -1) {
            this.finished = true;
        }
        return data;
    }

    @Override
    public int available() throws IOException {
        return this.sourceStream.available();
    }

    @Override
    public void close() throws IOException {
        super.close();
        this.sourceStream.close();
    }

    @Override
    public boolean isFinished() {
        return this.finished;
    }

    @Override
    public boolean isReady() {
        return true;
    }

    @Override
    public void setReadListener(ReadListener readListener) {
        throw new UnsupportedOperationException();
    }

}

inputStream in servlet request can only be used once because of it is stream,you can store it and then get it from a byte array,this can resolve. servlet 请求中的 inputStream 只能使用一次,因为它是流,您可以存储它然后从一个字节数组中获取它,这可以解决。

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) {
        }
    };
}
}

in filter:在过滤器中:

ServletRequest requestWrapper = new HttpServletRequestWrapper(request);

request.getInputStream() is allowed to read only one time. request.getInputStream()只允许读取一次。 In order to use this method many times, we need to do extra the custom task to HttpServletReqeustWrapper class.为了多次使用这个方法,我们需要对 HttpServletReqeustWrapper 类做额外的自定义任务。 see my sample wrapper class below.请参阅下面的示例包装类。

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();
        }
    }
}

In my case, I trace all incoming requests into the log.就我而言,我将所有传入请求跟踪到日志中。 I created a 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 {

    }
}

It works for me both Servlet 2.5 and 3.0.它适用于 Servlet 2.5 和 3.0。 I see all request params both form-encoded and request json body.我看到了所有请求参数,包括表单编码和请求 json 正文。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 无法从request.getInputStream()获取图像 - Unable to getImage from request.getInputStream() 从Grails的@Context HttpServletRequest请求中获取一个新的request.getInputStream() - get a new request.getInputStream() from @Context HttpServletRequest request in grails HTTPServlet Request.getInputStream()始终接收空白行 - HTTPServlet Request.getInputStream() always receiving blank line request.getInputStream不显示真实的字符串内容 - request.getInputStream does not show the real string content 在同一文件的request.getInputStream中接收不同的长度 - Receiving varying lengths in request.getInputStream for the same file 通过Jetty调用Request.getInputStream时管道损坏 - Broken pipe when calling Request.getInputStream over Jetty 从BufferedReader(request.getInputStream())和request.getParameter读取servlet上的jsp数据之间的区别 - Difference between reading jsp data on servlet from BufferedReader (request.getInputStream()) and request.getParameter 多次读取请求流 - Read the request stream multiple times HttpURLConnection getInputStream读取页面,然后发出POST请求 - HttpURLConnection getInputStream to read page, and then make POST request 如何在 Spring 'HandlerMethodArgumentResolver' 中多次读取请求正文? - How can I read request body multiple times in Spring 'HandlerMethodArgumentResolver'?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM