首页 > WEB开发 > 后台开发 > 过滤器(Filter)
2014
08-28

过滤器(Filter)

5、Filter高级开发:装饰设计模式

在Filter.doFilter中可以得到代表用户请求和响应的request、response对象,因此在编程中可以使用装饰模式(Decorator)对request、response对象进行包装,改写其中的某些方法,增强功能,再把包装对象传给目标资源,从而实现一些特殊需求。

在《数据库连接池(DataSource)》一文中讲过改写Connection.close功能的几种方法。在Filter的高级开发中也需要对request、response的进行功能上的改写。

Sun公司提供了HttpServletRequestWrapper、HttpServletResponseWrapper两个对HttpServletRequest、HttpServletRequest对象的简单包装类【作用同《数据库连接池(DataSource)》一文中的ConnectionAdapter 】如果要改写request或response对象的功能,只需继承HttpServletRequestWrapper、HttpServletResponseWrapper类覆写对应方法即可。

6、高级案例(4个)

1)SetCharacterEncodingFilter:解决全站中文乱码

上面的第一个案例解决了Post请求参数和响应输出的中文乱码问题,本案例是它的增强版:增加了get请求参数的中文乱码解决。

解决方法是改写request对象的getParameter方法。如何改写request对象的getParameter方法:装饰设计模式,见代码中的MyHttpServletRequest类。

class EncodingHttpServletRequest extends HttpServletRequestWrapper{

	public EncodingHttpServletRequest(HttpServletRequest request) {
		super(request);
	}
	//改写request对象的getParameter方法
	public String getParameter(String name) {
		String value = super.getParameter(name);
		if(value == null) return null;
		//解决get请求方式时的乱码问题
		if("get".equalsIgnoreCase(super.getMethod())){
			try {
				return new String(value.getBytes("ISO-8859-1"),super.getCharacterEncoding());
			} catch (UnsupportedEncodingException e) {
				throw new RuntimeException(e);
			}
		}
		return value;
	}
}

public class SetCharacterEncodingFilter implements Filter {
		………………
		//doFilter()
		request.setCharacterEncoding(encoding);//解决POST请求参数的乱码
		response.setCharacterEncoding(encoding);//输出流编码
		request.setContentType("text/html;charset="+encoding);

		EncodingHttpServletRequest ecRequest = new EncodingHttpServletRequest(request);
		//往下传递的request对象是改写了getParameter方法的包装对象
		chain.doFilter(ecRequest, request);}
		………………
}

2)DirtyWordsFilter:过滤脏话

核心还是对request对象中getParameter方法的改写:

class DirtyWordsHttpServletRequest extends HttpServletRequestWrapper{

	//脏话库
	private String[] strs = {"日","插","草","操"};
	public DirtyWordsHttpServletRequest(HttpServletRequest request) {
		super(request);
	}

	public String getParameter(String name) {
		String value = super.getParameter(name);
		if(value==null) return null;
		//遍历脏话库,对脏话用*替换
		for(String s:strs){
			value = value.replace(s, "*");
		}
		return value;
	}
}

3)HtmlFilter:过滤html标记

思路同DirtyWordsFilter,htmlFilter直接从Tomcat下的webapps\examples\WEB-INF\classes\util\HTMLFilter.java拷贝。

class HtmlHttpServletRequest extends HttpServletRequestWrapper{

	public HtmlHttpServletRequest(HttpServletRequest request) {
		super(request);
	}

	public String getParameter(String name) {
		String value = super.getParameter(name);
		if(value == null) return value;
		//对HTML标记中的标记符号转义
		value = htmlFilter(value);
		return value;
	}

	//下面的一大段代码拷贝自webapps\examples\WEB-INF\classes\ util\HTMLFilter.java
	private String htmlFilter(String message) {
        if (message == null)
            return (null);

        char content[] = new char[message.length()];
        message.getChars(0, message.length(), content, 0);
        StringBuffer result = new StringBuffer(content.length + 50);
        for (int i = 0; i < content.length; i++) {
            switch (content[i]) {
            case '<':
                result.append("&lt;");
                break;
            case '>':
                result.append("&gt;");
                break;
            case '&':
                result.append("&amp;");
                break;
            case '"':
                result.append("&quot;");
                break;
            default:
                result.append(content[i]);
            }
        }
        return (result.toString());
	}
}

4)GzipFilter:全站压缩(有难度)

下面的代码在一个普通的Java类中演示了Gzip压缩的过程:

String str = "压缩我吧!!!啊啊啊!!!!!!!!!!!";

