如何读取一个ServletInputStream不止一次,当你没有控制第二次读取它的代码

问题描述:

我有一个ServletInputStream,我需要多次读取(第二次读取它的代码是我不控制的API)。当使用IOUtils.copy时,它似乎仍然只允许读取一次(在流上不允许标记/重置)。如何读取一个ServletInputStream不止一次,当你没有控制第二次读取它的代码

任何想法?谢谢。

+1

在字节数组中读取它,在此字节数组上创建ByteArrayInputStream,并将此ByteArrayInputStream传递给API。但是你应该解释为什么你需要这个以及你想要在更高层次上做什么,因为可能有不同的解决方案。 – 2015-01-26 21:13:48

+0

谢谢。我从HttpServletRequest对象获取流,该对象正在用作API方法的输入。看起来他们直接从请求对象获取输入流,该请求对象在该点已经被读取。 – mstrom 2015-01-26 21:17:41

+0

此API如何处理流?你使用什么代码处理流? – 2015-01-26 21:18:57

创建一个扩展HttpServletRequestWrapper的类。该类将缓存原始请求的输入流中的内容到临时文件中。

public class CachedHttpServletRequest extends HttpServletRequestWrapper 
{ 

    public static final String TEMPORARY_FILENAME_PREFIX = "MyPrefix"; 
    public static final String TEMPORARY_FILENAME_SUFFIX = ".cache"; 

    public static final int LEN_BUFFER = 32768; //32 KB 


    private File m_TemporaryFile; 


    public CachedHttpServletRequest(HttpServletRequest httpServletRequest, File temporaryFolder) 
     throws ServletException { 

     super(httpServletRequest); 

     try { 
      //Create a temporary file to hold the contents of the request's input stream 
      m_TemporaryFile = File.createTempFile(TEMPORARY_FILENAME_PREFIX, null, temporaryFolder); 

      //Copy the request body to the temporary file 
      BufferedInputStream is = new BufferedInputStream(super.getInputStream()); 
      FileOutputStream os = new FileOutputStream(m_TemporaryFile); 
      byte[] buffer = new byte[LEN_BUFFER]; 
      int bytesWritten = 0; 
      int bytesRead = is.read(buffer); 
      while(bytesRead != -1) { 
       os.write(buffer, 0, bytesRead); 
       bytesWritten += bytesRead; 
       bytesRead = is.read(buffer); 
      } 
      is.close(); 
      os.close(); 
     } 
     catch(Exception e) { 
      throw new ServletException(e); 
     } 
    } 


    public void cleanup() { 
     m_TemporaryFile.delete(); 
    } 


    @Override 
    public ServletInputStream getInputStream() throws IOException { 
     return new CachedServletInputStream(m_TemporaryFile); 
    } 


    @Override 
    public BufferedReader getReader() throws IOException { 
     String enc = getCharacterEncoding(); 
     if(enc == null) enc = "UTF-8"; 
     return new BufferedReader(new InputStreamReader(getInputStream(), enc)); 
    } 
} 

创建一个扩展ServletInputStream的类。调用getInputStream()或getReader()时,您的请求封装类将返回此自定义输入流的实例。自定义输入流类将使用临时文件打开缓存的内容。

public class CachedServletInputStream extends ServletInputStream { 

    private File m_TemporaryFile; 
    private InputStream m_InputStream; 


    public CachedServletInputStream(File temporaryFile) throws IOException { 
     m_TemporaryFile = temporaryFile; 
     m_InputStream = null; 
    } 


    private InputStream acquireInputStream() throws IOException { 
     if(m_InputStream == null) { 
      m_InputStream = new FileInputStream(m_TemporaryFile); 
     } 

     return m_InputStream; 
    } 


    public void close() throws IOException { 
     try { 
      if(m_InputStream != null) { 
       m_InputStream.close(); 
      } 
     } 
     catch(IOException e) { 
      throw e; 
     } 
     finally { 
      m_InputStream = null; 
     } 
    } 


    public int read() throws IOException { 
     return acquireInputStream().read(); 
    } 


    public boolean markSupported() { 
     return false; 
    } 


    public synchronized void mark(int i) { 
     throw new UnsupportedOperationException("mark not supported"); 
    } 


    public synchronized void reset() throws IOException { 
     throw new IOException(new UnsupportedOperationException("reset not supported")); 
    } 
} 

创建一个实现javax.servlet.Filter的一个类实例化自定义请求包装,当它检测到需要缓存的输入数据流的行为的请求。

public class CachedHttpServletRequestFilter implements Filter { 

    public static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type"; 
    public static final String MIME_APPLICATION__X_WWW_FORM_URL_ENCODED = "application/x-www-form-urlencoded"; 


    private File m_TemporaryFolder; 


    public CachedHttpServletRequestFilter() { 
     m_TemporaryFolder = new File(/*...your temporary directory goes here...*/); 
    } 


    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 
     throws IOException, ServletException { 

     if(servletRequest instanceof HttpServletRequest) { 
      HttpServletRequest request = (HttpServletRequest) servletRequest; 
      // Check wether the current request needs to be able to support the body to be read multiple times 
      String contentType = StringHelper.getLowercaseTrimmed(request.getHeader(HTTP_HEADER_CONTENT_TYPE)); 

      if(contentType.equals(MIME_APPLICATION__X_WWW_FORM_URL_ENCODED)) { 
       // Override current HttpServletRequest with custom implementation 
       CachedHttpServletRequest cachedRequest = new CachedHttpServletRequest(request, m_TemporaryFolder); 
       filterChain.doFilter(cachedRequest, servletResponse); 
       cachedRequest.cleanup(); 
       return; 
      } 
     } 

     filterChain.doFilter(servletRequest, servletResponse); 
    } 


    public void init(FilterConfig filterConfig) throws ServletException { 

     try { 
      /* ...initialize where your temporary folder is located here... */ 
      //m_TemporaryFolder = new File(/*...*/); 
     } 
     catch(Exception e) { 
      throw new ServletException(e); 
     } 
    } 


    public void destroy() { 
    } 
} 

如果要检查或移交的请求到这样的API方法之前消耗部分或全部请求主体的,那么你可能无法做到这一点,在这个Servlet API的用户级别。相反,您需要降低一点,一方面需要使用Servlet API,另一方面也可以将其提供给其他API。

具体来说,可以保留通过任何手段你选择从请求的输入流中读取数据,并通过在实施包装HttpServletRequest是大多委托给包装的请求对象提供相同的其他API,但其getInputStream()方法提供了一个可以读取整个请求体的流。