Tomcat与JavaWeb 11.0 过滤器

在一个Web应用中,每一个Web组件都用于响应特定的客户请求,不过,在这些Web组件响应客户请求的过程中,可能都会完成一些相同的操作。比如都要先检查客户的的IP地址是否位于预定义的拒绝IP地址范围内,如果满足这一条件,就直接向客户端返回拒绝响应客户请求的信息,而不会继续执行后续操作。

如果在多个Web组件中编写完成相同操作的程序代码,显然会导致重复编码,从而降低开发效率和软件的可维护性。

为了解决上述问题,过滤器应运而生,它是在Java Servlet 2.3规范中出现的技术。过滤器能够对一部分客户请求先进行预处理操作,然后再把请求转发给相应的Web组件,等到Web组件生成响应结果后,过滤器还能对响应结果进行检查和修改,然后再把修改后的响应结果发送给客户。各个Web组件中的相同操作可以放到同一个过滤器中来完成,来减少重复编码。

1.    过滤器简介

过滤器能够对Servlet容器传给Web组件的ServletRequest和ServletResponse对象进行检查和修改。过滤器本身并不生成这两个对象,它只为Web组件提供如下过滤功能:

  • 过滤器能够在Web组件被调用之前检查ServletRequest对象,修改请求头和请求正文的内容,或者对请求进行预处理操作。
  • 还能在Web组件被调用之后检查ServletResponse对象,修改响应头和响应正文。

过滤器的逻辑位置是处于Servlet容器和Servlet、JSP、HTML等文件之间的中间件,它具有以下特点:

  • 可以检查ServletRequest和ServletResponse对象,并且利用ServletRequestWrapper和ServletResponseWrapper包装类来修改这两个对象。
  • 可以在web.xml文件中为过滤器映射特定的URL,当客户请求访问此URL时,Servlet容器就会先触发过滤器工作。
  • 过滤器可以多个串联在一起,协同为Web组件过滤请求对象和响应对象。

2.    创建过滤器

所有自定义的过滤器类都要实现javax.servlet.Filter接口,这个接口含有以下3个过滤器类必须实现的方法:

  • init(FilterConfig config):这是过滤器的初始化方法。在Web应用启动时,Servlet容器先创建包含了过滤器配置信息的FilterConfig对象,然后创建Filter对象,接着调用Filter对象的init方法,在这个方法中可通过config参数来读取web.xml中为过滤器配置的初始化参数。
  • doFilter(ServletRequest req,ServletResponse resp,FilterChain chain):这个方法完成实际的过滤操作。当客户请求访问的URL与为过滤器映射的URL匹配时,Servlet容器将先调用过滤器的doFilter()方法。FilterChain参数用于访问后续过滤器或者Web组件。
  • destroy():Servlet容器在销毁过滤器对象之前调用该方法,该方法可以释放过滤器占用的资源。

过滤器由Servlet容器创建,其生命周期包含以下阶段:

  • 初始化阶段:当Web应用启动时,Servlet容器会加载过滤器类,创建过滤器配置对象(FilterConfig)和过滤器对象,并调用过滤器对象的init方法。
  • 运行时阶段:当客户请求访问的URL与为过滤器映射的URL相匹配时,Servlet容器将先调用过滤器的doFilter()方法。
  • 销毁阶段:当Web应用终止时,Servlet容器将先调用过滤器对象的destroy()方法,然后销毁过滤器对象。

下面例程的NoteFilter类是一个过滤器的例子,它为NoteServlet(留言板)提供以下过滤功能:

  • 判断客户IP地址是否在预定义的IP地址范围内,如果满足这一条件,就直接返回拒绝信息,不再调用后续的NoteServlet组件。
  • 判断username请求参数表示的姓名是否位于预定义的黑名单中,如果满足这一条件,就直接返回拒绝信息。
  • 将NoteServlet响应客户请求所花的时间写入日志。

