我需要一个servlet过滤器来捕获所有输入,然后修改输入,在每个表单中插入一个特殊标记。想象一下,过滤器与所有请求相关联(例如url-pattern=*
)。我有捕获内容的代码,但似乎RequestWrapper
不足以捕获所有输入。某些输入返回零字节,然后我无法将该内容“流”回给用户。例如,我们仍在使用Struts 1.3.10,并且任何Struts代码都没有正确“捕获”,我们得到零字节内容。我相信这是因为Struts如何处理前锋。如果请求中涉及转发,我想知道下面的捕获代码是否有效。以下是所有代码,您是否有一种方法可以捕获用于流式传输给用户的任何类型的内容。
<filter>
<filter-name>Filter</filter-name>
<filter-class>mybrokenCaptureHtml.TokenFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
package mybrokenCaptureHtml;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class TokenFilter implements Filter {
@Override
public void destroy() {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
try {
final MyResponseWrapper responseWrapper = new MyResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, responseWrapper);
// **HERE DEPENDING ON THE SERVLET OR APPLICATION CODE (STRUTS, WICKET), the response returns an empty string //
// Especiall struts, is there something in their forwards that would cause an error?
final byte [] bytes = responseWrapper.toByteArray();
// For some applications that hit this filter
// ZERO BYTE DATA is returned, this is bad, but SOME
// CODE, the data is captured.
final String origHtml = new String(bytes);
final String newHtml = origHtml.replaceAll("(?i)</(\\s)*form(\\s)*>", "<input type=\"hidden\" name=\"zval\" value=\"fromSiteZ123\"/></form>");
response.getOutputStream().write(newHtml.getBytes());
} catch(final Exception e) {
e.printStackTrace();
}
return;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
static class MyResponseWrapper extends HttpServletResponseWrapper {
private final MyPrintWriter pw = new MyPrintWriter();
public byte [] toByteArray() {
return pw.toByteArray();
}
public MyResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public PrintWriter getWriter() {
return pw.getWriter();
}
@Override
public ServletOutputStream getOutputStream() {
return pw.getStream();
}
private static class MyPrintWriter {
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private PrintWriter pw = new PrintWriter(baos);
private ServletOutputStream sos = new MyServletStream(baos);
public PrintWriter getWriter() {
return pw;
}
public ServletOutputStream getStream() {
return sos;
}
byte[] toByteArray() {
return baos.toByteArray();
}
}
private static class MyServletStream extends ServletOutputStream {
ByteArrayOutputStream baos;
MyServletStream(final ByteArrayOutputStream baos) {
this.baos = baos;
}
@Override
public void write(final int param) throws IOException {
baos.write(param);
}
}
}
}
这就是Struts应用程序的示例,对于某些应用程序(不是Struts),我们可能会捕获内容。但对于像下面这样的应用程序,HTML内容会返回零字节,但应该有内容。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="/WEB-INF/struts-nested.tld" prefix="nested"%>
<html:html>
<head>
<title><bean:message key="myApp.customization.title" /></title>
<LINK rel="stylesheet" type="text/css" href="../theme/styles.css">
</head>
<body>
<html:form styleId="customizemyAppForm" method="post" action="/customizemyApp.do?step=submit">
<html:submit onclick="javascript:finish(this.form);" styleClass="input_small"> <bean:message key="myApp.customization.submit" /> </html:submit>
<input type="button" styleClass="input_small" width="80" style="WIDTH:80px" name="<bean:message key="myApp.customization.cancel" />" value="<bean:message key="myApp.customization.cancel" />" onclick="javascript:cancel();">
</html:form>
</body>
</html:html>
我怀疑MyResponseWrapper
和MyPrintWriter
不够强大,无法捕获所有类型的内容。
可行的示例servlet( a ):
response.getOutputStream().write(str.getBytes());
不起作用的示例servlet( b ):
response.getWriter().println("<html>data</html>");
示例 a 会获得捕获,示例 b 则不会。
这是一个改进的包装类,大多数应用程序都可以工作但是现在有些struts应用程序,只有一些响应被发送到浏览器。
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class ByteArrayResponseWrapper extends HttpServletResponseWrapper {
private PrintWriter output = null;
private ServletOutputStream outStream = null;
private static final String NL = System.getProperty("line.separator");
public ByteArrayResponseWrapper(final HttpServletResponse response) {
super(response);
}
public String getDocument() {
InputStream in = null;
try {
in = this.getInputStream();
if (in != null) {
return getDocument(in);
}
} catch(final Exception ee) {
// ee.print;StackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
//e.prin;tStackTrace();
}
}
}
return "";
}
protected String getDocument(final InputStream in) {
final StringBuffer buf = new StringBuffer();
BufferedReader br = null;
try {
String line = "";
br = new BufferedReader(new InputStreamReader(getInputStream(), this.getCharacterEncoding()));
while ((line = br.readLine()) != null) {
buf.append(line).append(NL);
}
} catch(final IOException e) {
//e.print;StackTrace();
} finally {
try {
if (br != null) {
br.close();
}
} catch (IOException ex) {
}
}
return buf.toString();
}
@Override
public PrintWriter getWriter() throws IOException {
if (output == null) {
output = new PrintWriter(new OutputStreamWriter(getOutputStream(), this.getCharacterEncoding()));
}
return output;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (outStream == null) {
outStream = new BufferingServletOutputStream();
}
return outStream;
}
public InputStream getInputStream() throws IOException {
final BufferingServletOutputStream out = (BufferingServletOutputStream) getOutputStream();
return new ByteArrayInputStream(out.getBuffer().toByteArray());
}
/**
* Implementation of ServletOutputStream that handles the in-memory
* buffering of the response content
*/
public static class BufferingServletOutputStream extends ServletOutputStream {
ByteArrayOutputStream out = null;
public BufferingServletOutputStream() {
this.out = new ByteArrayOutputStream();
}
public ByteArrayOutputStream getBuffer() {
return out;
}
public void write(int b) throws IOException {
out.write(b);
}
public void write(byte[] b) throws IOException {
out.write(b);
}
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
@Override
public void close() throws IOException {
out.close();
super.close();
}
@Override
public void flush() throws IOException {
out.flush();
super.flush();
}
}
}
我找到了一个可能的解决方案,在getInputStream
方法中,看起来我在所有对象上调用'close',例如outStream.flush()
和outStream.close()
然后{{1 }和out.flush()
...看起来最后的字节写得正确。它不直观但看起来很有效。
答案 0 :(得分:5)
您的初始方法失败,因为PrintWriter
wraps给定的ByteArrayOutputStream
BufferedWriter
有internal character buffer of 8192 characters,您永远不会flush()
从ByteArrayOutputStream
获取字节之前的缓冲区。换句话说,当少于约8KB的数据写入响应的getWriter()
时,包裹的ByteArrayOutputStream
实际上永远不会被填充;即一切仍然在那个内部字符缓冲区中,等待被刷新。
修复方法是在flush()
中toByteArray()
之前执行MyPrintWriter
来电:
byte[] toByteArray() {
pw.flush();
return baos.toByteArray();
}
这样,内部字符缓冲区将被刷新(即实际将所有内容写入包装流)。这也完全解释了为什么它在写getOutputStream()
时起作用,这一步即没有使用PrintWriter
,并且没有任何东西在某个内部缓冲区中被缓冲。
无关具体问题:这种方法存在一些严重问题。在构造PrintWriter
期间,它并不尊重response character encoding(您实际上应该将ByteArrayOutputStream
包裹在OutputStreamWriter
中而不是可以进行字符编码)并依赖于getWriter()
平台默认,换句话说,任何书面的Unicode字符都可能以Mojibake这种方式结束,因此这种方法不适合世界统治。
此外,这种方法可以在同一个响应中同时调用getOutputStream()
和public class CapturingResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream capture;
private ServletOutputStream output;
private PrintWriter writer;
public CapturingResponseWrapper(HttpServletResponse response) {
super(response);
capture = new ByteArrayOutputStream(response.getBufferSize());
}
@Override
public ServletOutputStream getOutputStream() {
if (writer != null) {
throw new IllegalStateException("getWriter() has already been called on this response.");
}
if (output == null) {
output = new ServletOutputStream() {
@Override
public void write(int b) throws IOException {
capture.write(b);
}
@Override
public void flush() throws IOException {
capture.flush();
}
@Override
public void close() throws IOException {
capture.close();
}
};
}
return output;
}
@Override
public PrintWriter getWriter() throws IOException {
if (output != null) {
throw new IllegalStateException("getOutputStream() has already been called on this response.");
}
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(capture, getCharacterEncoding()));
}
return writer;
}
@Override
public void flushBuffer() throws IOException {
super.flushBuffer();
if (writer != null) {
writer.flush();
}
else if (output != null) {
output.flush();
}
}
public byte[] getCaptureAsBytes() throws IOException {
if (writer != null) {
writer.close();
}
else if (output != null) {
output.close();
}
return capture.toByteArray();
}
public String getCaptureAsString() throws IOException {
return new String(getCaptureAsBytes(), getCharacterEncoding());
}
}
,同时considered an illegal state(正是为了避免这种缓冲和编码)麻烦)。
更新,这里是对响应包装器的完全重写,显示正确的方式,希望以比您自己编写的代码更自我解释的方式远:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
CapturingResponseWrapper capturingResponseWrapper = new CapturingResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, capturingResponseWrapper);
String content = capturingResponseWrapper.getCaptureAsString(); // This uses response character encoding.
String replacedContent = content.replaceAll("(?i)</form(\\s)*>", "<input type=\"hidden\" name=\"zval\" value=\"fromSiteZ123\"/></form>");
response.getWriter().write(replacedContent); // Don't ever use String#getBytes() without specifying character encoding!
}
以下是您应该如何使用它:
{{1}}