Filter高级开发(三)——压缩响应正文内容
来源:互联网 发布:医疗器械国家数据库 编辑:程序博客网 时间:2024/06/06 14:13
使用Decorator设计模式增强response对象
Servlet API中提供了response对象的Decorator设计模式的默认实现类HttpServletResponseWrapper,HttpServletResponseWrapper类实现了response接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的response对象的对应方法,以避免用户在对response对象进行增强时需要实现response接口中的所有方法。
response增强案例——压缩响应正文内容
应用HttpServletResponseWrapper对象,压缩响应正文内容。
具体思路:通过filter向目标页面传递一个自定义的response对象。在自定义的response对象中,重写getOutputStream方法和getWriter方法,使目标资源调用此方法输出页面内容时,获得的是我们自定义的ServletOutputStream对象。在我们自定义的ServletOuputStream对象中,重写write方法,使写出的数据写出到一个buffer中。当页面完成输出后,在filter中就可得到页面写出的数据,从而我们可以调用GzipOutputStream对数据进行压缩后再写出给浏览器,以此完成响应正文的压缩功能。
现在我们就来编写这样的一个压缩过滤器,老实说编写这样的一个东西,还真是特别麻烦的,但也不要怕,一步、两步、一步、两步…就能写出来了。
我们要写这样的一个压缩过滤器,可以从后面来推导,按照人的习惯来说,终究是要调用一个Servlet来将数据输出给浏览器的,而这样的Servlet也是大概要有这句代码的:
response.getOutputStream().write(......);
或
response.getWriter().write(......);
试想别人一调用response的getOutputStream方法和getWriter方法,我就应该返回我自己的流给他们,当别人拿到这样的流对象后,再调用它的方法写数据时,我应控制数据写到一个缓冲里面去。顺理成章的,应使用Decorator设计模式增强response对象,通过我们要编写的压缩过滤器向目标页面传递一个自定义的response对象,在自定义的response对象中,重写getOutputStream方法和getWriter方法。
首先考虑我们在自定义的response对象中重写getOutputStream方法。
怎么来编写自定义的response对象呢?有了上面的想法,我们试着来写代码来实现这样的想法。
class MyResponse extends HttpServletResponseWrapper { private ByteArrayOutputStream bout = new ByteArrayOutputStream(); // 缓冲流,用字节数组作为缓冲 private HttpServletResponse response; public MyResponse(HttpServletResponse response) { super(response); this.response = response; } @Override public ServletOutputStream getOutputStream() throws IOException { return new XxxServletOutputStream(bout); } // 得到底层流中的数据 public byte[] getBuffer() { return bout.toByteArray(); }}
大概来说,当别人一调用getOutputStream()方法时,这个方法应返回我自己的ServletOutputStream流对象出去,当别人调用我的ServletOutputStream流对象写数据的时候,我应控制数据写到缓冲流bout里面去。
注意:当别人一调用getOutputStream()方法的话,由这个方法的返回值决定了我必须要返回一个ServletOutputStream对象,我这时就不能返回原来的ServletOutputStream对象了,应该new出一个自己的ServletOutputStream对象返回出去。我要new一个自己的ServletOutputStream对象,那么就应该去查阅Servlet API,可以查到ServletOutputStream是一个抽象类,不可new。既然不可new,那么这时就要写一个子类继承ServletOutputStream,并实现其抽象方法即可。于是,我们自定义的response对象就应该是这样的:
class MyResponse extends HttpServletResponseWrapper { private ByteArrayOutputStream bout = new ByteArrayOutputStream(); // 缓冲流,用字节数组作为缓冲 private HttpServletResponse response; public MyResponse(HttpServletResponse response) { super(response); this.response = response; } @Override public ServletOutputStream getOutputStream() throws IOException { return new MyServletOutputStream(bout); } // 得到底层流中的数据 public byte[] getBuffer() { return bout.toByteArray(); }}class MyServletOutputStream extends ServletOutputStream { private ByteArrayOutputStream bout; public MyServletOutputStream(ByteArrayOutputStream bout) { this.bout = bout; } @Override public void write(int b) throws IOException { bout.write(b); } @Override public boolean isReady() { // TODO Auto-generated method stub return false; } @Override public void setWriteListener(WriteListener listener) { // TODO Auto-generated method stub }}
写完这样的自定义response对象,还要通过我们写的压缩过滤器传递给目标页面,当页面完成输出后,在压缩过滤器中就可得到页面写出的数据,从而我们可以调用GzipOutputStream对数据进行压缩后再写出给浏览器,以此完成响应正文的压缩功能。
public class GzipFllter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; MyResponse myResponse = new MyResponse(response); chain.doFilter(request, myResponse); // 不能直接放行,应增强response的getOutputStream()方法和response的getWriter()方法再放行 // 取出缓冲中的数据压缩后输出 byte[] out = myResponse.getBuffer(); // 得到目标资源的输出 System.out.println("压之前:" + out.length); byte[] gzipout = gzip(out); System.out.println("压之后:" + gzipout.length); response.setHeader("Content-Encoding", "gzip"); response.setHeader("Content-Length", gzipout.length+""); response.getOutputStream().write(gzipout); } // 压缩数据 public byte[] gzip(byte[] b) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); GZIPOutputStream gout = new GZIPOutputStream(bout); gout.write(b); gout.close(); return bout.toByteArray(); } // 内部类 class MyResponse extends HttpServletResponseWrapper { private ByteArrayOutputStream bout = new ByteArrayOutputStream(); // 缓冲流,用字节数组作为缓冲 private HttpServletResponse response; public MyResponse(HttpServletResponse response) { super(response); this.response = response; } @Override public ServletOutputStream getOutputStream() throws IOException { return new MyServletOutputStream(bout); } // 得到底层流中的数据 public byte[] getBuffer() { return bout.toByteArray(); } } class MyServletOutputStream extends ServletOutputStream { private ByteArrayOutputStream bout; public MyServletOutputStream(ByteArrayOutputStream bout) { this.bout = bout; } @Override public void write(int b) throws IOException { bout.write(b); } @Override public boolean isReady() { // TODO Auto-generated method stub return false; } @Override public void setWriteListener(WriteListener listener) { // TODO Auto-generated method stub } } @Override public void destroy() { // TODO Auto-generated method stub }}
接着不要忘记在web.xml中配置压缩过滤器哟。
<filter> <filter-name>CharacterEncodingFilter2</filter-name> <filter-class>cn.itcast.web.filter.example.CharacterEncodingFilter2</filter-class></filter><filter-mapping> <filter-name>CharacterEncodingFilter2</filter-name> <url-pattern>/*</url-pattern></filter-mapping><filter> <filter-name>GzipFllter</filter-name> <filter-class>cn.itcast.web.filter.example.GzipFllter</filter-class></filter><filter-mapping> <filter-name>GzipFllter</filter-name> <url-pattern>/*</url-pattern></filter-mapping>
不要奇怪以上的配置里面怎么会多了一个CharacterEncodingFilter2,这是一个解决全站中文乱码的过滤器,关于怎么编写这样的一个过滤器可以参见我的笔记Filter高级开发(一)——使用Decorator模式包装request对象解决get和post请求方式下的中文乱码问题哟!
既然编写好了这样的压缩过滤器,那么我们顺便编写一个Servlet来输出数据给浏览器显示。
public class ServletDemo3 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; response.getOutputStream().write(data.getBytes()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }}
测试,我们通过Firefox浏览器访问该ServletDemo3,浏览器显示正常。你高兴太早了,哈哈!!!千万不要以为这样测试完就万事大吉了,对于咱中国人来说,始终绕不开的一个话题,那就是中文数据乱码的问题了,哎!咱中国人写代码真是好麻烦啊!试想:如果ServletDemo3的代码是这样的:
public class ServletDemo3 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String data = "中国中国中国中国 中国"; response.getOutputStream().write(data.getBytes()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }}
再次通过Firefox浏览器访问该ServletDemo3,浏览器显示就会不正常了,出现中文乱码,出现此现象的原因也并不非我们写的压缩过滤器有问题,而是data.getBytes()
这句代码有问题,data.getBytes()
这句代码把”中国中国中国中国 中国”字符串转成一个字节数组,这时不可避免就要查阅一个码表,可它竟然查的是本地平台的码表——即查的是GBK码表,但response的头在CharacterEncodingFilter2过滤器设置的是UTF-8码表,这时明显写出去的是GBK的数据,而你控制浏览器以UTF-8的方式打开,那肯定就会出现中文乱码的情况了啊!所以我们应将ServletDemo3的代码修改为:
public class ServletDemo3 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String data = "中国中国中国中国 中国"; response.getOutputStream().write(data.getBytes("UTF-8")); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }}
至此,我们重写自定义的response对象的getOutputStream()方法就告一段落了。接下来考虑我们在自定义的response对象中重写getWriter方法。
有了想法,我们就来编码实现:
// 内部类class MyResponse extends HttpServletResponseWrapper { private ByteArrayOutputStream bout = new ByteArrayOutputStream(); // 缓冲流,用字节数组作为缓冲 private HttpServletResponse response; public MyResponse(HttpServletResponse response) { super(response); this.response = response; } @Override public ServletOutputStream getOutputStream() throws IOException { return new MyServletOutputStream(bout); } @Override public PrintWriter getWriter() throws IOException { return new PrintWriter(bout); } // 得到底层流中的数据 public byte[] getBuffer() { return bout.toByteArray(); }}
如果像上面那样重写自定义的response对象的getWriter()方法,你觉得可行吗?你说行傻子都不信啊!这样写代码是不行的,由于PrintWriter这个流是一个包装流,如果写出的数据量太小,它是不会写到底层流里面去的,而是写到缓冲里面,你若这样写代码,等会你从底层流bout里面拿数据,就会拿不到数据哟!若要解决,则须在自定义的response对象中维护一个PrintWriter对象,并要修改得到底层流中数据的方法——getBuffer(),那么修改完后的代码即为:
// 内部类class MyResponse extends HttpServletResponseWrapper { private ByteArrayOutputStream bout = new ByteArrayOutputStream(); // 缓冲流,用字节数组作为缓冲 private PrintWriter pw; private HttpServletResponse response; public MyResponse(HttpServletResponse response) { super(response); this.response = response; } @Override public ServletOutputStream getOutputStream() throws IOException { return new MyServletOutputStream(bout); } @Override public PrintWriter getWriter() throws IOException { pw = new PrintWriter(bout); return pw; } // 得到底层流中的数据 public byte[] getBuffer() { if (pw != null) { pw.close(); } return bout.toByteArray(); }}
同样我们要测试,编写一个Servlet来输出数据给浏览器显示。
public class ServletDemo3 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; response.getWriter().write(data); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }}
测试,我们通过Firefox浏览器访问该ServletDemo3,浏览器显示正常。输出英文数据总是正常的,而若我们输出中文数据呢?就像下面这样:
public class ServletDemo3 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String data = "中国中国中国中国 中国"; response.getWriter().write(data); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }}
终究还是会显示乱码的,而此时出现中文乱码的原因就是我们写的压缩过滤器有问题了,因为response.getWriter()
得到的是我new的PrintWriter对象,然后PrintWriter.write("中国中国中国中国 中国");
控制这串中文字符写到底层流ByteArrayOutputStream中,要想把该字符串往底层流(即字节数组)里面写,PrintWriter这个类内部在写字符串”中国中国中国中国 中国”出去时,需要查码表,但查的是GBK码表,反正查的不是UTF-8码表。若要解决,则须修改自定义的response对象的getWriter()方法:
public PrintWriter getWriter() throws IOException { pw = new PrintWriter(new OutputStreamWriter(bout, response.getCharacterEncoding())); // response.getCharacterEncoding()返回的是"UTF-8" return pw;}
至此,我们重写自定义的response对象的getWriter()方法就告一段落了。
我大概还是怕有人坚持不到这里,也怕有人看的糊里糊涂,遂在此处贴出压缩过滤器的完整代码:
public class GzipFllter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; MyResponse myResponse = new MyResponse(response); chain.doFilter(request, myResponse); // 不能直接放行,应增强response的getOutputStream()方法和response的getWriter()方法再放行 // 取出缓冲中的数据压缩后输出 byte[] out = myResponse.getBuffer(); // 得到目标资源的输出 System.out.println("压之前:" + out.length); byte[] gzipout = gzip(out); System.out.println("压之后:" + gzipout.length); response.setHeader("Content-Encoding", "gzip"); response.setHeader("Content-Length", gzipout.length+""); response.getOutputStream().write(gzipout); } // 压缩数据 public byte[] gzip(byte[] b) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); GZIPOutputStream gout = new GZIPOutputStream(bout); gout.write(b); gout.close(); return bout.toByteArray(); } // 内部类 class MyResponse extends HttpServletResponseWrapper { private ByteArrayOutputStream bout = new ByteArrayOutputStream(); // 缓冲流,用字节数组作为缓冲 private PrintWriter pw; private HttpServletResponse response; public MyResponse(HttpServletResponse response) { super(response); this.response = response; } @Override public ServletOutputStream getOutputStream() throws IOException { return new MyServletOutputStream(bout); } @Override public PrintWriter getWriter() throws IOException { pw = new PrintWriter(new OutputStreamWriter(bout, response.getCharacterEncoding())); // response.getCharacterEncoding()返回的是"UTF-8" return pw; } // 得到底层流中的数据 public byte[] getBuffer() { if (pw != null) { pw.close(); } return bout.toByteArray(); } } class MyServletOutputStream extends ServletOutputStream { private ByteArrayOutputStream bout; public MyServletOutputStream(ByteArrayOutputStream bout) { this.bout = bout; } @Override public void write(int b) throws IOException { bout.write(b); } @Override public boolean isReady() { // TODO Auto-generated method stub return false; } @Override public void setWriteListener(WriteListener listener) { // TODO Auto-generated method stub } } @Override public void destroy() { // TODO Auto-generated method stub }}
虽然我们的压缩过滤器是写好了,但是在web.xml文件中配置该过滤器时,我们拦截的是所有资源,即有人访问服务器中的xxx.jsp、xxx.jpg(png/gif)等时,都会将数据压缩之后再发送给浏览器,我们还要考虑若是有人下载一部电影,电影的容量大概在1G左右,这么大的数据量,要用一个字节数组去存储,势必内存会爆炸,所以我们不应该拦截所有资源,而是有选取的拦截一些资源,又因为gzip这种压缩格式只对文本格式数据的压缩效率最高,所以我们可以在web.xml文件中这样配置压缩过滤器:
<filter> <filter-name>GzipFllter</filter-name> <filter-class>cn.itcast.web.filter.example.GzipFllter</filter-class></filter><filter-mapping> <filter-name>GzipFllter</filter-name> <url-pattern>*.jsp</url-pattern> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher></filter-mapping><filter-mapping> <filter-name>GzipFllter</filter-name> <url-pattern>*.html</url-pattern></filter-mapping><filter-mapping> <filter-name>GzipFllter</filter-name> <url-pattern>*.js</url-pattern></filter-mapping><filter-mapping> <filter-name>GzipFllter</filter-name> <url-pattern>*.css</url-pattern></filter-mapping>
我们应该尤其注意该过滤器所拦截的jsp资源被Servlet容器调用的方式,不管是用户直接访问页面,还是目标资源是通过RequestDispatcher的forward()方法访问,该过滤器都应予以拦截。
- Filter高级开发(三)——压缩响应正文内容
- javaweb学习总结(四十三)——Filter高级开发
- javaweb学习总结(四十三)——Filter高级开发
- javaweb学习总结(四十三)——Filter高级开发
- javaweb学习总结(四十三)——Filter高级开发
- javaweb学习总结(四十三)——Filter高级开发
- javaweb学习总结(四十三)——Filter高级开发
- JavaWeb学习总结(四十三)——Filter高级开发
- 记性不如烂笔头33-利用java过滤器实现压缩响应正文内容
- 演示gzip压缩响应正文数据
- python爬虫——获取正文内容
- 9Filter高级开发2--实现全站压缩
- 利用Filter压缩HTTP响应
- javaweb学习总结——Filter高级开发
- Filter高级开发(四)——缓存数据到内存
- javaweb学习总结——Filter高级开发
- Filter高级开发(一)
- Filter(过滤器)高级开发
- Redux系列01:从一个简单例子了解action、store、reducer
- linux多线程学习(六)——信号量实现同步
- Construct Binary Tree from Inorder and Postorder Traversal
- jvm笔记4--类文件结构
- leetcode 35. Search Insert Position
- Filter高级开发(三)——压缩响应正文内容
- GL_CLAMP GL_CLAMP_TO_EDGE GL_CLAMP_TO_BORDER
- Caffe 初学拾遗(四) CUDA 框架说明
- JSON 对象与字符串相互转换
- ural 1306 (heap推排序)
- 四川旅游总览(一)
- PaaS的新世界:在颠覆与被颠覆中高效竞争
- Egit的merge合并冲突具体解决方法
- 高性能高并发服务的瓶颈及突破思路