一.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的变化。