Servlet過濾器介紹之實用過濾器

Servlet過濾器介紹之實用過濾器
這裏介紹幾個實用的過濾器設計代碼。整理自Marty Halls寫的《ServletJSP權威指南》。我已作過測試,均經過可用。
author: ZJ 2007-3-5
6.禁止站點過濾器
若是你但願在你的過濾器檢測到不正常的異常而中途中斷後面的過濾過程時,可這樣作:
public void doFilter(ServletRequest request, ServletResponse response,
       FilterChain chain) throws ServletException, IOException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;
    if (isUnusualCondition(req)) {
       res.sendRedirect("http://www.somesite.com");
    } else {
       chain.doFilter(req, res);
    }
}
下例是一個禁止站點過濾器,若是不但願某些站點訪問你的網站,你能夠在web.xmlparam-value中列出它的站點,而後應用上面的原理跳出常規過濾,給出禁止訪問的頁面。
BannedAccessFilter.java
package com.zj.sample;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.StringTokenizer;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
 
public class BannedAccessFilter implements Filter {
    private HashSet<String> bannedSiteTable;
 
/**
* Deny access if the request comes from a banned site or is referred here
* by a banned site.
 */
    public void doFilter(ServletRequest request, ServletResponse response,
           FilterChain chain) throws ServletException, IOException {
       System.out.println("Within BannedAccessFilter:Filtering the Request...");
       HttpServletRequest req = (HttpServletRequest) request;
       String requestingHost = req.getRemoteHost();
       String referringHost = getReferringHost(req.getHeader("Referer"));
       String bannedSite = null;
       boolean isBanned = false;
       if (bannedSiteTable.contains(requestingHost)) {
           bannedSite = requestingHost;
           isBanned = true;
       } else if (bannedSiteTable.contains(referringHost)) {
           bannedSite = referringHost;
           isBanned = true;
       }
       if (isBanned) {
           showWarning(response, bannedSite);
       } else {
           chain.doFilter(request, response);
       }
       System.out.println("Within BannedAccessFilter:Filtering the Response...");
    }
 
/**
* Create a table of banned sites based on initialization parameters.
* Remember that version 2.3 of the servlet API mandates the use of the
* Java  2 Platform. Thus, it is safe to use HashSet (which determines
* whether a given key exists) rather than the clumsier Hashtable
* (which has a value for each key).
*/
 
    public void init(FilterConfig config) throws ServletException {
       bannedSiteTable = new HashSet<String>();
       String bannedSites = config.getInitParameter("bannedSites");
       // Default token set: white space.
       StringTokenizer tok = new StringTokenizer(bannedSites);
       while (tok.hasMoreTokens()) {
           String bannedSite = tok.nextToken();
           bannedSiteTable.add(bannedSite);
           System.out.println("Banned " + bannedSite);
       }
    }
 
    public void destroy() {}
 
    private String getReferringHost(String refererringURLString) {
       try {
           URL referringURL = new URL(refererringURLString);
           return (referringURL.getHost());
       } catch (MalformedURLException mue) { // Malformed or null
           return (null);
       }
    }
 
    // Replacement response that is returned to users
    // who are from or referred here by a banned site.
    private void showWarning(ServletResponse response, String bannedSite)
           throws ServletException, IOException {
       response.setContentType("text/html");
       PrintWriter out = response.getWriter();
       String docType = "<!DOCTYPE HTML PUBLIC \"-//W 3C //DTD HTML 4.0 "
              + "Transitional//EN\">\n";
       out.println(docType + "<HTML>\n"
              + "<HEAD><TITLE>Access Prohibited</TITLE></HEAD>\n"
              + "<BODY BGCOLOR=\"WHITE\">\n" + "<H1>Access Prohibited</H1>\n"
              + "Sorry, access from or via " + bannedSite + "\n"
              + "is not allowed.\n" + "</BODY></HTML>");
    }
}
 
web.xml
<filter>
    <filter-name>BannedAccessFilter</filter-name>
    <filter-class>com.zj.sample.BannedAccessFilter</filter-class>
    <init-param>
       <param-name>bannedSites</param-name>
       <param-value>
           [url]www.competingsite.com[/url] [url]www.bettersite.com[/url]
           [url]www.moreservlets.com[/url] 127.0.0.1//咱們測試這個
       </param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>BannedAccessFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
 
測試:
[url]http://localhost:8080/Test4Jsp/[/url]
 
結果:

