Filter过滤器

一.Filter基本工作原理

Filter程序必须时间javax.servlet.Filter接口,Filter程序必须在web.xml中进行注册和设置拦截资源的值,不管任何类型的资源,最终都是以Servlet程序的形式来运行的,所以,可以使用Filter来拦截任何资源。

1.Filter工作原理图

通常情况下,当浏览器直接访问某个页面时,是直接由web容器和Servlet程序进行交互,如果这个访问链接设置为被某个Filter程序拦截,便变成了上图的一个逻辑。Filter接口提供了3个重要的生命周期方法,如下:

  • init:当Web应用程序启动时,Web服务器根据web.xml的配置信息来创建每个注册的Filter实例对象,init()为Filter对象创建时调用。该方法会传递一个包含Filter配置和运行环境(xml文件中配置的信息)的FilterConfig对象,完整语法如下:              public void init(FilterConfig filterconfig) throws ServletException
  • doFilter:该方法为Filter程序的核心方法,主要用于激活Servlet程序的service方法或者交付于Filter链中的下一个Filter程序。当在web.xml文件中为某一个资源设置了多个Filter程序进行拦截时,便会形成一个如下图所示的Filter链:                                                                处于链末端的Filter程序用于激活Servlet程序的service方法,前面的Filter程序会依次把请求交付给下一个Filter程序,完整语法如:public void doFilter(ServletRequest request,ServletResponse reponse,FilterChain chain) throws IOException,ServletException,只有当init方法执行成功之后,才会将Filter程序加入Filter链。
  • destory:该方法是在Web容器卸装Filter对象之前被调用。

二.Filter应用

1.在Filter程序中修改请求和响应消息

从浏览器访问的每一个链接最终都是以servlet程序来进行处理的,我们可以理解为每一个链接实际上都访问了一个servlet程序。当在执行Filter链末端Filter程序的doFilter方法时,会向Servlet程序传递一个request对象和response对象,我们可以在Filter程序中对request对象和response对象进行包装,并将包装后的对象传递给Servlet程序,Servlet程序并不知道这个对象是否是修改过的对象,因此我们可以利用Filter程序对响应消息和请求消息进行一个统一的处理。

2.HttpServletRequestWrapper类

如果要在Filter程序中创建一个"假"的request对象传递给Servlet,这个"假"的request对象必须实现HttpServletRequest接口,我们可以通过实现该接口中的特定方法来达到我们想要的效果。由于Filter程序往往只需要修改request对象中的极少数方法,因此Servlet API中提供了HttpServletRequestWrapper类来包装原始对象。这个类实现了HttpServletRequest接口中所有的方法,内部仅仅调用了原始对象的对应方法,因此只需要重写特定的方法便可。

测试:文件上传用例

1.包装类MultipartRequest

package filter;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.*;

