1.Servlet過濾器
1.1 什么是過濾器
過濾器是一個程序,它先于與之相關的servlet或JSP頁面運行在服務器上。過濾器可附加到一個或多個servlet或JSP頁面上,并且可以檢查進入這些資源的請求信息。在這之后,過濾器可以作如下的選擇:
①以常規的方式調用資源(即,調用servlet或JSP頁面)。
②利用修改過的請求信息調用資源。
③調用資源,但在發送響應到客戶機前對其進行修改。
④阻止該資源調用,代之以轉到其他的資源,返回一個特定的狀態代碼或生成替換輸出。
1.2 Servlet過濾器的基本原理
在Servlet作為過濾器使用時,它可以對客戶的請求進行處理。處理完成后,它會交給下一個過濾器處理,這樣,客戶的請求在過濾鏈里逐個處理,直到請求發送到目標為止。例如,某網站里有提交“修改的注冊信息”的網頁,當用戶填寫完修改信息并提交后,服務器在進行處理時需要做兩項工作:判斷客戶端的會話是否有效;對提交的數據進行統一編碼。這兩項工作可以在由兩個過濾器組成的過濾鏈里進行處理。當過濾器處理成功后,把提交的數據發送到最終目標;如果過濾器處理不成功,將把視圖派發到指定的錯誤頁面。
2.Servlet過濾器開發步驟
開發Servlet過濾器的步驟如下:
①編寫實現Filter接口的Servlet類。
②在web.xml中配置Filter。
開發一個過濾器需要實現Filter接口,Filter接口定義了以下方法:
①destory()由Web容器調用,初始化此Filter。
②init(FilterConfig filterConfig)由Web容器調用,初始化此Filter。
③doFilter(ServletRequest request,ServletResponse response,FilterChain chain)具體過濾處理代碼。
3.一個過濾器框架實例
SimpleFilter1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package com.zj.sample; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class SimpleFilter1 implements Filter { @SuppressWarnings ( "unused" ) private FilterConfig filterConfig; public void init(FilterConfig config) throws ServletException { this .filterConfig = config; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { System.out.println( "Within SimpleFilter1:Filtering the Request..." ); chain.doFilter(request, response); // 把處理發送到下一個過濾器 System.out .println( "Within SimpleFilter1:Filtering the Response..." ); } catch (IOException ioe) { ioe.printStackTrace(); } catch (ServletException se) { se.printStackTrace(); } } public void destroy() { this .filterConfig = null ; } } |
SimpleFilter2.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package com.zj.sample; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class SimpleFilter2 implements Filter { @SuppressWarnings ( "unused" ) private FilterConfig filterConfig; public void init(FilterConfig config) throws ServletException { this .filterConfig = config; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { System.out.println( "Within SimpleFilter2:Filtering the Request..." ); chain.doFilter(request, response); // 把處理發送到下一個過濾器 System.out.println( "Within SimpleFilter2:Filtering the Response..." ); } catch (IOException ioe) { ioe.printStackTrace(); } catch (ServletException se) { se.printStackTrace(); } } public void destroy() { this .filterConfig = null ; } } |
web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
< filter > < filter-name >filter1</ filter-name > < filter-class >com.zj.sample.SimpleFilter1</ filter-class > </ filter > < filter-mapping > < filter-name >filter1</ filter-name > < url-pattern >/*</ url-pattern >//為所有的訪問做過濾 </ filter-mapping > < filter > < filter-name >filter2</ filter-name > < filter-class >com.zj.sample.SimpleFilter2</ filter-class > </ filter > < filter-mapping > < filter-name >filter2</ filter-name > < url-pattern >/*</ url-pattern >//為所有的訪問做過濾 </ filter-mapping > |
打開web容器中任意頁面輸出結果:(注意過濾器執行的請求/響應順序)
1
2
3
4
|
Within SimpleFilter1:Filtering the Request... Within SimpleFilter2:Filtering the Request... Within SimpleFilter2:Filtering the Response... Within SimpleFilter1:Filtering the Response... |
4.報告過濾器
我們來試驗一個簡單的過濾器,只要調用相關的servlet或JSP頁面,它就打印一條消息到標準輸出。為實現此功能,在doFilter方法中執行過濾行為。每當調用與這個過濾器相關的servlet或JSP頁面時,doFilter方法就生成一個打印輸出,此輸出列出請求主機和調用的URL。因為getRequestURL方法位于HttpServletRequest而不是ServletRequest中,所以把ServletRequest對象構造為HttpServletRequest類型。我們改動一下章節3的SimpleFilter1.java。
SimpleFilter1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package com.zj.sample; import java.io.IOException; import java.util.Date; 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 SimpleFilter1 implements Filter { @SuppressWarnings ( "unused" ) private FilterConfig filterConfig; public void init(FilterConfig config) throws ServletException { this .filterConfig = config; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { System.out.println( "Within SimpleFilter1:Filtering the Request..." ); HttpServletRequest req = (HttpServletRequest) request; System.out.println(req.getRemoteHost() + " tried to access " + req.getRequestURL() + " on " + new Date() + "." ); chain.doFilter(request, response); System.out.println( "Within SimpleFilter1:Filtering the Response..." ); } catch (IOException ioe) { ioe.printStackTrace(); } catch (ServletException se) { se.printStackTrace(); } } public void destroy() { this .filterConfig = null ; } } |
web.xml設置不變,同章節3。
測試:
輸入[url]http://localhost:8080/Test4Jsp/login.jsp[/url]
結果:
1
2
3
4
5
|
Within SimpleFilter1:Filtering the Request... 0:0:0:0:0:0:0:1 tried to access [url]http://localhost:8080/Test4Jsp/login.jsp[/url] on Sun Mar 04 17:01:37 CST 2007. Within SimpleFilter2:Filtering the Request... Within SimpleFilter2:Filtering the Response... Within SimpleFilter1:Filtering the Response... |
5.訪問時的過濾器(在過濾器中使用servlet初始化參數)
下面利用init設定一個正常訪問時間范圍,對那些不在此時間段的訪問作出記錄。我們改動一下章節3的SimpleFilter2.java。
SimpleFilter2.java。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
package com.zj.sample; import java.io.IOException; import java.text.DateFormat; import java.util.Calendar; import java.util.GregorianCalendar; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; public class SimpleFilter2 implements Filter { @SuppressWarnings ( "unused" ) private FilterConfig config; private ServletContext context; private int startTime, endTime; private DateFormat formatter; public void init(FilterConfig config) throws ServletException { this .config = config; context = config.getServletContext(); formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); try { startTime = Integer.parseInt(config.getInitParameter( "startTime" )); // web.xml endTime = Integer.parseInt(config.getInitParameter( "endTime" )); // web.xml } catch (NumberFormatException nfe) { // Malformed or null // Default: access at or after 10 p.m. but before 6 a.m. is // considered unusual. startTime = 22 ; // 10:00 p.m. endTime = 6 ; // 6:00 a.m. } } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { System.out.println( "Within SimpleFilter2:Filtering the Request..." ); HttpServletRequest req = (HttpServletRequest) request; GregorianCalendar calendar = new GregorianCalendar(); int currentTime = calendar.get(Calendar.HOUR_OF_DAY); if (isUnusualTime(currentTime, startTime, endTime)) { context.log( "WARNING: " + req.getRemoteHost() + " accessed " + req.getRequestURL() + " on " + formatter.format(calendar.getTime())); // The log file is under <CATALINA_HOME>/logs.One log per day. } chain.doFilter(request, response); System.out .println( "Within SimpleFilter2:Filtering the Response..." ); } catch (IOException ioe) { ioe.printStackTrace(); } catch (ServletException se) { se.printStackTrace(); } } public void destroy() {} // Is the current time between the start and end // times that are marked as abnormal access times? private boolean isUnusualTime( int currentTime, int startTime, int endTime) { // If the start time is less than the end time (i.e., // they are two times on the same day), then the // current time is considered unusual if it is // between the start and end times. if (startTime < endTime) { return ((currentTime >= startTime) && (currentTime < endTime)); } // If the start time is greater than or equal to the // end time (i.e., the start time is on one day and // the end time is on the next day), then the current // time is considered unusual if it is NOT between // the end and start times. else { return (!isUnusualTime(currentTime, endTime, startTime)); } } } |
web.xml設置不變。
關于Tomcat日志處理,這里補充介紹一下。config.getServletContext().log("log message")會將日志信息寫入<CATALINA_HOME>/logs文件夾下,文件名應該為localhost_log.2007-03-04.txt這樣的形式(按日期每天產生一個,第二天可以看見)。要得到這樣一個日志文件,應該在server.xml中有:
<Logger className="org.apache.catalina.logger.FileLogger" prefix="catalina_log." suffix=".txt" timestamp="true"/>
6.禁止站點過濾器
如果你希望在你的過濾器檢測到不正常的異常而中途中斷后面的過濾過程時,可這樣做:
1
2
3
4
5
6
7
8
9
10
|
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.xml的param-value中列出它的站點,然后應用上面的原理跳出常規過濾,給出禁止訪問的頁面。
BannedAccessFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
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 \"-//W3C//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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
< 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 > |
測試:
1
|
[url]http://localhost:8080/Test4Jsp/[/url] |
結果:
7.替換過濾器
7.1修改響應
過濾器能夠阻止對資源的訪問或者阻止激活它們。但如果過濾器想更改資源所生成的響應。怎么辦呢?似乎沒有辦法能夠對一個資源所生成的響應進行訪問。DoFilter的第二個參數(ServletResponse)給過濾器提供了一種發送新輸出到客戶機的辦法,但沒有給過濾器提供對servlet或JSP頁面輸出進行訪問的辦法。為什么會這樣呢?因為在第一次調用doFilter方法時,servlet或JSP頁面甚至還沒有執行。一旦調用了FilterChain對象中的doFilter方法,要修改響應似乎就太遲了,這是數據已經發送到客戶機。
不過,辦法是有的,那就是修改傳遞給FilterChain對象的doFilter方法的響應對象。一般,建立緩存servlet或JSP頁面生成的所有輸出的版本。Servlet API 2.3版為此提供了一種有用的資源,即,HttpServletResponseWrapper類。這個類的使用包括以下五個步驟:
1)建立一個響應包裝器。擴展javax.servlet.http.HttpServletResponseWrapper。
2)提供一個緩存輸出的PrintWriter。重載getWriter方法,返回一個保存發送給它的所有東西的PrintWriter,并把結果存進一個可以稍后訪問的字段中。
3)傳遞該包裝器給doFilter。此調用是合法的,因為HttpServletResponseWrapper實現HttpServletResponse。
4)提取和修改輸出。在調用FilterChain的doFilter方法后,原資源的輸出只要利用步驟2中提供的機制就可以得到。只要對你的應用適合,就可以修改或替換它。
5)發送修改過的輸出到客戶機。因為原資源不再發送輸出到客戶機(這些輸出已經存放到你的響應包裝器中了),所以必須發送這些輸出。這樣,你的過濾器需要從原響應對象中獲得PrintWriter或OutputStream,并傳遞修改過的輸出到該流中。
7.2一個可重用的響應包裝器
下例程序給出了一個包裝器,它可用于希望過濾器修改資源的輸出的大多數應用中。CharArrayWrapper類重載getWriter方法以返回一個PrintWriter,它累積一個大字符數組中的所有東西。開發人員可利用toCharArray(原始char[])或toString(從char[]得出的一個String)方法得到這個結果。
CharArrayWrapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
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型值,用一個替代串替換某個目標串的所有出現,并發送此修改過的結果到客戶機。
關于這個過濾器,有兩件事情需要注意。首先,它是一個抽象類。要使用它,必須建立一個提供getTargetString和getReplacementString方法的實現的子類。下一小節中給出了這種處理的一個例子。其次,它利用一個較小的實用類(見FilterUtils.java)來進行實際的串替換。你可使用新的常規表達式包而不是使用String和StringTokenizer中低級的和繁瑣的方法。
ReplaceFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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
1
2
3
4
5
6
7
8
|
< 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接口的類。這個類名為CompressionFIlter。init方法存放FilterConfig對象在一個字段中,以防子類需要訪問servlet環境或過濾器名。destory方法體為空。
2)包裝的響應對象。DoFilter方法將ServletResponse對象包裝在一個CharArrayWrapper中,并傳遞此包裝器到FilterChain對象的doFilter方法上。在此調用完成后,所有其他過濾器和最終資源都已執行,且輸出結果位于包裝器之內。這樣,原doFilter提取一個代表所有資源的輸出的字符數組。如果客戶機指出它支持壓縮(即,以gzip作為Accept-Encoding頭的一個值),則過濾器附加一個GZIPOutputStream到ByteArrayOutputStream上,將字符數組復制到此流中,并設置Content-Encoding響應頭為gzip。如果客戶機不支持gzip,則將未修改過的字符數組復制到ByteArrayOutputStream。最后,doFilter通過將整個字符數組(可能是壓縮過的)寫到與original響應相關的OutputStream中,發送結果到客戶機。
3)對LongServlet進行注冊。
CompressionFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
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 \"-//W3C//DTD HTML 4.0 " + "Transitional//EN\">\n" ; String id="codetool">
延伸 · 閱讀
精彩推薦
|