四、實現偽會話管理機制
下面所介紹的工程稱為PseudoSession,它是偽會話機制一個很簡單的實現。考慮到移植性,我們以JavaBean的形式實現它。PseudoSessionBean的完整代碼可以從本文後面下載。
PseudoSessionBean擁有如下域(Field):
public String path;public long timeOut;
path是保存所有會話文本文件的目錄。如果Web伺服器的數量在一個以上,這個目錄必須允許所有伺服器訪問。然而,為了防止用戶直接訪問這些文本文件,這個路徑應該不允許用戶直接訪問。解決這個問題的一種方法是使用Web網站根之外的目錄。
timeOut是用戶的最後一個請求到會話過期作廢之間的時間。在PseudoSessionBean的代碼清單中,timeOut設置成了以毫秒表示的20分鐘,這是一個比較合理的超時時間值。對於任何用戶,如果他在這個超時時間之後才繼續發出請求,他將得到一個新的會話標識符。
PseudoSessionBean有4個方法:getSessionID,setValue,getValue,deleteAllInvalidSessions。
4.1 getSessionID方法
getSessionID方法的聲明如下:
public String getSessionID(HttpServletRequest request)
這個方法應該在每一個JSP頁面的開頭調用。它完成如下任務:
? 如果用戶是第一次訪問,則為該用戶設定一個新的會話標識符。
? 檢查URL所帶會話標識符的合法性。如果會話標識符已經過期,則getSessionID方法返回一個新的會話標識符。
下面我們來看看getSessionID方法的工作過程。
String sessionId = request.getParameter("sessionId");
validSessionIdFound是一個標記,用於指示會話標識符是否合法。validSessionIdFound的初始值是false。
boolean validSessionIdFound = false;
long類型的now變量包含請求出現時的伺服器時間。該變量用於確定用戶會話的合法性。
long now = System.currentTimeMillis();
如果找到了會話標識符,則getSessionID方法檢查它的合法性。檢查過程如下:
? 一個合法的會話標識符必須有對應的同名文本文件。
? 文件的最後修改時間加上timeOut應該大於當前時間。
? 如果存在與會話對應的文本文件,但文件已經過期,則原來的文件被刪除。
? 把合法會話標識符所對應文本文件的最後修改日期改為now。
這些任務主要藉助File對象完成,創建File對象的參數就是會話文本文件的路徑:
if (sessionId!=null) {
File f = new File(path + sessionId);
if (f.exists()) {
if (f.lastModified() + timeOut > now) { // 會話合法// 使用setLastModified時,如果文件已經被其他程序鎖定,// 程序不會產生任何異常,但文件數據不會改變f.setLastModified(now);validSessionIdFound = true; } else { // 會話已經過期 // 刪除文件f.delete(); }} // end if (f.exists) } // end if (sessionId!=null)
如果不存在合法的會話標識符,則getSessionID方法生成一個會話標識符以及相應的文本文件:
if (!validSessionIdFound) { sessionId = Long.toString(now); // 創建文件 File f = new File(path + sessionId); try {f.createNewFile(); } catch (IOException ioe) {}} // end of if !validSessionIdFound
程序保證文件名字隨機性的方法非常簡單:把當前的系統時間直接轉換成會話標識符。對於那些涉及敏感數據的應用,我們應該考慮運用更安全的隨機數生成器來生成會話標識符。
綜上所述,getSessionID並不總是返回新的合法會話標識符:它返回的標識符可能與傳遞給它的標識符相同,也可能是新創建的會話標識符。
為了保證JSP頁面擁有合法的會話標識符以便調用setValue、getValue方法,每個JSP頁面都必須在開頭位置調用getSesstionID方法。
4.2 setValue方法
setValue方法保存value字符串以及與它關聯的字符串名字。這種「名字-值」對很容易使人想起Dictionary對象。setValue方法要求在第一個參數中提供合法的會話標識符,它假定在自己被調用之前getSessionID方法已經執行,經過檢驗的合法會話標識符必然存在,因此它不再對傳入的會話標識符進行合法性檢驗。
setValue方法按如下規則保存名字-值對:
? 如果與value值關聯的name以前還沒有保存過,則新的名字-值對加入到文本文件的末尾。
? 如果value字符串關聯的name值以前已經保存過,則原來保存的值被新的value值替換。
setValue方法按照如下格式保存名字-值對,注意「名字」是大小寫敏感的:
name-1 value-1name-2 value-2name-3 value-3...name-n value-n
setValue方法的聲明如下:
public void setValue(String sessionId, String name, String value)
setValue方法首先尋找與當前會話對應的文本文件。如果不能找到文本文件,則setValue方法不做任何事情直接返回。如果找到了會話文本文件,setValue方法讀取文本文件的各個行,然後比較讀入的行與name:如果讀入的文本行開頭與name一樣,則說明該名字已經保存,setValue方法將替換該行後面的值;如果name不能與讀入的文本行匹配,則這行文本被直接複製到一個臨時文件。
這部分功能的實現代碼如下:
try { FileReader fr = new FileReader(path + sessionId); BufferedReader br = new BufferedReader(fr); FileWriter fw = new FileWriter(path + sessionId + ".tmp"); BufferedWriter bw = new BufferedWriter(fw); String s; while ((s = br.readLine()) != null)if (!s.startsWith(name + " ")) { bw.write(s); bw.newLine();} bw.write(name + " " + value); bw.newLine(); bw.close(); br.close(); fw.close(); bw.close(); . . .}catch (FileNotFoundException e) {}catch (IOException e) { System.out.println(e.toString());}
原來文本文件中的所有行複製到臨時文件之後,setValue方法刪除原來的文本文件,然後把臨時文件改成會話文本文件的名字:
File f = new File(path + sessionId + ".tmp");File dest = new File(path + sessionId);dest.delete();f.renameTo(dest);
4.3 getValue方法
getValue方法用於提取原來保存在偽會話中的數據。正如setValue方法,getValue方法也要求傳入一個合法的會話標識符,而且getValue方法不再對傳入的會話標識符進行合法性檢查。getValue方法的第二個參數是待提取數據的name,返回值是與指定name關聯的value。
getValue方法的聲明如下:
public String getValue(String sessionId, String name)
getValue方法的基本執行過程如下:首先找到會話文本文件,然後按行讀入直至找到與name匹配的文本行;找到匹配的文本行之後,getValue方法返回該行保存的value;如果不能找到,getValue方法返回null。
4.4 deleteAllInvalidSessions方法
deleteAllInvalidSessions方法刪除那些與已經過期的會話關聯的文本文件。由於調用getSessionID方法時過期的會話文本文件會被刪除,deleteAllInvalidSessions方法並不是關鍵的方法。什麼時候調用這個方法由應用自己決定。例如,我們可以編寫一個專用的後臺程序,由這個程序每天一次清除所有過期的文本文件。最簡單的辦法是在JSP文件末尾調用deleteAllInvalidSessions方法,但如果網站比較繁忙,重複地調用deleteAllInvalidSessions方法將降低整個網站的響應能力。一種明智的做法是:編寫一個在訪問量較少的時候自動進行清理的後臺程序。
deleteAllInvalidSessions方法的聲明如下:
public void deleteAllInvalidSessions()
它首先把所有會話文本文件的名字讀入files字符串數組:
File dir = new File(path); String[] files = dir.list();
deleteAllInvalidSessions方法比較文本文件的最後修改時間(加上超時時間)和系統當前時間,確定會話是否過期。long類型的變量now用於保存系統的當前時間。
long now = System.currentTimeMillis();
接下來,deleteAllInvalidSessions方法通過循環訪問files數組,依次檢查每個文件的lastModified屬性。所有與過期會話關聯的文件都將被刪除:
for (int i=0; i now) f.delete(); // 刪除過期的會話文本文件}
五、應用實例
編譯好PseudoSessionBean這個JavaBean之後,我們就可以利用偽會話管理機制來管理Web應用的會話狀態信息了。由於不必再使用伺服器的會話管理機制,我們可以在page指令中把session屬性設置為false關閉默認的JSP/Servlet會話管理功能。
< %@ page session="false" %>
然後,我們用JSP的標記告訴JSP容器程序要使用PseudoSessionBean:
< jsp:useBean id="PseudoSessionId" scope="application" class="pseudosession.PseudoSessionBean" />
在上面這個標記中,class屬性值是「包.類名字」形式。當然,對於不同的包名字,class屬性的值應該作相應的修改。注意Bean的scope屬性是「application」,這是因為我們要在應用的所有頁面中使用這個Bean。在這個應用中,把Bean的scope屬性設置為「application」具有最好的效率,因為我們只需創建Bean對象一次就可以了。另外,正如前面所提到的,getSessionID方法必須在所有其他代碼之前調用。
< % String sessionId = PseudoSessionId.getSessionID(request);%>
為了說明PseudoSessionBean的應用,下面我們來看兩個JSP頁面,它們是index.jsp和secondPage.jsp。index.jsp頁面在偽會話變量中保存用戶的名字,而secondPage.jsp則提取這個用戶名字。
index.jsp頁面的代碼如下:
< %@ page session="false" contentType="text/html;charset=gb2312" %>
< jsp:useBean id="PseudoSessionId" scope="application" class="pseudosession.PseudoSessionBean" />
< % String sessionId = PseudoSessionId.getSessionID(request);%>
< html>
< head>
< title>偽會話< /title>
< /head>
< body>
< h1>偽會話管理機制< /h1>
< % String userName = "bulbul"; PseudoSessionId.setValue(sessionId, "userName", userName);%>
< a href="/secondPage.jsp?sessionId=<";%=sessionId%>>點擊此處
< form method="post" action=anotherPage.jsp?sessionId=< %=sessionId%>>
輸入數據:< input type="text" name="sample">
< input type="submit" name="Submit" value="Submit">
< /form>
< /body>
< /html>
< % PseudoSessionId.deleteAllInvalidSessions();%>
注意,包括