2010-12-15 113 views
26

J'ai ce code:Comment lire request.getInputStream() plusieurs fois

@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 (paquets sont raccourcies et les noms ont changé, mais il semble même)

<?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 souhaite appeler la servlet après le filtre. J'espérais chain.doFilter(...) pourrait faire l'affaire, mais je reçois 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) 
+0

Oui, cela devrait fonctionner. La servlet fonctionne-t-elle sans le filtre? – morja

+0

La servlet fonctionne sans le filtre et le filtre sans 'chain.doFilter()' fonctionne aussi – user219882

+0

en le mettant dehors si ..d'autre n'a pas aidé – user219882

Répondre

7

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é. Qu'est-ce que vous devez faire est d'utiliser un ServletRequestWrapper pour faire une copie du corps de la demande, de sorte que vous pouvez le lire avec des méthodes multiples. Je n'ai pas le temps de trouver un exemple complet juste savoir ... désolé ...

+2

Avoir une copie de la requête et utiliser 'getReader()' n'aide pas. L'exception sera 'getReader() a déjà été appelée pour cette requête' –

6

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

fluxEntrée dans la demande de servlet ne peut être utilisé qu'une seule fois à cause de cela est courant, 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) { 
     } 
    }; 
} 

}

Filtre:

ServletRequest requestWrapper = new HttpServletRequestWrapper(request); 
2

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); 
0

request.getInputStream() est autorisé à lire une seule fois. Afin d'utiliser cette méthode plusieurs fois, nous devons faire plus de la tâche personnalisée à la classe HttpServletReqeustWrapper. voir mon exemple de classe d'emballage 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

public class TracerRequestFilter implements Filtre { 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 { 

} 

}

Il travaille pour moi à la fois Servlet 2.5 et 3.0. Je vois tous les params de requête à la fois encodés en forme et demande le corps de json.