7.替換過濾器
7.1修改響應
過濾器可以阻止對資源的訪問或者阻止激活它們。但若是過濾器想更改資源所生成的響應。怎麼辦呢?彷佛沒有辦法可以對一個資源所生成的響應進行訪問。DoFilter的第二個參數(ServletResponse)給過濾器提供了一種發送新輸出到客戶機的辦法,但沒有給過濾器提供對servletJSP頁面輸出進行訪問的辦法。爲何會這樣呢?由於在第一次調用doFilter方法時,servletJSP頁面甚至尚未執行。一旦調用了FilterChain對象中的doFilter方法,要修改響應彷佛就太遲了,這是數據已經發送到客戶機。
不過,辦法是有的,那就是修改傳遞給FilterChain對象的doFilter方法的響應對象。通常,創建緩存servletJSP頁面生成的全部輸出的版本。Servlet API 2.3版爲此提供了一種有用的資源,即,HttpServletResponseWrapper類。這個類的使用包括如下五個步驟:
1)創建一個響應包裝器。擴展javax.servlet.http.HttpServletResponseWrapper
2)提供一個緩存輸出的PrintWriter。重載getWriter方法,返回一個保存發送給它的全部東西的PrintWriter,並把結果存進一個能夠稍後訪問的字段中。
3)傳遞該包裝器給doFilter。此調用是合法的,由於HttpServletResponseWrapper實現HttpServletResponse
4)提取和修改輸出。在調用FilterChaindoFilter方法後,原資源的輸出只要利用步驟2中提供的機制就能夠獲得。只要對你的應用適合,就能夠修改或替換它。
5)發送修改過的輸出到客戶機。由於原資源再也不發送輸出到客戶機(這些輸出已經存放到你的響應包裝器中了),因此必須發送這些輸出。這樣,你的過濾器須要從原響應對象中得到PrintWriterOutputStream,並傳遞修改過的輸出到該流中。
 
7.2一個可重用的響應包裝器
下例程序給出了一個包裝器,它可用於但願過濾器修改資源的輸出的大多數應用中。CharArrayWrapper類重載getWriter方法以返回一個PrintWriter,它累積一個大字符數組中的全部東西。開發人員可利用toCharArray(原始char[])或toString(從char[]得出的一個String)方法獲得這個結果。
CharArrayWrapper.java
package com.zj.sample;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
 
/**
 * A response wrapper that takes everything the client would normally
 * output and saves it in one big character array.
 */
public class CharArrayWrapper extends HttpServletResponseWrapper {
    private CharArrayWriter charWriter;
 
    /**
     * Initializes wrapper.
     * <P>
     * First, this constructor calls the parent constructor. That call
     *is crucial so that the response is stored and thus setHeader, *setStatus, addCookie, and so forth work normally.
     * <P>
     * Second, this constructor creates a CharArrayWriter that will
*  be used to accumulate the response.
     */
    public CharArrayWrapper(HttpServletResponse response) {
       super(response);
       charWriter = new CharArrayWriter();
    }
 
    /**
     * When servlets or JSP pages ask for the Writer, don't give them
* the real one. Instead, give them a version that writes into
* the character array.
     * The filter needs to send the contents of the array to the
* client (perhaps after modifying it).
     */
    public PrintWriter getWriter() {
       return (new PrintWriter(charWriter));
    }
 
    /**
     * Get a String representation of the entire buffer.
     * <P>
     * Be sure <B>not</B> to call this method multiple times on the same
     * wrapper. The API for CharArrayWriter does not guarantee that it
     * "remembers" the previous value, so the call is likely to make
* a new String every time.
     */
    public String toString() {
       return (charWriter.toString());
    }
 
    /** Get the underlying character array. */
    public char[] toCharArray() {
       return (charWriter.toCharArray());
    }
}
 
7.3 替換過濾器
這裏展現前一節中給出的CharArrayWrapper的一個常見的應用:更改一個屢次出現的目標串爲某個替代串的過濾器。
 
7.3.1 通用替換過濾器
ReplaceFilter.java給出一個過濾器,它在CharArraryWrapper中包裝響應,傳遞該包裝器到FilterChain對象的doFilter方法中,提取一個給出全部資源的輸出的String型值,用一個替代串替換某個目標串的全部出現,併發送此修改過的結果到客戶機。
關於這個過濾器,有兩件事情須要注意。首先,它是一個抽象類。要使用它,必須創建一個提供getTargetStringgetReplacementString方法的實現的子類。下一小節中給出了這種處理的一個例子。其次,它利用一個較小的實用類(見FilterUtils.java)來進行實際的串替換。你可以使用新的常規表達式包而不是使用StringStringTokenizer中低級的和繁瑣的方法。
ReplaceFilter.java
package com.zj.sample;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
 
