我编写了一个Java HTTP响应过滤器,我在其中修改了HTTP响应主体。由于我正在更改HTTP响应主体,因此我必须根据新内容更新响应的http内容长度标头。我是按照以下方式做的。
response.setContentLength( next.getBytes().length );
接下来是string
但是,此方法无法设置HTTP响应的新内容长度。有人可以告诉我在Java过滤器中完成它的正确方法吗
package com.test;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class DumpFilter implements Filter {
private static class ByteArrayServletStream extends ServletOutputStream {
ByteArrayOutputStream baos;
ByteArrayServletStream(ByteArrayOutputStream baos) {
this.baos = baos;
}
public void write(int param) throws IOException {
baos.write(param);
}
}
private static class ByteArrayPrintWriter {
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private PrintWriter pw = new PrintWriter(baos);
private ServletOutputStream sos = new ByteArrayServletStream(baos);
public PrintWriter getWriter() {
return pw;
}
public ServletOutputStream getStream() {
return sos;
}
byte[] toByteArray() {
return baos.toByteArray();
}
}
private class BufferedServletInputStream extends ServletInputStream {
ByteArrayInputStream bais;
public BufferedServletInputStream(ByteArrayInputStream bais) {
this.bais = bais;
}
public int available() {
return bais.available();
}
public int read() {
return bais.read();
}
public int read(byte[] buf, int off, int len) {
return bais.read(buf, off, len);
}
}
private class BufferedRequestWrapper extends HttpServletRequestWrapper {
ByteArrayInputStream bais;
ByteArrayOutputStream baos;
BufferedServletInputStream bsis;
byte[] buffer;
public BufferedRequestWrapper(HttpServletRequest req) throws IOException {
super(req);
InputStream is = req.getInputStream();
baos = new ByteArrayOutputStream();
byte buf[] = new byte[1024];
int letti;
while ((letti = is.read(buf)) > 0) {
baos.write(buf, 0, letti);
}
buffer = baos.toByteArray();
}
public ServletInputStream getInputStream() {
try {
bais = new ByteArrayInputStream(buffer);
bsis = new BufferedServletInputStream(bais);
} catch (Exception ex) {
ex.printStackTrace();
}
return bsis;
}
public byte[] getBuffer() {
return buffer;
}
}
private boolean dumpRequest;
private boolean dumpResponse;
public void init(FilterConfig filterConfig) throws ServletException {
dumpRequest = Boolean.valueOf(filterConfig.getInitParameter("dumpRequest"));
dumpResponse = Boolean.valueOf(filterConfig.getInitParameter("dumpResponse"));
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest)servletRequest;
BufferedRequestWrapper bufferedRequest= new BufferedRequestWrapper(httpRequest);
if (dumpRequest) {
System.out.println("REQUEST -> " + new String(bufferedRequest.getBuffer()));
}
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final ByteArrayPrintWriter pw = new ByteArrayPrintWriter();
HttpServletResponse wrappedResp = new HttpServletResponseWrapper(response) {
public PrintWriter getWriter() {
return pw.getWriter();
}
public ServletOutputStream getOutputStream() {
return pw.getStream();
}
};
filterChain.doFilter(bufferedRequest, wrappedResp);
byte[] bytes = pw.toByteArray();
String s = new String(bytes);
String next = "test message";
response.getOutputStream().write(next.getBytes());
///response.setHeader("Content-Length", String.valueOf(next.length()));
response.setContentLength( next.getBytes().length );
// if (dumpResponse) System.out.println("RESPONSE -> " + s);
}
public void destroy() {}
}
上面给出的是Filter类,但您可能不需要阅读整个类。以下是doFilter代码,我正在修改http主体并设置内容长度。
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest)servletRequest;
BufferedRequestWrapper bufferedRequest= new BufferedRequestWrapper(httpRequest);
if (dumpRequest) {
System.out.println("REQUEST -> " + new String(bufferedRequest.getBuffer()));
}
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final ByteArrayPrintWriter pw = new ByteArrayPrintWriter();
HttpServletResponse wrappedResp = new HttpServletResponseWrapper(response) {
public PrintWriter getWriter() {
return pw.getWriter();
}
public ServletOutputStream getOutputStream() {
return pw.getStream();
}
};
filterChain.doFilter(bufferedRequest, wrappedResp);
byte[] bytes = pw.toByteArray();
String s = new String(bytes);
String next = "test message";
response.getOutputStream().write(next.getBytes());
///response.setHeader("Content-Length", String.valueOf(next.length()));
response.setContentLength( next.getBytes().length );
// if (dumpResponse) System.out.println("RESPONSE -> " + s);
}
答案 0 :(得分:1)
这是一个执行此操作的Java示例。它将响应存储在临时文件中,该文件在响应完成时将被删除。它仅用于此时提供静态文件,因为它通过url路径临时缓存文件。请注意,它通过url路径将文件的长度存储在内存中,并在后续请求中使用它来避免I / O.
请注意,如果在调用过滤器之前有某些内容写入响应正文,则会忽略您的Content-Length
标头。这个标题需要在任何内容写出之前设置,所以如果你发现它没有被添加,那就是原因。
像这样使用它:
new ContentLengthFilter("contentLengthFilter_", new File("/tmp/fileCache"))
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.commons.io.IOUtils;
/*
* This filter adds a "Content-Length" header to all responses.
* It does this by caching the response to a temporary file, which
* is deleted immediately after the response completes.
*
* It caches the size of the file to a hashmap, and uses that for
* any matching requests that it encounters in the future, to decrease
* the amount of I/O required. So the first request to a file is the
* only one that does file I/O, the rest use the cache.
*
* Note that it ignores queryString params when comparing responses.
* If this is important to you, then you should override the getFilenameForUrl
* method as required.
*/
public class ContentLengthFilter implements Filter
{
protected ServletContext servletContext;
protected final File tempDir;
protected final Map<String, Long> contentLengths = new HashMap<String, Long>();
protected final String filenamePrefix;
public static final String CONTENT_LENGTH = "Content-Length";
public ContentLengthFilter(String filenamePrefix, File tempDir)
{
this.filenamePrefix = filenamePrefix;
this.tempDir = tempDir;
this.tempDir.mkdirs();
}
private final static class BufferingOutputStreamFile extends ServletOutputStream
{
private FileOutputStream baos;
public BufferingOutputStreamFile(File file)
{
try
{
baos = new FileOutputStream(file);
}
catch (FileNotFoundException e)
{
baos = null;
}
}
@Override
public void write(int b) throws IOException
{
baos.write(b);
}
@Override
public void write(byte[] b) throws IOException
{
baos.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
baos.write(b, off, len);
}
}
private final static class BufferingHttpServletResponse extends HttpServletResponseWrapper
{
private enum StreamType
{
OUTPUT_STREAM, WRITER
}
private final HttpServletResponse httpResponse;
private StreamType acquired;
private PrintWriter writer;
private ServletOutputStream outputStream;
private boolean savedResponseToTmpFile;
private File file;
public BufferingHttpServletResponse(HttpServletResponse response, File file)
{
super(response);
this.file = file;
httpResponse = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException
{
if (acquired == StreamType.WRITER)
throw new IllegalStateException("Character stream already acquired.");
if (outputStream != null)
return outputStream;
if (alreadyHasContentLength())
{
outputStream = super.getOutputStream();
}
else
{
outputStream = new BufferingOutputStreamFile(file);
savedResponseToTmpFile = true;
}
acquired = StreamType.OUTPUT_STREAM;
return outputStream;
}
@Override
public PrintWriter getWriter() throws IOException
{
if (acquired == StreamType.OUTPUT_STREAM)
throw new IllegalStateException("Binary stream already acquired.");
if (writer != null)
return writer;
if (alreadyHasContentLength())
{
writer = super.getWriter();
}
else
{
writer = new PrintWriter(new OutputStreamWriter(getOutputStream(), getCharacterEncoding()), false);
}
acquired = StreamType.WRITER;
return writer;
}
private boolean alreadyHasContentLength()
{
return super.containsHeader(CONTENT_LENGTH);
}
public void copyTmpFileToOutput() throws IOException
{
if (!savedResponseToTmpFile)
throw new IllegalStateException("Not saving response to temporary file.");
// Get the file, and write it to the output stream
FileInputStream fis = new FileInputStream(file);
ServletOutputStream sos;
try
{
long contentLength = file.length();
httpResponse.setHeader(CONTENT_LENGTH, contentLength + "");
sos = httpResponse.getOutputStream();
IOUtils.copy(fis, sos);
}
finally
{
IOUtils.closeQuietly(fis);
fis.close();
}
}
}
protected String getFilenameForUrl(HttpServletRequest request)
{
String result = filenamePrefix + request.getRequestURI();
result = hashString(result);
return result;
}
// Simple way to make a unique filename for an url. Note that
// there could be collisions of course using this approach,
// so use something better (e.g. MD5) if you want to avoid
// collisions entirely. This approach is more readable, and
// is why it's used.
protected String hashString(String input)
{
String result = input.replaceAll("[^0-9A-Za-z]", "_");
return result;
}
public void log(Object o)
{
System.out.println(o);
}
protected boolean setContentLengthUsingMap(String key, FilterChain chain, HttpServletResponse response) throws IOException, ServletException
{
Long contentLength = contentLengths.get(key);
if (contentLength == null)
return false;
response.setHeader(CONTENT_LENGTH, contentLength + "");
log("content-length from map:" + key + ", length:" + contentLength + ", entries:" + contentLengths.size());
return true;
}
protected void writeFileToResponse(String filenameFromUrl, HttpServletRequest request, File file, BufferingHttpServletResponse wrappedResponse) throws IOException
{
Long contentLength = file.length();
if (contentLength > 0)
{
log("Response written to temporary_file=" + filenameFromUrl + ", contentLength=" + contentLength);
contentLengths.put(filenameFromUrl, contentLength);
}
else
{
log("Skipping caching response for temporary_file=" + filenameFromUrl + ", contentLength=" + contentLength);
}
wrappedResponse.copyTmpFileToOutput();
String contentType = servletContext.getMimeType(request.getRequestURI());
wrappedResponse.setContentType(contentType);
}
protected void deleteTempFileIfExists(File file)
{
if (file.exists())
{
try
{
file.delete();
}
catch (Exception e)
{
log(e);
}
}
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException
{
final HttpServletResponse response = (HttpServletResponse) resp;
final HttpServletRequest request = (HttpServletRequest) req;
final String filenameFromUrl = getFilenameForUrl(request);
// If we've downloaded this file before, we saved it's
// size, so write that out and skip caching the file locally
// as it's not required.
if (setContentLengthUsingMap(filenameFromUrl, chain, response))
{
chain.doFilter(request, response);
return;
}
// We've never seen this request before, so download the response
// to a temporary file, then write that file and it's
// file size to the response.
final File file = new File(tempDir, filenameFromUrl + UUID.randomUUID());
try
{
final BufferingHttpServletResponse wrappedResponse = new BufferingHttpServletResponse(response, file);
chain.doFilter(req, wrappedResponse);
if (wrappedResponse.savedResponseToTmpFile)
{
writeFileToResponse(filenameFromUrl, request, file, wrappedResponse);
}
}
finally
{
deleteTempFileIfExists(file);
}
}
public void destroy()
{
this.servletContext = null;
}
public void init(FilterConfig config) throws ServletException
{
this.servletContext = config.getServletContext();
}
}
用于执行此操作的另一个很棒的示例过滤器,可以从项目中单独使用,来自github上this ContentLengthFilter.java项目的Carrot2。请注意,它工作得很好,但在写出时将每个文件存储在内存中,因此如果您有大文件,则需要考虑不同的方法。
这使用带有字节流的响应包装器来解决问题,因此这也可以确保Transfer-Encoding: Chunked
不会被过滤器链中的其他过滤器/代码设置,并覆盖您的{{ 1}}标题设置时。您可以通过使用较大的文件对其进行测试来验证,因为它们通常会在响应中进行分块。
我也会在这里复制文件的内容,以确保它不会成为断开的链接。
Content-Length