web-dev-qa-db-fra.com

Comment lire plusieurs fois request.getInputStream ()

J'ai ce 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);
    }
}

et ce web.xml (les packages sont raccourcis et les noms changés, mais ils se ressemblent)

<?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>

Je veux invoquer le servlet après le filtre. J'espérais que chain.doFilter(...) pourrait faire l'affaire, mais j'obtiens toujours cette erreur sur la ligne avec 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)
35
user219882

Vous commencez probablement à consommer HttpServletRequest en utilisant getReader() dans:

String ba = getBaId(getBody(httpRequest)); 

Votre servlet essaie d'appeler getInputStream() sur la même demande, ce qui n'est pas autorisé. Ce que vous devez faire est d'utiliser un ServletRequestWrapper pour faire une copie du corps de la demande, afin que vous puissiez le lire avec plusieurs méthodes. Je n'ai pas le temps de trouver un exemple complet sais bien ... désolé ...

8
Guillaume

Code de travail basé sur la réponse acceptée.

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

Cela a fonctionné pour moi. Il implémente 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();
            }
        };
    }

}

Ensuite, vous utilisez dans votre méthode:

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

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

}
6
宏杰李

request.getInputStream () ne peut lire qu'une seule fois. Pour utiliser cette méthode plusieurs fois, nous devons ajouter la tâche personnalisée à la classe HttpServletReqeustWrapper. voir mon exemple de classe wrapper ci-dessous.

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

Dans mon cas, je trace toutes les demandes entrantes dans le journal. J'ai créé un filtre

classe publique TracerRequestFilter implémente 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 {

}
}

Cela fonctionne pour moi à la fois Servlet 2.5 et 3.0. Je vois tous les paramètres de demande à la fois codés en forme et corps json de demande.

1
sopheamak

inputStream dans la requête de servlet ne peut être utilisé qu'une seule fois en raison de son flux, vous pouvez le stocker puis l'obtenir à partir d'un tableau d'octets, cela peut résoudre.

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

dans le filtre:

ServletRequest requestWrapper = new HttpServletRequestWrapper(request);
1
link