/**
 * Filter that replaces all occurrences of a given string with a
* replacement.
 * This is an abstract class: you <I>must</I> override the getTargetString
* and getReplacementString methods in a subclass.
* The first of these methods specifies the string in the response
* that should be replaced. The second of these specifies the string
* that should replace each occurrence of the target string.
 */
public abstract class ReplaceFilter implements Filter {
    private FilterConfig config;
 
    public void doFilter(ServletRequest request, ServletResponse response,
           FilterChain chain) throws ServletException, IOException {
       CharArrayWrapper responseWrapper = new CharArrayWrapper(
              (HttpServletResponse) response);
       // Invoke resource, accumulating output in the wrapper.
       chain.doFilter(request, responseWrapper);
       // Turn entire output into one big String.
       String responseString = responseWrapper.toString();
       // In output, replace all occurrences of target string with replacement
       // string.
       responseString = FilterUtils.replace(responseString, getTargetString(),
              getReplacementString());
       // Update the Content-Length header.
       updateHeaders(response, responseString);
       PrintWriter out = response.getWriter();
       out.write(responseString);
    }
 
    /**
     * Store the FilterConfig object in case subclasses want it.
     */
    public void init(FilterConfig config) throws ServletException {
       this.config = config;
    }
 
    protected FilterConfig getFilterConfig() {
       return (config);
    }
 
    public void destroy() {
    }
 
    /**
     * The string that needs replacement.
*Override this method in your subclass.
     */
    public abstract String getTargetString();
 
    /**
     * The string that replaces the target. Override this method in
     * your  subclass.
     */
    public abstract String getReplacementString();
 
    /**
     * Updates the response headers. This simple version just sets
*the Content-Length header, assuming that we are using a
*character set that uses 1 byte per character.
* For other character sets, override this method to use
* different logic or to give up on persistent HTTP connections.
* In this latter case, have this method set the Connection header
* to "close".
     */
    public void updateHeaders(ServletResponse response, String responseString) {
       response.setContentLength(responseString.length());
    }
}
 
FilterUtils.java
package com.zj.sample;
 
/**
 * Small utility to assist with response wrappers that return strings.
 */
public class FilterUtils {
    /**
     * Change all occurrences of orig in mainString to replacement.
     */
    public static String replace(String mainString, String orig,
           String replacement) {
       String result = "";
       int oldIndex = 0;
       int index = 0;
       int origLength = orig.length();
       while ((index = mainString.indexOf(orig, oldIndex)) != -1) {
           result = result + mainString.substring(oldIndex, index)
                  + replacement;
           oldIndex = index + origLength;
       }
       result = result + mainString.substring(oldIndex);
       return (result);
    }
}
 
7.3.2 實現一個字符替換過濾器
假設百度收購了google(只是假設),如今全部的頁面上凡是出現google字樣的文字都必須替換爲百度!ReplaceSiteNameFilter.java繼承上文ReplaceFilter.java來實現這一功能。
ReplaceSiteNameFilter.java
package com.zj.sample;
 
public class ReplaceSiteNameFilter extends ReplaceFilter {
    public String getTargetString() {
       return ("google.com.cn");
    }
 
    public String getReplacementString() {
       return ("baidu.com");
    }
}
 
web.xml
<filter>
    <filter-name>ReplaceSiteNameFilter</filter-name>
    <filter-class>com.zj.sample.ReplaceSiteNameFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>ReplaceSiteNameFilter</filter-name>
    <url-pattern>/login.jsp</url-pattern>
</filter-mapping>
 
測試結果:
過濾前

 
過濾後

8.壓縮過濾器
有幾個最新的瀏覽器可處理壓縮的內容,自動解開以gzip做爲Content-Encoding響應頭值的壓縮文件,而後就像對原文檔那樣處理結果。發送這樣的壓縮內容能夠節省不少時間,由於在服務器上壓縮文檔,而後在客戶機上解開文檔所需的時間與下載文件的時間相比是微不足道的。程序LongServlet.java給出了一個具備很長的、重複的純文本輸出的servlet,這是一個可供壓縮使用的成熟的servlet。若是使用gzip,它能夠把輸出結果壓縮到1/300
在瀏覽器支持這個壓縮能力時,壓縮過濾器可利用章節7介紹的CharArrayWrapper來壓縮內容,完成此任務須要下列內容:
1)實現Filter接口的類。這個類名爲CompressionFIlterinit方法存放FilterConfig對象在一個字段中,以防子類須要訪問servlet環境或過濾器名。destory方法體爲空。
2)包裝的響應對象。DoFilter方法將ServletResponse對象包裝在一個CharArrayWrapper中,並傳遞此包裝器到FilterChain對象的doFilter方法上。在此調用完成後,全部其餘過濾器和最終資源都已執行,且輸出結果位於包裝器以內。這樣,原doFilter提取一個表明全部資源的輸出的字符數組。若是客戶機指出它支持壓縮(即,以gzip做爲Accept-Encoding頭的一個值),則過濾器附加一個GZIPOutputStreamByteArrayOutputStream上,將字符數組複製到此流中,並設置Content-Encoding響應頭爲gzip。若是客戶機不支持gzip,則將未修改過的字符數組複製到ByteArrayOutputStream。最後,doFilter經過將整個字符數組(多是壓縮過的)寫到與original響應相關的OutputStream中,發送結果到客戶機。
3)對LongServlet進行註冊。
CompressionFilter.java
package com.zj.sample;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.zip.GZIPOutputStream;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
/**
 * Filter that compresses output with gzip (assuming that browser supports
 * gzip).
 */
