我编写了一个 Java HTTP 响应过滤器,在其中修改 HTTP 响应正文。由于我正在更改 HTTP 响应正文,因此我必须根据新内容更新响应中提交的 http 内容长度 header 。我按照以下方式进行。
response.setContentLength( next.getBytes().length );
听到下一个是一个字符串
但是,此方法无法设置 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);
}
最佳答案
这是执行此操作的 Java 示例。它将响应存储在临时文件中,响应完成后该文件将被删除。此时它仅用于提供静态文件,因为它通过 url 路径临时缓存文件。请注意,它通过 url 路径将文件的长度存储在内存中,并在后续请求中使用它以避免 I/O。
请注意,如果在调用过滤器之前向响应正文写入了某些内容,则您的 Content-Length
header 将被忽略。需要在写出任何内容之前设置此 header ,因此如果您发现它没有被添加,这就是原因。
像这样使用它:
new ContentLengthFilter("contentLengthFilter_", new File("/tmp/fileCache"))
ContentLengthFilter.java
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();
}
}
用于执行此操作的另一个很棒的示例过滤器,可以在项目中独立使用,是 this ContentLengthFilter.java来自Carrot2 github上的项目。请注意,它工作得很好,但在写出每个文件时将其存储在内存中,因此如果您有大文件,则需要考虑不同的方法。
这使用带有字节流的响应包装器来解决问题,因此这也确保了 Transfer-Encoding: Chunked
不会被过滤器链中的其他过滤器/代码设置,并在设置后覆盖您的 Content-Length
header 。您可以通过使用较大的文件进行测试来验证这一点,因为它们通常会在响应中分块。
我还将在此处复制文件的内容,以确保它不会成为损坏的链接。
/*
* Carrot2 project.
*
* Copyright (C) 2002-2010, Dawid Weiss, Stanisław Osiński.
* All rights reserved.
*
* Refer to the full license file "carrot2.LICENSE"
* in the root folder of the repository checkout or at:
* http://www.carrot2.org/carrot2.LICENSE
*/
package org.carrot2.webapp;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* Buffer the output from filters below and set accurate <code>Content-Length</code>
* header. This header is required by flash, among others, to display progress
* information.
*/
public class ContentLengthFilter implements Filter
{
private final static class BufferingOutputStream extends ServletOutputStream
{
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@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 buffering;
public BufferingHttpServletResponse(HttpServletResponse response)
{
super(response);
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 (hasContentLength())
{
outputStream = super.getOutputStream();
}
else
{
outputStream = new BufferingOutputStream();
buffering = 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 (hasContentLength())
{
writer = super.getWriter();
}
else
{
writer = new PrintWriter(new OutputStreamWriter(
getOutputStream(), getCharacterEncoding()), false);
}
acquired = StreamType.WRITER;
return writer;
}
/**
* Returns <code>true</code> if the user set <code>Content-Length</code>
* explicitly.
*/
private boolean hasContentLength()
{
return super.containsHeader("Content-Length");
}
/**
* Push out the buffered data.
*/
public void pushBuffer() throws IOException
{
if (!buffering)
throw new IllegalStateException("Not buffering.");
BufferingOutputStream bufferedStream =
(BufferingOutputStream) outputStream;
byte [] buffer = bufferedStream.baos.toByteArray();
httpResponse.setContentLength(buffer.length);
httpResponse.getOutputStream().write(buffer);
}
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException
{
final HttpServletResponse response = (HttpServletResponse) resp;
final BufferingHttpServletResponse wrapped =
new BufferingHttpServletResponse(response);
chain.doFilter(req, wrapped);
if (wrapped.buffering)
{
wrapped.pushBuffer();
}
}
public void destroy()
{
// Empty
}
public void init(FilterConfig config) throws ServletException
{
// Empty
}
}
关于java - 如何更改Java Filter中的HTTP响应内容长度 header ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39767239/