Подтвердить что ты не робот

Как читать 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>

Я хочу вызвать 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)
4b9b3361

Ответ 1

Вы, вероятно, начнете потреблять HttpServletRequest, используя getReader() в:

String ba = getBaId(getBody(httpRequest)); 

Ваш сервлет пытается вызвать getInputStream() по тому же запросу, что не разрешено. Что вам нужно сделать, это использовать ServletRequestWrapper, чтобы сделать копию тела запроса, чтобы вы могли прочитать его несколькими способами. У меня нет времени, чтобы найти полный пример, который правильно знает... извините...

Ответ 2

Рабочий код на основе принятого ответа.

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

Ответ 3

Это сработало для меня. Он реализует 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);

Ответ 4

Для сервлета 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();
    }

}

Ответ 5

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

в фильтре:

ServletRequest requestWrapper = new HttpServletRequestWrapper(request);

Ответ 6

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

В моем случае я отслеживаю все входящие запросы в журнал. Я создал фильтр

открытый класс TracerRequestFilter реализует 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 {

}
}

У меня работает как сервлет 2.5, так и 3.0. Я вижу все параметры запроса как в кодировке формы, так и в теле запроса json.