public class CompressionFilter implements Filter {
 
    private FilterConfig config;
 
    /**
     * If browser does not support gzip, invoke resource normally. If browser
     * <I>does</I> support gzip, set the Content-Encoding response header and
     * invoke resource with a wrapped response that collects all the output.
     * Extract the output and write it into a gzipped byte array. Finally, write
     * that array to the client's output stream.
     */
    public void doFilter(ServletRequest request, ServletResponse response,
           FilterChain chain) throws ServletException, IOException {
       HttpServletRequest req = (HttpServletRequest) request;
       HttpServletResponse res = (HttpServletResponse) response;
       if (!isGzipSupported(req)) {
           // Invoke resource normally.
           chain.doFilter(req, res);
       } else {
           // Tell browser we are sending it gzipped data.
           res.setHeader("Content-Encoding", "gzip");
           // Invoke resource, accumulating output in the wrapper.
           CharArrayWrapper responseWrapper = new CharArrayWrapper(res);
           chain.doFilter(req, responseWrapper);
           // Get character array representing output.
           char[] responseChars = responseWrapper.toCharArray();
           // Make a writer that compresses data and puts it into a byte array.
           ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
           GZIPOutputStream zipOut = new GZIPOutputStream(byteStream);
           OutputStreamWriter tempOut = new OutputStreamWriter(zipOut);
           // Compress original output and put it into byte array.
           tempOut.write(responseChars);
           // Gzip streams must be explicitly closed.
           tempOut.close();
           // Update the Content-Length header.
           res.setContentLength(byteStream.size());
           // Send compressed result to client.
           OutputStream realOut = res.getOutputStream();
           byteStream.writeTo(realOut);
       }
    }
 
    /**
     * Store the FilterConfig object in case subclasses want it.
     */
    public void init(FilterConfig config) throws ServletException {
       this.config = config;
    }
 
    protected FilterConfig getFilterConfig() {
       return (config);
    }
 
    public void destroy() {}
 
    private boolean isGzipSupported(HttpServletRequest req) {
       String browserEncodings = req.getHeader("Accept-Encoding");
       return ((browserEncodings != null) && (browserEncodings.indexOf("gzip") != -1));
    }
}
 
LongServlet.java
package com.zj.sample;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
/**
 * Servlet with <B>long</B> output. Used to test the effect of the compression
 * filter of Chapter 9.
 */
 
public class LongServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
       response.setContentType("text/html");
       PrintWriter out = response.getWriter();
       String docType = "<!DOCTYPE HTML PUBLIC \"-//W 3C //DTD HTML 4.0 "
              + "Transitional//EN\">\n";
       String title = "Long Page";
       out.println(docType + "<HTML>\n" + "<HEAD><TITLE>" + title
              + "</TITLE></HEAD>\n" + "<BODY BGCOLOR=\"#FDF5E6\">\n"
              + "<H1 ALIGN=\"CENTER\">" + title + "</H1>\n");
       String line = "Blah, blah, blah, blah, blah. "
              + "Yadda, yadda, yadda, yadda.";
       for (int i = 0; i < 10000; i++) {
           out.println(line);
       }
       out.println("</BODY></HTML>");
    }
}
 
web.xml
<filter>
    <filter-name>CompressionFilter</filter-name>
    <filter-class>com.zj.sample.CompressionFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CompressionFilter</filter-name>
    <servlet-name>LongServlet</servlet-name>
</filter-mapping>
 
<servlet>
    <servlet-name>LongServlet</servlet-name>
    <servlet-class>com.zj.sample.LongServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>LongServlet</servlet-name>
    <url-pattern>/LongServlet</url-pattern>
</servlet-mapping>
相關文章
相關標籤/搜索