mypack/NoteServlet.java 主要功能是一个表单。

package mypack;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class NoteServlet extends HttpServlet{
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("mypack.NoteServlet:service()");

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.println("<html><head><title> 留言板 </title></head><body>");
        String username = req.getParameter("username");
        String content = req.getParameter("content");
        if (username != null) {
            username = new String(username.getBytes("ISO-8859-1"), "utf-8");
        }
        if (content != null) {
            content = new String(content.getBytes("ISO-8859-1"), "utf-8");
        }
        if (content != null && !content.equals("")) {
            out.println("<p>"+username+"的留言为:"+content+"</p");
        }
        out.println("<form action="+req.getContextPath()+"/note method=POST>");
        out.println("<b>姓名</b>");
        out.println("<input type=text size=10 name=username><br>");
        out.println("<b>留言</b>");
        out.println("<textarea name=content rows=5 cols=20></textarea><br><br>");
        out.println("<input type=submit value=提交>");
        out.println("</form>");
        out.println("</body></html>");

        out.close();
    }
} 

该表单将会提交到localhost:8080/note地址。这时候,就是过滤器派上用场的时候了。在原来的NoteServlet页面表单重定向到到新的NoteServlet页面时,NoteFilter将会在NoteServlet接收到提交来的信息之前先捕获这些信息,进行一些预处理。

mypackl/NoteFilter.java

package mypack;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;

public class NoteFilter implements Filter {
    private FilterConfig config = null;
    private String blackList = null;
    private String ipblock = null;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("NoteFilter: init()");
        this.config = filterConfig;

        ipblock = config.getInitParameter("ipblock");
        blackList = config.getInitParameter("blacklist");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException,ServletException{
        System.out.println("NoteFilter: doFilter()");

        if (!checkRemoteIP(servletRequest,servletResponse)) return ;
        if (!checkUsername(servletRequest,servletResponse)) return ;

        long before = System.currentTimeMillis();
        config.getServletContext().log("NoteFilter:before call chain.doFilter()");

        filterChain.doFilter(servletRequest,servletResponse);

        config.getServletContext().log("NoteFilter:after call chain.doFilter()");
        long after = System.currentTimeMillis();

        String name="";
        if (servletRequest instanceof HttpServletRequest) {
            name = ((HttpServletRequest)servletRequest).getRequestURI();
        }

        config.getServletContext().log("NoteFilter:"+name+":"+(after - before)+"ms");
    }

    private boolean checkRemoteIP(ServletRequest servletRequest,ServletResponse servletResponse)
            throws IOException,ServletException {
        String addr = servletRequest.getRemoteAddr();
        if (addr.indexOf(ipblock) == 0) {
            servletResponse.setContentType("text/html;charset=utf-8");
            PrintWriter out = servletResponse.getWriter();
            out.println("<h1>对不起,服务器拒绝为您提供服务。</h1>");
            out.flush();
            return false;
        } else {
            return true;
        }
    }

    private boolean checkUsername(ServletRequest servletRequest,ServletResponse servletResponse)
            throws IOException,ServletException {
        String username = ((HttpServletRequest)servletRequest).getParameter("username");
        if (username != null) {
            username = new String (username.getBytes("ISO-8859-1"),"utf-8");
        }
        if (username != null && username.contains(blackList)) {
            servletResponse.setContentType("text/html;charset=utf-8");
            PrintWriter out = servletResponse.getWriter();
            out.println("<h1>对不起," + username + ",你没有权限留言</h1>");
            out.flush();
            return false;
        } else {
            return true;
        }
    }

    @Override
    public void destroy() {
        System.out.println("NoteFilter:destroy()");
        config = null;
    }
} 

web.xml中要设定对应的NoteServlet和NoteFilter的地址映射相同。这里需要注意,一个NoteFilter可以有多个地址映射。同时在web.xml设定一些初始化的黑名单字段和ip:

