简体   繁体   English

当您无法控制第二次读取它的代码时,如何多次读取ServletInputStream

[英]How to read a ServletInputStream more than once when you don't have control of the code that reads it a second time

I have a ServletInputStream that I need to read more than once (the code that reads it the second time is in an API I don't control). 我有一个需要不止一次读取的ServletInputStream (第二次读取它的代码是在我无法控制的API中)。 When using IOUtils.copy , it still seems that the stream is only allowed to be read once (marked/reset is not allowed on the stream). 使用IOUtils.copy ,似乎只允许读取一次流(在流上不允许标记/重置)。

Any ideas? 有任何想法吗? Thanks. 谢谢。

Create a class that extends HttpServletRequestWrapper. 创建一个扩展HttpServletRequestWrapper的类。 This class will cache the contents on the original request's input stream in a temporary file. 此类将在原始请求的输入流中缓存临时文件中的内容。

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

Create a class that extends ServletInputStream. 创建一个扩展ServletInputStream的类。 Your request wrapper class will return an instance of this custom input stream when getInputStream() or getReader() is called. 当调用getInputStream()或getReader()时,您的请求包装器类将返回此自定义输入流的实例。 The custom input stream class will open the cached contents using the temporary file. 自定义输入流类将使用临时文件打开缓存的内容。

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

Create a class that implements javax.servlet.Filter that instantiates your custom request wrapper when it detects a request that requires cached input stream behavior. 创建一个实现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() {
    }
}

If you want to examine or consume part or all of the request body before handing off the request to this other API method, then you probably cannot do that at the level of a user of the Servlet API. 如果要在将请求移交给其他API方法之前检查或使用部分或全部请求主体,那么您可能无法在Servlet API的用户级别执行此操作。 Instead, you need to work a little lower, consuming the Servlet API on the one hand, but also serving it to the other API. 相反,您需要降低工作量,一方面使用Servlet API,另一方面将其提供给其他API。

Specifically, you can preserve the data read from the request's input stream by whatever means you choose, and provide the same to the other API by wrapping the HttpServletRequest in an implementation that mostly delegates to the wrapped request object, but whose getInputStream() method provides a stream from which the whole request body can be read. 具体来说,您可以通过您选择的任何方式保留从请求的输入流中读取的数据,并通过 HttpServletRequest 包装在主要委托给包装的请求对象但其getInputStream()方法提供的实现中, 其提供给其他API。可以从中读取整个请求正文的流。

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM