如何读取一个ServletInputStream不止一次,当你没有控制第二次读取它的代码
问题描述:
我有一个ServletInputStream
,我需要多次读取(第二次读取它的代码是我不控制的API)。当使用IOUtils.copy
时,它似乎仍然只允许读取一次(在流上不允许标记/重置)。如何读取一个ServletInputStream不止一次,当你没有控制第二次读取它的代码
任何想法?谢谢。
答
创建一个扩展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()
方法提供了一个可以读取整个请求体的流。
在字节数组中读取它,在此字节数组上创建ByteArrayInputStream,并将此ByteArrayInputStream传递给API。但是你应该解释为什么你需要这个以及你想要在更高层次上做什么,因为可能有不同的解决方案。 – 2015-01-26 21:13:48
谢谢。我从HttpServletRequest对象获取流,该对象正在用作API方法的输入。看起来他们直接从请求对象获取输入流,该请求对象在该点已经被读取。 – mstrom 2015-01-26 21:17:41
此API如何处理流?你使用什么代码处理流? – 2015-01-26 21:18:57