// 获得字节数组
byte[] b = str.getBytes("utf-8");
System.out.println("压缩前大小:" + b.length);

//☆☆☆☆☆对数据进行压缩☆☆☆☆☆
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gout = new GZIPOutputStream(out);
gout.write(b);
gout.close(); //这句话一定要有

b = out.toByteArray();
System.out.println("压缩后大小:" + b.length);

在web工程中对响应输出进行压缩前需要判断客户端是否支持Gzip压缩(请求头的Accept-Encoding字段是否含有gzip压缩),如果支持gzip压缩则进行压缩,并在响应头字段中告诉客户端响应消息为gzip压缩。

因为是对响应输出进行压缩,所以压缩代码应放在chain.doFilter()之后。全站压缩的最大难点在于如何拦截目标资源的响应输出。假设目标资源为一个Servlet,且Servlet中的相应输出代码如下:

ServletOutputStream out = response.getOutputStream();
out.write(b);

则截取该Servlet的响应输出思路如下:

① 继承HttpServletResponseWrapper,改写getOutputStream方法,返回一个自己定义的输出流。

② 该输出流继承ServletOutputStream,并覆写其中的write(int)方法,每次都向指定的地方输出(很关键,即下文的ByteArrayOutputStream对象),这样就完成了响应消息的截获。

下面的代码演示了这一过程,在改写response功能时,定义了一个ByteArrayOutputStream对象baos,并提供了获取该字节数组的getBufferedBytes方法。在getOutputStream()中,返回一个包装了baos的ServletOutputStream对象。

class GzipHttpServletResponse extends HttpServletResponseWrapper{
	//截获输出流,放入baos
	private ByteArrayOutputStream baos = new ByteArrayOutputStream();

	private PrintWriter pw = null;

	public GzipHttpServletResponse(HttpServletResponse response) {
		super(response);
	}

	//返回包装了baos的ServletOutputStream对象,用户每次的输出都会被“截获”到baos中
	public ServletOutputStream getOutputStream() throws IOException {
		return new MyServletOutputStream(baos);
	}

	//截获字符流:放到baos中
	public PrintWriter getWriter() throws IOException {
		pw = new PrintWriter(new OutputStreamWriter(baos,super.getCharacterEncoding()));
		return pw;
	}

	//获取截获的字节数组
	public byte[] getBufferedBytes() {
		try {
			if(pw!=null){
				pw.close();
			}
			baos.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return baos.toByteArray();
	}
}

//改写ServletOutputStream --> 包装流
class MyServletOutputStream extends ServletOutputStream{

	private ByteArrayOutputStream baos = new ByteArrayOutputStream();

	public MyServletOutputStream(ByteArrayOutputStream baos){
		this.baos = baos;
	}
	public void write(int b) throws IOException {
		baos.write(b);
	}
}

过滤器doFilter方法中的部分代码:

GzipHttpServletResponse gzipResponse = new GzipHttpServletResponse(response);

chain.doFilter(request, gzipResponse);

byte b[] = gzipResponse.getBufferedBytes();//得到原始的数据为编码的关键点
System.out.println("压缩前大小:"+b.length);
//判断客户是否支持gzip压缩
String acceptEncoding = request.getHeader("Accept-Encoding");
if(acceptEncoding!=null&&acceptEncoding.contains("gzip")){	//支持
	ByteArrayOutputStream out = new ByteArrayOutputStream();
	GZIPOutputStream gout = new GZIPOutputStream(out);
	gout.write(b);
	gout.close();

	b = out.toByteArray();//压缩后的数据
	System.out.println("压缩后大小:"+b.length);

	//告知浏览器压缩方式
	response.setHeader("Content-Encoding", "gzip");
	response.setContentLength(b.length);//告知客户端,正文的长度
}
response.getOutputStream().write(b);

在配置web.xml的过滤范围时要注意,一般只过滤字符文件(*.jsp,*.html,*.css,*.js等),因为*.jpg,*.png,*.zip等都已经是压缩过的文件了(再压缩没意义,只会浪费服务器资源)。

<filter>
	<filter-name>GzipFilter</filter-name>
	<filter-class>org.flyne.filter.advanced.GzipFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>GzipFilter</filter-name>
	<url-pattern>*.jsp</url-pattern>
	<url-pattern>*.html</url-pattern>
	<url-pattern>*.js</url-pattern>
	<url-pattern>*.css</url-pattern>
</filter-mapping>

留下一个回复

你的email不会被公开。