寻找使用 servlet 过滤器将内容插入响应的示例
Posted
技术标签:
【中文标题】寻找使用 servlet 过滤器将内容插入响应的示例【英文标题】:Looking for an example for inserting content into the response using a servlet filter 【发布时间】:2013-01-22 01:54:41 【问题描述】:我一直在网络和 *** 中搜索某人使用 servlet 过滤器将内容插入响应的示例,但只能找到人们捕获/压缩输出和/或更改标头的示例。我的目标是在所有 html 响应的结束
【参考方案1】:我正在使用的代码库在处理响应时调用 getOutputStream 方法,而不是 getWriter,因此其他答案中包含的示例没有帮助。这是一个更完整的答案,它适用于 OutputStream 和 PrintWriter,如果写入器被访问两次,甚至会正确出错。这源自一个很好的例子,DUMP REQUEST AND RESPONSE USING JAVAX.SERVLET.FILTER。
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class MyFilter implements Filter
private FilterConfig filterConfig = null;
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();
public class CharResponseWrapper extends HttpServletResponseWrapper
private ByteArrayPrintWriter output;
private boolean usingWriter;
public CharResponseWrapper(HttpServletResponse response)
super(response);
usingWriter = false;
output = new ByteArrayPrintWriter();
public byte[] getByteArray()
return output.toByteArray();
@Override
public ServletOutputStream getOutputStream() throws IOException
// will error out, if in use
if (usingWriter)
super.getOutputStream();
usingWriter = true;
return output.getStream();
@Override
public PrintWriter getWriter() throws IOException
// will error out, if in use
if (usingWriter)
super.getWriter();
usingWriter = true;
return output.getWriter();
public String toString()
return output.toString();
public void init(FilterConfig filterConfig) throws ServletException
this.filterConfig = filterConfig;
public void destroy()
filterConfig = null;
public void doFilter(
ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
CharResponseWrapper wrappedResponse = new CharResponseWrapper(
(HttpServletResponse)response);
chain.doFilter(request, wrappedResponse);
byte[] bytes = wrappedResponse.getByteArray();
if (wrappedResponse.getContentType().contains("text/html"))
String out = new String(bytes);
// DO YOUR REPLACEMENTS HERE
out = out.replace("</head>", "WTF</head>");
response.getOutputStream().write(out.getBytes());
else
response.getOutputStream().write(bytes);
【讨论】:
一个重要提示:您需要使用 HttpServletResponse.setContentLength 方法根据您的响应正文更改更新响应标头,否则不匹配可能导致客户端出现未知行为。 @snowindy 该信息绝对应该添加到主要答案中 @snowindy 非常重要的注意事项!但是,我必须在 进行实际替换之前调整内容长度(即在调用 response.getOutputStream().write() 之前),否则它只会被忽略。 @Serban 它被缓冲到某个特定值,相当小,但每个 servlet 容器可配置。当缓冲区溢出发生时,响应起始块会自动发送到客户端。 此解决方案不再可用(至少在此发布的表单中)。 ByteArrayServletStream 需要实现两个抽象方法,并且 init 函数不能再被调用,因为它是 final 的。【参考方案2】:您需要实现 HttpServletResponseWrapper 来修改响应。请参阅此文档The Essentials of Filters,它有一个转换响应的示例,这比您想要的要多
编辑
我尝试了一个带有响应过滤器的简单 Servlet,它运行良好。 Servlet 输出字符串Test
,响应过滤器将字符串filtered
附加到它上面,最后当我从浏览器运行时,我得到响应Test filtered
,这就是您要实现的目标。
我确实在 Apache Tomcat 7 上运行了以下代码,并且它正常工作。
Servlet:
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
response.getWriter().println("Test");
过滤器:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException
System.out.println("BEFORE filter");
PrintWriter out = response.getWriter();
CharResponseWrapper responseWrapper = new CharResponseWrapper(
(HttpServletResponse) response);
chain.doFilter(request, responseWrapper);
String servletResponse = new String(responseWrapper.toString());
out.write(servletResponse + " filtered"); // Here you can change the response
System.out.println("AFTER filter, original response: "
+ servletResponse);
CharResponseWrapper(与文章完全相同)
public class CharResponseWrapper extends HttpServletResponseWrapper
private CharArrayWriter output;
public String toString()
return output.toString();
public CharResponseWrapper(HttpServletResponse response)
super(response);
output = new CharArrayWriter();
public PrintWriter getWriter()
return new PrintWriter(output);
web.xml
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/TestServlet</url-pattern>
</servlet-mapping>
<filter>
<filter-name>TestFilter</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TestFilter</filter-name>
<url-pattern>/TestServlet/*</url-pattern>
</filter-mapping>
【讨论】:
按照那个例子,我得到了这个异常 - java.lang.IllegalStateException: getWriter() has already been called for this response 如果能贴出异常的代码和stacktrace就更好了。 添加了响应的包装。我的 CharResponseWrapper 是 The Essentials of Filters 链接中的一个精确副本。 我在 chain.doFilter(wrappedRequest, WrappedResponse);看起来底层代码正在调用 getOutputStream(),它使用相同的编写器。 我用一个简单的 Servlet 对其进行了测试,它工作正常,请参阅我的更新答案【参考方案3】:iTech 的答案部分对我有用,这是基于该响应..
但您必须注意,似乎 一些网络服务器(和 AppEngine 标准)在第一次调用 chain.doFilter 后关闭了 outputStream 在过滤器中..
因此,当您需要在预先保存的 PrintWritter 上进行写入时,流将关闭并且您会看到一个空白屏幕。 (我什至没有收到错误来了解发生了什么)。
所以我的解决方案是创建一个“虚拟”ServletOutputStream 并返回到我的 ResponseWrapper 的 getOutputStream 方法中。
这些更改加上 iTech 的解决方案使我能够在 json 响应中的 html 中插入完全呈现的 jsp 响应(正确转义引号等冲突字符)。
这是我的代码:
我的过滤器
@WebFilter("/json/*")
public class Myfilter implements Filter
@Override
public void init(FilterConfig filterConfig) throws ServletException
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
//Save original writer
PrintWriter out = response.getWriter();
//Generate a response wrapper with a different output stream
ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
//Process all in the chain (=get the jsp response..)
chain.doFilter(request, responseWrapper);
//Parse the response
out.write("BEFORE"+responseWrapper.toString()+"AFTER"); //Just + for clear display, better use a StringUtils.concat
@Override
public void destroy()
我的ResponseWrapper:
public class ResponseWrapper extends HttpServletResponseWrapper
private StringWriter output;
public String toString()
return output.toString();
public ResponseWrapper(HttpServletResponse response)
super(response);
//This creates a new writer to prevent the old one to be closed
output = new StringWriter();
public PrintWriter getWriter()
return new PrintWriter(output,false);
@Override
public ServletOutputStream getOutputStream() throws IOException
//This is the magic to prevent closing stream, create a "virtual" stream that does nothing..
return new ServletOutputStream()
@Override
public void write(int b) throws IOException
@Override
public void setWriteListener(WriteListener writeListener)
@Override
public boolean isReady()
return true;
;
【讨论】:
那么当过滤器使用“getOutputStream”然后写入它时,这些数据会去哪里?【参考方案4】:太棒了!但请更新内容长度,
String out = new String(bytes);
// DO YOUR REPLACEMENTS HERE
out = out.replace("</head>", "WTF</head>");
response.setContentLength(out.length());
response.getOutputStream().write(out.getBytes());
【讨论】:
以上是关于寻找使用 servlet 过滤器将内容插入响应的示例的主要内容,如果未能解决你的问题,请参考以下文章
Grails spring security、servlet 过滤器和响应