如何在Java筛选器中更改HTTP响应内容长度头
我编写了一个JavaHTTP响应过滤器,在其中修改HTTP响应主体。由于我正在更改HTTP响应主体,因此必须根据新内容更新响应中的HTTP内容长度头文件。我是用下面的方法做的如何在Java筛选器中更改HTTP响应内容长度头,java,http,servlets,filter,content-length,Java,Http,Servlets,Filter,Content Length,我编写了一个JavaHTTP响应过滤器,在其中修改HTTP响应主体。由于我正在更改HTTP响应主体,因此必须根据新内容更新响应中的HTTP内容长度头文件。我是用下面的方法做的 response.setContentLength( next.getBytes().length ); hear next是一个字符串 但是,此方法无法设置HTTP响应的新内容长度。有人能告诉我用Java过滤器完成它的正确方法是什么吗 package com.test; import java.io.Buffered
response.setContentLength( next.getBytes().length );
hear next是一个字符串
但是,此方法无法设置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 请注意,如果在调用筛选器之前有内容写入响应正文,则将忽略
内容长度
标题。在写出任何内容之前,都需要设置此标题,因此,如果您发现它没有被添加,这就是原因
像这样使用它:
new ContentLengthFilter("contentLengthFilter_", new File("/tmp/fileCache"))
ContentLengthFilter.java
content length response header描述响应正文的长度。您得到的错误是什么?请发布servlet代码。从PrintWriter获得的Writer没有设置编码类型的工具,它使用与平台相关的默认编码。在mosy平台上,默认编码为UTF-16,因此next.getBytes().length将返回next长度的2倍。我已成功使用了第二个选项(胡萝卜2)-谢谢。
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();
}
}
/*
* 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
}
}