在进行网站开发时需要对页面输出的信息内容进行过滤,于是编写了一个Filter过滤器由于拦截JSP和
Servlet的请求,并使用响应HttpServletResponseWrapper封装类现实响应数据的捕获。在实际应用过程中发现,在获取响应数
据并进行内容过滤后,要把数据写回response对象响应客户端时:
(1)使用tomcat5容器调用response.getOutputStream()方法即可实现,但调用
requonse.getWriter()方法时,输出二进制数据时(图片等内容无法显示)则出现“getWriter() has already
been called for this response”异常。
(2)使用tomcat6容器调用response.getOutputStream()方法时有中文字符会发
生“java.io.CharConversionException:Not an ISO 8859-1
character:”异常,调用requonse.getWriter()方法时可实现文本字符串数据输出,调用
response.getOutputStream()方法可现实字节流数据的输出。
就上述出现的问题进行分析研究,阅读了tomcat6的源代码发现,在调用response.getOutputStream()方法时会判断是否已调用
了requonse.getWriter()方法;相反在调用requonse.getWriter()方法时会判断是否已调用了
response.getOutputStream()方法。
在tomcat5时并没有出现这个问题,使用response.getOutputStream()方法可现实两种数据输出,只是在使用
requonse.getWriter()时发生异常,而在tomcat6下则必须针对不同的数据类型选择相应输出流,这时为什么呢?仔细阅读
tomcat6源代码没有发现问题的根源,给出的参考时:在一次客户端请求的响应动作中,只能调用一种响应输出方法,要么是getWriter()要么是
getOutputStream(),且如果使用getOutputStream()方法输出字符串格式的数据时,中文无法正常通过将发生
“java.io.CharConversionException:Not an ISO 8859-1
character:”异常,在tomcat5下没有对getOutputStream()方法进行严格控制,中文字符串可正常通过。可见tomcat6
的安全机制比tomcat5要严格,对于字符串格式的数据要求使用getWriter()方法输出响应,如果使用了getOutputStream()方
法输出响应,则对输出的字符串数据进验证,要求高字节必须为0,显然中文是无法通过的。
源代码分析:
//tomcat6中对用getOutputStream()方法的实现
public ServletOutputStream getOutputStream()
throws IOException {
if (usingWriter)
throw new IllegalStateException
(sm.getString("coyoteResponse.getOutputStream.ise"));
usingOutputStream = true;
if (outputStream == null) {
outputStream = new CoyoteOutputStream(outputBuffer);
}
return outputStream;
}
//tomcat6中对用getWriter ()方法的实现
public PrintWriter getWriter()
throws IOException {
if (usingOutputStream)
throw new IllegalStateException
(sm.getString("coyoteResponse.getWriter.ise"));
if (Globals.STRICT_SERVLET_COMPLIANCE) {
setCharacterEncoding(getCharacterEncoding());
}
usingWriter = true;
outputBuffer.checkConverter();
if (writer == null) {
writer = new CoyoteWriter(outputBuffer);
}
return writer;
}
//tomcat6中的ServletOutputStream对象的实现,其中print(String s)方法的实现
public void print(String s) throws IOException {
if (s==null) s="null";
int len = s.length();
for (int i = 0; i < len; i++) {
char c = s.charAt (i);
if ((c & 0xff00) != 0) { // high order byte must be zero
String errMsg = lStrings.getString("err.not_iso8859_1");
Object[] errArgs = new Object[1];
errArgs[0] = new Character(c);
errMsg = MessageFormat.format(errMsg, errArgs);
throw new CharConversionException(errMsg);
}
write (c);
}
}
经过上述分析可知tomcat6的安全性提高了两个响应输出流是互锁关系,JSP或Servlet中到底调用了那个方法响应请求?能通过方式知道呢?难道因为提高了安全性就没有办法解决这个问题了吗?
回过头再看看我们的过滤器和封装类的调用关系,在过滤器拦截下请求后,请求将暂时停留在过滤器的处理链中,为了获取响应数据使用了
HttpServletResponseWrapper封装类,目的是将响应的输出写入封装类中,而不是直接写入到
HttpServletResponse对象里返回给客户端,我们要对信息进行过滤,恰恰在获得响应数据后对响应的数据进行内容过滤,然后再送往客户端。
在正常情况下tomcat容器connector处理一次客户端http请求时的处理流程是:创建HttpServletRequest请求、
HttpServletResponse响应和ClientJSPorServlet客户JSP或Servlet对象实例,然后调用service方法完
成处理。加入过滤器Filter和封装类Wrapper后情况有所改变,使调用过程发生了变化。容器在初始化时先创建Filter对象示例,当用客户端请
求时将先转给Filter进行处理(根据过滤器的url-pattern设置拦截那些请求),由Filter过滤器决定滞后的处理流程(或正常处理或中
断、转向等有代码控制);Filter将HttpServletRequest请求和封装后的HttpServletResponseWrapper响应
转给客户JSP或Servlet对象进行处理,处理结束后又回到过滤器,我们在这里加入信息过滤处理过程,然后再将数据写回
HttpServletResponse对象,响应客户端。
由此可见,HttpServletResponseWrapper是一个封装后的“假”响应对象,我们真是使用假的响应对象获取了响应数据,只是在写回数
据调用输出流时发生了问题:在JSP或Servket中调用了getWriter()或getOutputStream()方法时,如果在Wrapper
中现实了(覆盖)这两个方法则调用Wrapper中的方法,如果没有实现,则相当于调用HttpServletResponse中方法;而我们在写回数据
时必须调用getWriter()或getOutputStream()方法。
因此,tomcat6下:
(1)在Wrapper中现实getWriter()方法截获响应数据时,JSP或Servlet调用
getWriter()方法将数据写入Wrapper对象的缓存,处理后再调用HttpServletResponse的getWriter()方法写回
数据正常通过;但调用HttpServletResponse的getOutputStream()时由于数据是字符串流,因此中文无法通过。
(2)在Wrapper中现实getOutputStream
()方法截获响应数据时,JSP或Servlet调用getOutputStream
()方法将数据写入Wrapper对象的缓存,处理后再调用HttpServletResponse的getOutputStream
()方法写回数据正常通过。
如何知道JSP或Servlet对用了那个方法呢?我们在Wrapper中实现getOutputStream()和getWriter()两个方法,并
使用标识变量标识那个方法被调用,在截获响应数据后,判断Wrapper中的那个方法被调用了,以决定何种方式读出响应数据,处理后调用
HttpServletResponse的对应输出流写回数据。
//实现了两种方式的HttpServletResponse封装类
public class FilterResponseWrapper
extends HttpServletResponseWrapper {
/**
* 使用 OutputStream 输出数据
*/
private boolean usingOutputStream=false;
/**
* 使用 PrintWriter 输出数据
*/
private boolean usingWriter=false;
/**
* 输出字节流的对象,用于数据输出
*/
private ByteArrayOutputStream outstream;
/**
* 输出字符串文本的对象,用与JSP页面的输出
*/
private CharArrayWriter output;
/**
* 返回字符串格式数据
* @return String
*/
public String toString() {
return output.toString();
}
/**
* 返回字节数组格式的数据
* @return byte[]
*/
public byte[] toByteArray(){
return outstream.toByteArray();
}
/**
*
* @param response HttpServletResponse
*/
public FilterResponseWrapper(HttpServletResponse response) {
super(response);
output = new CharArrayWriter();
outstream = new ByteArrayOutputStream();
}
/**
*
* @return PrintWriter
*/
public PrintWriter getWriter() {
this.usingWriter=true;
return new PrintWriter(output);
}
/**
*
* @return ServletOutputStream
*/
public ServletOutputStream getOutputStream(){
this.usingOutputStream=true;
return new ByteOutputStream(outstream);
}
/**
*
* @return boolean
*/
public boolean isUsingOutputStream(){
return this.usingOutputStream;
}
/**
*
* @return boolean
*/
public boolean isUsingWriter(){
return this.usingWriter;
}
}
//
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterResponseWrapper wrapper = new FilterResponseWrapper( (HttpServletResponse)
response);
chain.doFilter(request, wrapper);
//使用 PrintWriter 对象输出数据
//进行内容过滤
if(wrapper.isUsingWriter()){
String buffer = word.process(wrapper.toString(),fileName);
PrintWriter output = response.getWriter();
output.print(buffer);
output.flush();
}
//使用 ServletOutputStream 对象输出数据
if(wrapper.isUsingOutputStream()){
ServletOutputStream output = response.getOutputStream();
output.write(wrapper.toByteArray());
output.flush();
}
return;
}
分享到:
相关推荐
MyBatisPlus条件构造器 -Wrapper详解(为知笔记版,可用网页打开),详解wrapper条件构造器的各种使用方法及其扩展类的使用方法。
Java Service Wrapper使用说明
windows 关于qt的数据库操作封装类 以及用例: SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它的功能特点有: 1. ACID事务 2. 零配置 – 无需安装和管理配置 3....
平时有很多项目都可能会使用到cURL,再加上近期疯狂地编写站外的API,例如:facebook, twitter, sina, 人人网等,所以为了方便以后使用,就封装了此类,有兴趣的朋友可以参考一下。
Java Service Wrapper部署说明 Java Service Wrapper是一个开源的软件,能够将Java程序发布到不同平台,并保证其JVM正常运行。 附件是一个实例,有比较详细的说明。更多的可以参考其网站
java程序做成windows服务,使用wrapper-windows,包含了wrapper-windows-x86-32-3.5.13工具包和中文操作说明文档。亲自试验过了。保证可用。
QueryWrapper 常用用法
ADO Wrapper ClassesADO包装类
文章链接:https://blog.csdn.net/liuting52001/article/details/111220596
先运行install,他会在C:\Program Files\RDP Wrapper拷入俩文件rdpwrap.dll和rdpwrap.ini 然后运行update,他会自动网上下载更新的rdpwrap.ini 但是我测试有个问题,1809之前的版本全部正常,比如2016LTSB 2015LTSB...
mybatis反向工程.根据数据库表生成wrapper xml文件和类
windows wrapper 相关文件,包括wrapper相关文档、配置说明文档。实现通过windows服务的方式启动java项目
10G以太网 wrapper 千兆万兆以太网控制器 wrapper 万兆
apktool_2.6.0 (附Windows、Linux、Mac下的安装说明和wrapper包装脚本).zip 2021最新版
使用Wrapper将Java Application程序封装为Windows下面的服务
去CSDN下载所谓破解出现以下错误:wrapper | Licensed to (null) for (null) wrapper | wrapper | Launching a JVM... jvm 1 | WrapperManager: Initializing... jvm 1 | WrapperJNI Error: Not licensed to use ...
ssl_wrapper, 将普通TCP流量封装到SSL中 ssl_wrapperSSL包装器是在安全SSL隧道内加密任何不安全的网络通信的应用程序,并提供。SSL包装器打开侦听端口,并以两种模式将流量转发到指定的目标主机:侦听端口为 SSL,...
Kompex SQLite Wrapper Source
使用Shady DOM polyfill时有关CSS封装的注意事项如果打算使用Shady DOM *.vue ,建议在*.vue文件中使用代替<style scoped> ,因为它不像Shadow DOM那样提供真实的样式封装,因此外部样式表可能会影响您的组件...
tcp_wrapper tcp_wrapper