public class MultipartRequest extends HttpServletRequestWrapper {
    HashMap<String,String[]> parameters=new HashMap<>();
    HashMap<String,FileItem> files=new HashMap<>();
    public MultipartRequest(HttpServletRequest request)throws FileUploadException
    {
        //包装的request原始对象
        super(request);
        //这个存储路径在这儿没用
        String storePath=getServletContext().getRealPath("/")+"upload/";
        //工厂对象
        DiskFileItemFactory fileItemFactory=new DiskFileItemFactory();
        //上传对象,必须与一个工厂对象一起使用,可以单独设置
        ServletFileUpload fileUpload=new ServletFileUpload(fileItemFactory);
        //设置上传文件的最大数据量,单位字节
        fileUpload.setSizeMax(1024*1024*200);
        //设置编码方式
        fileUpload.setHeaderEncoding("utf-8");
        //设置使用临时文件保存的临界值,临时文件可以单独设置setRepository
        fileItemFactory.setSizeThreshold(1024*1024);
        List fileItem=null;
        fileItem=fileUpload.parseRequest(request);
        Iterator i=fileItem.iterator();
        while (i.hasNext())
        {
            //FileItem为表单每一项的封装对象
            FileItem fi=(FileItem)i.next();
            //输入属性时
            if(fi.isFormField())
            {
                String fieldName=fi.getFieldName();
                String content=null;
                try
                {
                    content=fi.getString("utf-8");
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
            //文件时
            else
            {
                String pathSrc=fi.getName();
                if(pathSrc.trim().equals(""))
                {
                    continue;
                }
                String fieldName=fi.getFieldName();
                files.put(fieldName,fi);
            }
        }
    }
    private void setParameter(String name,String value){
        String[] mValue=parameters.get(name);
        if(mValue==null)
        {
            mValue=new String[0];
        }
        String[] newValue=new String[mValue.length+1];
        System.arraycopy(mValue,0,newValue,0,mValue.length);
        newValue[mValue.length]=value;
        parameters.put(name,newValue);
    }
    public String getParameter(String name)
    {
        String[] mValue=(String[])parameters.get(name);
        if(mValue!=null&&mValue.length>0)
        {
            return mValue[0];
        }
        return null;
    }
    public String[] getParameterValues(String name)
    {
        String[] mValue=(String[])parameters.get(name);
        return mValue;
    }
    public Map getParameterMap()
    {
        return parameters;
    }
    public FileItem getFileItem(String name)
    {
        FileItem fItem=(FileItem)files.get(name);
        return fItem;
    }
    public Enumeration getFileItemNames()
    {
        Collection c=files.keySet();
        return Collections.enumeration(c);
    }

}

2.Filter类

package filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.rmi.ServerException;

public class UploadFilter implements Filter {
    long sizeMax=0;
    int sizeThreshold=0;
    String repositoryPath=null;
    String headerEncoding=null;
    public void init(FilterConfig config) throws ServletException
    {

    }
    public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException,ServletException
    {
        HttpServletRequest req=(HttpServletRequest)request;
        String type=req.getHeader("Content-Type");
        //当enctype不是"multipart/form-data"时正常访问
        if(type==null||!type.startsWith("multipart/form-data"))
        {
            chain.doFilter(request,response);
        }
        //否则传递一个包装的request对象
        else
        {
            MultipartRequest mreq=null;
            try
            {
                mreq=new MultipartRequest(req);
            }catch (Exception e){
                throw new ServerException(e.getMessage());
            }
            chain.doFilter(mreq,response);
        }
    }

    public void destroy() {

    }
}

3.入口file_upload.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>upload experiment</title>
    <meta http-equiv="content-type" content="text/html" charset="UTF-8">
</head>
<body>
<h3>测试文件上传组件的页面</h3>
<form action="upload.jsp" method="post" enctype="multipart/form-data">
    作者:<input type="text" name="author"><br>
    来自:<input type="text" name="company"><br>
    文件1:<input type="file" name="file1"><br>
    文件2:<input type="file" name="file2"><br>
    <input type="submit" value="上载">
</form>
</body>
</html>

4.处理页面upload.jsp

<%@ page import="filter.MultipartRequest" %>
<%@ page import="java.io.File" %>
<%@ page import="java.util.Enumeration" %>
<%@ page import="org.apache.commons.fileupload.FileItem" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
用户:<%=request.getParameter("author")%><br>
来自:<%=request.getParameter("company")%><br>
上传了如下文件:
<ul>
    <%
        if(!(request instanceof MultipartRequest))
        {
            return;
        }
        String uploadDir=getServletConfig().getServletContext().getRealPath("/upload");
        if(uploadDir==null)
        {
            out.println("无法访问存储目录!");
            return;
        }
        File fUploadDir=new File(uploadDir);
        if(!fUploadDir.exists())
        {
            if(!fUploadDir.mkdir())
            {
                out.println("无法创建目录");
                return;
            }
        }
        MultipartRequest mreq=null;
        mreq=(MultipartRequest)request;
        Enumeration<String> fileNames=mreq.getFileItemNames();
        while (fileNames.hasMoreElements())
        {
            String name=(String)fileNames.nextElement();
            FileItem fi=mreq.getFileItem(name);
            String pathSrc=fi.getName();
            int start=pathSrc.lastIndexOf('\\');
            String fileName=pathSrc.substring(start+1);
            File pathDest=new File(uploadDir,fileName);
            fi.write(pathDest);
            out.println("<li>"+fileName+"</li>");
        }
    %>
</ul>
</body>

</html>

5.xml配置

  <filter>
    <filter-name>UploadFilter</filter-name>
    <filter-class>filter.UploadFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>UploadFilter</filter-name>
    <url-pattern>/upload.jsp</url-pattern>
  </filter-mapping>

这个测试用例在当表单的entype类型不为“multipart/form-data”时正常访问,否则传递一个包装的request对象,这个包装的request对象对表单的数据进行了处理分类,然后在Jsp页面进行存储输出!

3.HttpServletResponseWrapper类

与请求消息包装类相对应的,还有一个输出消息包装类,设计思想与HttpServletRequestWrapper类似,总体而言都是要覆盖一些特定的方法以达到我们想要的效果。想要改变输目标Servlet输出的响应正文,必须重写getWriter方法和getOutputStream方法,并且返回自定义的PrintWriter对象和ServletOutputStream对象。

测试:Filter实现响应正文压缩

1.Filter类

package filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

public class CompressionFilter implements Filter {
    private FilterConfig config=null;
    private int minThreshold=128;
    protected int compressionThreshold;

    @Override
    public void init(FilterConfig filterConfig) {
        config=filterConfig;
        if(filterConfig!=null){
            //xml文件里面配置的压缩临界值
            String str=filterConfig.getInitParameter("compressionThreshold");
            if(str!=null){
                compressionThreshold =Integer.parseInt(str);
                if(compressionThreshold!=0&&compressionThreshold<minThreshold){
                    compressionThreshold=minThreshold;
                }
            }else {
                compressionThreshold=0;
            }
        }else {
            compressionThreshold=0;
        }
    }

    @Override
    public void destroy() {
        this.config=null;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException,ServletException {
        //当压缩临界值为0时,不压缩
        if(compressionThreshold==0){
            filterChain.doFilter(servletRequest,servletResponse);
            return;
        }
        boolean supportCompression=false;
        if(servletRequest instanceof HttpServletRequest){
            Enumeration e=((HttpServletRequest)servletRequest).getHeaders("Accept-Encoding");
            while (e.hasMoreElements()){
                String name=(String)e.nextElement();
                //当请求头Accept-Encoding的值都不含"gzip",不压缩
                if(name.indexOf("gzip")!=-1){
                    supportCompression=true;
                }
            }
        }
        if(!supportCompression){
            filterChain.doFilter(servletRequest,servletResponse);
            return;
        }else {
            if(servletResponse instanceof HttpServletResponse){
                //当可以压缩时,传递一个包装的response对象
                CompressionServletResponseWrapper wrapperResponse=new CompressionServletResponseWrapper((HttpServletResponse)servletResponse);
                wrapperResponse.setCompressionThreshold(compressionThreshold);
                try{
                    filterChain.doFilter(servletRequest,wrapperResponse);
                }finally {
                    wrapperResponse.finishResponse();
                }
                return;
                }
        }
    }
}

2.自定义ServletOutputStream对象

package filter;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;

public class CompressionResponseStream extends ServletOutputStream {
    protected int compressionThreshold=0;//是否启用压缩的临界值
    protected byte[] buffer=null;//临时容纳写入的数据的缓冲区
    protected int bufferCount=0;//缓冲区中实际写入的数据量
    protected GZIPOutputStream gzipOutputStream=null;
    protected boolean closed=false;//当前流对象是否处于关闭状态
    protected int length=-1;
    protected HttpServletResponse response=null;
    protected ServletOutputStream output=null;

    public CompressionResponseStream(HttpServletResponse response) throws IOException
    {
        super();
        closed=false;
        this.response=response;
        this.output=response.getOutputStream();
    }
    //设置是否启用压缩的临界值
    protected void setBuffer(int threshold)
    {
        compressionThreshold=threshold;
        buffer=new byte[compressionThreshold];
    }
    public void close() throws IOException
    {
        if(closed)
        {
            throw new IOException("This output stream has already been closed");
        }
        if(gzipOutputStream!=null){
            flushToGzip();
            gzipOutputStream.close();
            gzipOutputStream=null;
        }else {
            if(bufferCount>0){
                output.write(buffer,0,bufferCount);
                bufferCount=0;
                output.close();
            }
        }
        closed=true;
    }
    public void flush() throws IOException
    {
        if(closed){
            throw new IOException("Cannot flush a closed output stream");
        }
        if(gzipOutputStream!=null){
            gzipOutputStream.flush();
        }
    }
    //将buffer缓冲区内的数据写入到gzipstream对象中
    public void flushToGzip()throws IOException
    {
        if(bufferCount>0){
            writeToGZip(buffer,0,bufferCount);
            bufferCount=0;
        }
    }
    public void write(int b)throws IOException
    {
        if(closed){
            throw new IOException("Cannot write to a closed output stream");
        }
        if(bufferCount>=buffer.length){
            flushToGzip();
        }
        buffer[bufferCount++]=(byte)b;
    }
    public void write(byte[] b) throws IOException{
        write(b,0,b.length);
    }
    public void write(byte[] b,int off,int len) throws IOException
    {
        if(closed){
            throw new IOException("Cannot write to a closed output stream");
        }
        if(len==0){
            return;
        }
        if(len<=(buffer.length-bufferCount)){
            System.arraycopy(b,off,buffer,bufferCount,len);
            bufferCount+=len;
            return;
        }
        flushToGzip();
        if(len<=(buffer.length-bufferCount)){
            System.arraycopy(b,off,buffer,bufferCount,len);
            bufferCount+=len;
            return;
        }
        writeToGZip(b,off,len);
    }
    public void writeToGZip(byte[] b,int off,int len) throws IOException
    {
        if(gzipOutputStream==null){
            response.addHeader("Content-Encoding","gzip");
            gzipOutputStream=new GZIPOutputStream(output);
        }
        gzipOutputStream.write(b,off,len);
    }
    public boolean isClosed()
    {
        return this.closed;
    }

    @Override
    public boolean isReady() {
        return false;
    }

    @Override
    public void setWriteListener(WriteListener writeListener) {

    }
}

3.包装response对象

package filter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class CompressionServletResponseWrapper extends HttpServletResponseWrapper {
    protected HttpServletResponse origResponse=null;
    protected static final String info="CompressionServletResponseWrapper";
    protected ServletOutputStream stream=null;
    protected PrintWriter writer=null;
    protected int threshold=0;
    protected String contentType=null;

    public CompressionServletResponseWrapper(HttpServletResponse response){
        super(response);
        origResponse=response;
    }

    public void setContentType(String contentType)
    {
        this.contentType=contentType;
        origResponse.setContentType(contentType);
    }

    public void setCompressionThreshold(int threshold)
    {
        this.threshold=threshold;
    }

    public ServletOutputStream createdOutputStream() throws IOException
    {
        CompressionResponseStream stream=new CompressionResponseStream(origResponse);
        stream.setBuffer(threshold);
        return stream;
    }

    public void finishResponse()
    {
        try{
            if(writer!=null){
                writer.close();
            }else {
                if(stream!=null){
                    stream.close();
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    public void flushBuffer() throws IOException
    {
        ((CompressionResponseStream)stream).flush();
    }

    public ServletOutputStream getOutputStream() throws IOException
    {
        if(writer!=null){
            throw new IllegalStateException("getWriter() has already called for this response");
        }
        if(stream==null){
            stream=createdOutputStream();
        }
        return stream;
    }

    public PrintWriter getWriter() throws IOException
    {
        if(writer!=null){
            return writer;
        }
        if(stream!=null){
            throw new IllegalStateException("getOutputStream() has already been called for this response");
        }
        stream=createdOutputStream();
        String charEnc=origResponse.getCharacterEncoding();
        if(charEnc!=null){
            writer=new PrintWriter(new OutputStreamWriter(stream,charEnc));
        }else {
            writer=new PrintWriter(stream);
        }
        return writer;
    }

    public void setContentLength(int length){
    }

    private static String getCharsetFromContentType(String type){
        if(type==null){
            return null;
        }
        int semi=type.indexOf(";");
        if(semi==-1) {
            return null;
        }
        String afterSemi=type.substring(semi+1);
        int charsetLocation=afterSemi.indexOf("charset=");
        if(charsetLocation==-1){
            return null;
        } else {
            String afterCharset =afterSemi.substring(charsetLocation+8);
            String encoding=afterCharset.trim();
            return encoding;
        }
    }
}

4.测试jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h3>阅读第一篇文章</h3><br>
<h3>阅读第二篇文章</h3><br>
</body>
</html>

5.xml配置

  <filter>
    <filter-name>CompressionFilter</filter-name>
    <filter-class>filter.CompressionFilter</filter-class>
    <init-param>
      <param-name>compressionThreshold</param-name>
      <param-value>128</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CompressionFilter</filter-name>
    <url-pattern>/articles.jsp</url-pattern>
  </filter-mapping>

在这个测试用例中,当访问的数据响应消息数据大小超过临界值时,便对数据进行压缩之后在进行传输,可以取消xml文件的配置进行访问测试,看响应头的Content-Length的变化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值