<filter>
        <filter-name>NoteFilter</filter-name>
        <filter-class>mypack.NoteFilter</filter-class>

        <init-param>
            <param-name>ipblock</param-name>
            <param-value>221.45</param-value>
        </init-param>
        <init-param>
            <param-name>blacklist</param-name>
            <param-value>捣蛋鬼</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>NoteFilter</filter-name>
        <url-pattern>/note</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>NoteServlet</servlet-name>
        <servlet-class>mypack.NoteServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>NoteServlet</servlet-name>
        <url-pattern>/note</url-pattern>
    </servlet-mapping> 

OK,那么现在启动服务器,访问localhost:8080/note,注意下方打印出的信息:

NoteFilter的init方法是在访问localhost:8080/note之前就已经运行的,也就是说,当服务器部署起来后,该方法就会被Servlet容器调用。而2和3两个方法都是访问的时候才会执行的。现在我们可以看到一个表单:

尝试输入姓名包含“捣蛋鬼”的文字:

提交:

而如果输入的姓名不包含“捣蛋鬼”,就可以留言成功。

3.    串联过滤器

多个过滤器可以串联起来协同工作,Servlet容器将根据它们在web.xml中定义的先后顺序来依次调用它们的doFilter()方法。假定有两个过滤器串联,它们的doFilter()方法均采用如下结构: 

Code1;  //表示调用doFilter()之前的代码
chain.doFilter(); 
Code2;  //表示调用doFilter()之后的代码
假定这两个过滤器都会为同一个Servlet预处理客户请求,当客户请求访问这个Servlet时,这两个过滤器及Servlet的工作流程如下所示:

加入一个过滤文本的过滤器:TextFliter:

package mypack;

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

public class TextFilter implements Filter {
    private FilterConfig config = null;
    private String searchStr = null;
    //private String replaceStr = null;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException{
        System.out.println("TextFilter:init()");
        this.config = filterConfig;
        this.searchStr = config.getInitParameter("search");
      //  this.replaceStr = config.getInitParameter("replace");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException,IOException{
        System.out.println("TextFilter:doFilter()");
        config.getServletContext().log("TextFilter:before calling chain.doFilter()");
        String content = servletRequest.getParameter("content");
        if (content != null) content = new String(content.getBytes("ISO-8859-1"),"utf-8");
        if (content != null && content.contains(searchStr)) {
            servletRequest.setAttribute("content_error","true");
            servletResponse.setContentType("text/html;charset=utf-8");
            PrintWriter out = servletResponse.getWriter();
            out.println("<h1>Error:Contains illegal contents!</h1>");
            out.flush();
        }
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("TextFilter:after calling chain.doFilter()");
        config.getServletContext().log("TextFilter:after calling chain.doFilter()");
    }

    @Override
    public void destroy() {
        System.out.println("TextFilter:destroy()");
        config = null;
    }
}
然后需要在NoteServlet的代码中修改一处判定语句:
if (content != null && !content.equals("") && req.getAttribute("content_error")!="true") {
            out.println("<p>"+username+"的留言为:"+content+"</p");
        }
在web.xml中:
    <filter>
        <filter-name>TextFilter</filter-name>
        <filter-class>mypack.TextFilter</filter-class>
        
        <init-param>
            <param-name>search</param-name>
            <param-value>暴力</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>TextFilter</filter-name>
        <url-pattern>/note</url-pattern>
    </filter-mapping>

如此,运行应用,如果在内容框中输入带“暴力”的内容,就会如下效果:

控制台打印的语句:
  可以看到执行的顺序。

具体相关内容参考:点击打开链接


至此,我们已经接触到了JavaWeb开发需要的大部分知识。但是应用中的JavaWeb都是基于框架的,需要结合框架来使用我们已经学到的Web知识。

anyway,本系列的目的已经达到了,于是光荣结束~ 撒花


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值