在进行网站开发时需要对页面输出的信息内容进行过滤,于是编写了一个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;
}
分享到:
相关推荐
【作品名称】:泰迪杯 : 基于 python 实现 运输车辆安全驾驶行为的分析 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【项目介绍】: 在车辆运输过程中,不良驾驶行为主要包括疲劳驾驶、急加速、急减速、怠速预热、 超长怠速、熄火滑行、超速、急变道等。 针对以上运输车辆的不良驾驶行为,给出不同不良驾驶行为的判别标准,行车安全评价模型如下: 疲劳驾驶:连续行车时间超过4小时。 提取数据思路:若某一行acc_state列值为1并且gps_speed列数值大于0,则认为汽车开始启动,继续扫描数据表,直到寻找到一行gps_speed列的数值为0,则认为汽车已经处于停止状态,再根据location_time列由两个数据获取时间间隔,判断是否属于疲劳驾驶。 急加速、急减速:每两个经纬度间汽车的加速度达到或者超过20km/s^2。两个经纬度间汽车的加速 【资源声明】:本资源作为“参考资料”而不是“定制需求”,代码只能作为参考,不能完全复制照搬。需要有一定的基础看懂代码,自行调试代码并解决报错,能自行添加功能修改代码。
基于springboot的校园社交平台源码数据库文档.zip
scipy-1.7.1-cp37-cp37m-linux_armv7l.whl
java源码资源EJB 模拟银行ATM流程及操作源代码提取方式是百度网盘分享地址
pillow-11.0.0-cp39-cp39-linux_armv7l.whl
java面试视频资源微服务架构之Spring Cloud Eureka 场景分析与实战提取方式是百度网盘分享地址
基于springboot+vue的音乐播放系统源码数据库文档.zip
matplotlib-3.5.0-cp37-cp37m-linux_armv7l.whl
onnxruntime-1.16.2-cp311-cp311-win_amd64.whl
基于springboot复兴村医疗管理系统源码数据库文档.zip
环境说明: 开发软件:VS 2017 (版本2017以上即可,不能低于2017) 数据库:SqlServer2008r2(数据库版本无限制,都可以导入) 开发模式:mvc
onnxruntime-win-x64-gpu-1.19.2.zip
bimdata_api_client-4.0.7-py3-none-any.whl
基于springboot的实验室开放管理系统源码数据库文档.zip
Pillow-9.2.0-cp39-cp39-linux_armv7l.whl
STM32神舟III号例程源码STM32芯片按键点灯-无防抖(STM32神舟III号-寄存器版)提取方式是百度网盘分享地址
基于springboot医疗废物管理系统源码数据库文档.zip
基于springboot的车辆保险理赔平台源码数据库文档.zip