过滤器Filter,是介于Servlet之前,可拦截过滤浏览器对Servlet的请求,也可以改变Servlet对浏览器的响应。
一、过滤器的概念
现在有以下几个请求:
1、针对所有的Servlet,产品经理想要了解从请求到响应之间的时间差。
2、针对某些特定的页面,希望仅有几个用户才能知道。
3、基于安全方面,用户希望输入的特定字符必须过滤并且替换为无害的字符。
4、请求与响应的编码从Big5改为UTF-8。
思路分析:
1、运行Servlet的service()方法前后,各记录一个时间,计算其差。:过滤器
2、运行Servlet的service()方法前,验证是否为允许的用户。
3、运行Servlet的service()方法前,对请求参数进行字符过滤与替换。
4、运行Servlet的service()方法前,对请求与响应对象设置编码。
性能评测、用户验证、字符替换、编码设置这类需求,应该设置为独立的元件,随时可以加入到应用程序中,这类元件就像过滤器,安插在浏览器与Servlet之间。如图、
二、过滤器的实现与设置
必须实现Filter接口,并用@WebFilter标注。Filter接口主要实现三个方法:init()、doFilter()、destroy()
FilterConfig类似于Servlet接口init()方法参数上的ServletConfig,FilterConfig设置信息的代表对象。如果定义过滤器时设置了初始参数,可以通过FilterConfig的getInitParameter()方法来取得初始参数。
Filter接口的doFilter()方法,类似Servlet接口的service()方法,当请求来到容器,而容器发现调用Servlet的service()方法前,就会调用该过滤器的doFilter()方法。可以再doFilter()方法中进行service()方法的前置处理,而后决定是否调用FilterChain的doFilter()方法。
如果调用了FilterChain的doFilter()方法,就会执行下一个过滤器,如果没有下一个过滤器了,就调用Servlet的service()方法,如果因为某个情况没有执行下一个过滤器(如,用户没有通过验证)而没有调用FilterChain的doFilter()方法,则请求就不会交给目标Servlet。FilterChain运行后会以堆栈顺序返回。
例1:实现简单的性能评测过滤器,记录请求和响应间的时间差,了解Servlet处理请求到响应所花的时间。
1 package ServletAPI; 2 3 import java.io.IOException; 4 import javax.servlet.Filter; 5 import javax.servlet.FilterChain; 6 import javax.servlet.FilterConfig; 7 import javax.servlet.ServletException; 8 import javax.servlet.ServletRequest; 9 import javax.servlet.ServletResponse; 10 import javax.servlet.annotation.WebFilter; 11 import javax.servlet.annotation.WebInitParam; 12 13 /** 14 * Servlet Filter implementation class PerformanceFilter 15 */ 16 @WebFilter( 17 filterName = "performance", 18 urlPatterns = { "/*" } 19 ) 20 public class PerformanceFilter implements Filter { 21 private FilterConfig fConfig; 22 /** 23 * Default constructor. 24 */ 25 public PerformanceFilter() { 26 // TODO Auto-generated constructor stub 27 } 28 29 /** 30 * @see Filter#destroy() 31 */ 32 public void destroy() { 33 // TODO Auto-generated method stub 34 } 35 36 /** 37 * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) 38 */ 39 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 40 // TODO Auto-generated method stub 41 // place your code here 42 43 // pass the request along the filter chain 44 long begin=System.currentTimeMillis(); 45 chain.doFilter(request, response); 46 fConfig.getServletContext().log("Request process in "+(System.currentTimeMillis()-begin)+" millseconds."); 47 } 48 49 /** 50 * @see Filter#init(FilterConfig) 51 */ 52 public void init(FilterConfig fConfig) throws ServletException { 53 // TODO Auto-generated method stub 54 this.fConfig=fConfig; 55 } 56 57 }
三、请求封装器
1、实现字符替换过滤器
假设用户不希望在留言中输入<a href=’http://openhome.cc’>openhome.cc</a>这样的信息,希望直接变成超链接。希望将一些html过滤掉,如将<、>角括号置换为HTML实体字符< >
EscapeWrapper类继承了HttpServletRequestWrapper,并定义了一个接受HttpSevletRequest的构造器,真正的HttpSevletRequest将通过此构造器传入,必须使用super()调用HttpSevletRequestWrapper接受HttpSevletRequest的构造器,之后要取得被封装的HttpSevletRequest,则可以调用getRequest()方法。
1 package ServletAPI; 2 import javax.servlet.http.HttpServletRequest; 3 import javax.servlet.http.HttpServletRequestWrapper; 4 5 6 import org.apache.commons.lang3.StringEscapeUtils; 7 8 public class EscapeWrapper extends HttpServletRequestWrapper { 9 public EscapeWrapper(HttpServletRequest request){ 10 super(request);//必须调用父类的构造器,传入HttpServletRequest的实例。 11 } 12 public String getParameter(String name){//重新定义getParameter方法 13 String value=getRequest().getParameter(name); 14 return StringEscapeUtils.escapeHtml4(value);//将取得的请求参数值进行字符替换 15 } 16 }
可以使用这个请求封装器类搭配过滤器,以进行字符过滤操作的服务。例如:
1 package ServletAPI; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.annotation.WebFilter; 12 import javax.servlet.http.HttpServlet; 13 import javax.servlet.http.HttpServletRequest; 14 15 /** 16 * Servlet Filter implementation class EscapeFilter 17 */ 18 @WebFilter("/*") 19 public class EscapeFilter implements Filter { 20 21 /** 22 * Default constructor. 23 */ 24 public EscapeFilter() { 25 // TODO Auto-generated constructor stub 26 } 27 28 /** 29 * @see Filter#destroy() 30 */ 31 public void destroy() { 32 // TODO Auto-generated method stub 33 } 34 35 /** 36 * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) 37 */ 38 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 39 // TODO Auto-generated method stub 40 // place your code here 41 42 // pass the request along the filter chain 43 HttpServletRequest requestWrapper=new EscapeWrapper((HttpServletRequest) request);//将原请求对象包裹至EscapeWrapper中。 44 45 chain.doFilter(request, response);//将EscapeWrapper对象当做请求对象传入doFilter() 46 } 47 48 /** 49 * @see Filter#init(FilterConfig) 50 */ 51 public void init(FilterConfig fConfig) throws ServletException { 52 // TODO Auto-generated method stub 53 } 54 55 }
运行结果:
2、实现编码设置过滤器
实现方式:过滤器+封装器
1 package ServletAPI; 2 3 import java.io.UnsupportedEncodingException; 4 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpServletRequestWrapper; 7 8 public class EncodingWrapper extends HttpServletRequestWrapper{ 9 private String ENCODING; 10 public EncodingWrapper(HttpServletRequest request,String ENCODING){ 11 super(request); 12 this.ENCODING=ENCODING; 13 } 14 public String getParameter(String name){ 15 String value=getRequest().getParameter(name); 16 if(value!=null){ 17 try { 18 byte[] b=value.getBytes("ISO-8859-1"); 19 value=new String(b,ENCODING);//编码转换 20 } catch (UnsupportedEncodingException e) { 21 // TODO: handle exception 22 throw new RuntimeException(e); 23 } 24 } 25 return value; 26 } 27 }
1 package ServletAPI; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.annotation.WebFilter; 12 import javax.servlet.annotation.WebInitParam; 13 import javax.servlet.http.HttpServletRequest; 14 15 import org.omg.CORBA.PUBLIC_MEMBER; 16 17 /** 18 * Servlet Filter implementation class EncodingFilter 19 */ 20 @WebFilter( 21 urlPatterns = { "/*" }, 22 initParams = { 23 @WebInitParam(name = "ENCODING", value = "UTF-8") 24 }) 25 public class EncodingFilter implements Filter { 26 private String ENCODING; 27 /** 28 * Default constructor. 29 */ 30 public EncodingFilter() { 31 // TODO Auto-generated constructor stub 32 } 33 34 /** 35 * @see Filter#destroy() 36 */ 37 public void destroy() { 38 // TODO Auto-generated method stub 39 } 40 41 /** 42 * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) 43 */ 44 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 45 // TODO Auto-generated method stub 46 // place your code here 47 48 // pass the request along the filter chain 49 HttpServletRequest req=(HttpServletRequest)request; 50 if("GET".equals(req.getMethod())){ 51 req=new EncodingWrapper(req,ENCODING);//GET请求时创建封装器 52 } 53 else{ 54 req.setCharacterEncoding(ENCODING); 55 } 56 chain.doFilter(request, response); 57 } 58 59 /** 60 * @see Filter#init(FilterConfig) 61 */ 62 public void init(FilterConfig fConfig) throws ServletException { 63 // TODO Auto-generated method stub 64 ENCODING=fConfig.getInitParameter("ENCODING");//读取初始参数 65 } 66 67 }
四、响应封装器
Servlet是通过HttpServletResponse对象来对浏览器进行响应的,通过getWriter()取得PrintWriter,或是通过getOutputStream()取得ServletOutputStream。如果想要对响应的内容进行压缩处理,主要继承了HttpServletResponseWrapper之后,重新定义这两个方法实现的。
用GZIPOutputStream类实现压缩,其增加了压缩输出功能,重新定义writer()方法,通过GZIPOutputStream的writer()方法来做串流输出,GZIPOutputStream的writer()实现了压缩的功能。
1 package ServletAPI; 2 3 import java.io.IOException; 4 import java.util.zip.GZIPOutputStream; 5 6 import javax.servlet.ServletOutputStream; 7 8 public class GZipServletOutputStream extends ServletOutputStream{ 9 private GZIPOutputStream gzipOutputStream; 10 public GZipServletOutputStream(ServletOutputStream servletOutputStream) throws IOException{ 11 this.gzipOutputStream=new GZIPOutputStream(servletOutputStream); 12 //使用GZIPOutputStream来增加压缩功能。 13 } 14 @Override 15 public void write(int b) throws IOException { 16 // TODO Auto-generated method stub 17 gzipOutputStream.write(b);//输出时通过GZIPOutputStream的write()方法来压缩输出 18 } 19 public GZIPOutputStream getGzipOutputStream(){ 20 return gzipOutputStream; 21 } 22 23 }
1 package ServletAPI; 2 3 import java.io.IOException; 4 import java.io.OutputStreamWriter; 5 import java.io.PrintWriter; 6 import java.util.zip.GZIPOutputStream; 7 8 import javax.servlet.ServletOutputStream; 9 import javax.servlet.http.HttpServletResponse; 10 import javax.servlet.http.HttpServletResponseWrapper; 11 12 public class CompressionWrapper extends HttpServletResponseWrapper { 13 private GZipServletOutputStream gzServletOutputStream; 14 private PrintWriter printWriter; 15 public CompressionWrapper(HttpServletResponse response) { 16 super(response); 17 // TODO Auto-generated constructor stub 18 19 } 20 public ServletOutputStream getOutputStream()throws IOException{ 21 if(printWriter!=null){ 22 throw new IllegalStateException();//已调用过getWriter(),再调用getOutputStream()就抛出异常 23 } 24 if(gzServletOutputStream==null){ 25 gzServletOutputStream=new GZipServletOutputStream(getResponse().getOutputStream()); 26 //创建有压缩功能的GZipServletOutputStram对象 27 } 28 return gzServletOutputStream; 29 } 30 public PrintWriter getWriter() throws IOException{ 31 if(gzServletOutputStream!=null){ 32 throw new IllegalStateException();//已调用过getOutputStream(),再调用getWriter()就抛出异常 33 } 34 if(printWriter==null){ 35 gzServletOutputStream=new GZipServletOutputStream(getResponse().getOutputStream()); 36 OutputStreamWriter osw= new OutputStreamWriter(gzServletOutputStream,getResponse().getCharacterEncoding()); 37 //创建GzipServletOutputStream对象,供构造PrintWriter时使用 38 printWriter=new PrintWriter(osw); 39 } 40 return printWriter; 41 } 42 43 public void setContentLength(int len){}//不实现此方法内容,因为真正的输出会压缩 44 public GZIPOutputStream getGZIPOutputStream(){ 45 if(this.gzServletOutputStream==null){ 46 return null; 47 } 48 return this.gzServletOutputStream.getGzipOutputStream(); 49 } 50 }
在同一个请求期间,getWriter()和getOutputStream()只能选择一个调用。接下来实现一个压缩过滤器,使用上面开发的CompressionWrapper来封装原HttpServletResponse。
1 package ServletAPI; 2 3 import java.io.IOException; 4 import java.util.zip.GZIPOutputStream; 5 6 import javax.servlet.Filter; 7 import javax.servlet.FilterChain; 8 import javax.servlet.FilterConfig; 9 import javax.servlet.ServletException; 10 import javax.servlet.ServletRequest; 11 import javax.servlet.ServletResponse; 12 import javax.servlet.annotation.WebFilter; 13 import javax.servlet.http.HttpServletRequest; 14 import javax.servlet.http.HttpServletResponse; 15 16 /** 17 * Servlet Filter implementation class CompressionFilter 18 */ 19 @WebFilter(filterName="CompressionFilter",urlPatterns="/*") 20 public class CompressionFilter implements Filter { 21 22 /** 23 * Default constructor. 24 */ 25 public CompressionFilter() { 26 // TODO Auto-generated constructor stub 27 } 28 29 /** 30 * @see Filter#destroy() 31 */ 32 public void destroy() { 33 // TODO Auto-generated method stub 34 } 35 36 /** 37 * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) 38 */ 39 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 40 // TODO Auto-generated method stub 41 // place your code here 42 43 // pass the request along the filter chain 44 HttpServletRequest req=(HttpServletRequest)request; 45 HttpServletResponse res=(HttpServletResponse)response; 46 String encoding=req.getHeader("accept-encoding"); 47 48 if((encoding!=null)&&(encoding.indexOf("gzip")>-1)){//检查是否接受gzip压缩 49 CompressionWrapper responseWrapper=new CompressionWrapper(res);//创建响应封装器 50 responseWrapper.setHeader("content-encoding","gzip");//设置响应内容编码为gzip格式 51 chain.doFilter(request, responseWrapper);//下一个过滤器 52 GZIPOutputStream gzipOutputStream=responseWrapper.getGZIPOutputStream(); 53 if(gzipOutputStream!=null){ 54 gzipOutputStream.finish();//调用GZIPOutputStream的finish方法来完成压缩输出。 55 } 56 }else{ 57 chain.doFilter(request, response);//不接受压缩,直接进行下一个过滤器 58 } 59 } 60 61 /** 62 * @see Filter#init(FilterConfig) 63 */ 64 public void init(FilterConfig fConfig) throws ServletException { 65 // TODO Auto-generated method stub 66 } 67 68 }
浏览器是否采用GZIP压缩的格式,可以通过检查accept-encoding请求标头中是否包括gzip字符串来判断,如果接受,就创建CompressionWrapper封装原对象,并设置content-encoding响应标头为gzip即可。接着调用doFilter()时,传入的响应对象为CompressionWrapper对象。当doFilter()结束时,必须调用GZIPOutputStream的finish()方法,这才会将GZIP后的资料从缓冲区中全部移除并进行响应。
如果客户端不接受GZIP压缩,就直接调用FilterChain的doFilter()。
当神已无能为力,那便是魔渡众生