2010年7月23日 星期五

JSP & Servlet Session、Cookie處理

Session幫助我們記錄與 Client 端溝通過程的一切,並將之存放在記憶體中
因為HTTP連線協定並不是持續的存在,所以他無法紀錄請求的資訊
對container來說,每個請求都來自新的 Client 端
所以我們會建立具有獨特身分的session來辨識客戶及其需求

溝通的過程中,交換辨識身分用的資訊是必要的
最簡單而且最常見的方式是透過Cookie來交換這項資訊
所以 session 的實作有很大的機會與Cookie相關

正常情況下,也就是 Client 端沒關閉Cookie的功能時
container會自動產生辨識此 Client 端身分用的ID
Client 端會建立一個叫做 jsessionid 的Cookie來儲存此 ID
Server 端會根據這個Cookie裡的資料來對應產生的session
撰寫的程式不應該自行請求 jsessionid 參數( getParameter("jsessionid") )
也不應該在 header 裡增加 jsessionid 屬性,以免Cookie被覆寫
可以由container代為處理的部分千萬不要自己動手
Cookie相關的處理由container代勞,我們只需要關注簡短的session處理即可
程式碼範例:
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
                                         throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
       
        HttpSession session = request.getSession();
       
        if(session.isNew()){
            out.println("A new session");
        }else{
            out.println("Welcome back ! ");
        }
    } 

request.getSession()這行程式碼會回傳session
尚未有session的情況下,則會自動產生session ID並以它建立session後回傳
getSession() 裡接受boolean型別參數,能做出篩選已存在session的效果
getSession(true) 跟 getSession()作用相同
getSession(false) 表示希望得到已經存在的session,找不到時會回傳null值
session.isNew() 則如同它字面上的意思般易懂,客戶端還沒用這個session回覆時會回傳true
這邊再提醒一次,一般來說container會自動產生辨識此 Client 端的session
所以這個函式通常只是回傳container自動建立的session
實際運用上,用 session.isNew() 判斷session的存在會比較省事

關閉Cookie會讓 Client 端沒辦法加入session
session.isNew()會一直回傳 true,也不會有警告訊息提醒無法將session關連到 Client 端
解決關閉Cookie的方法是我們在URL上面附加辨識身分的資訊來協助對應session
範例程式碼:
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
                                         throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        //to get session if Cookie function is open
        HttpSession session = request.getSession();
       
        out.println("<html><body>");
        out.println("<a href=\"" + response.encodeURL("/destination")
                    + "\">click me</a>");
        out.println("</body></html>");
    }

URL的 rewrite 只有在container嘗試Cookie失敗,和有呼叫回應物件的 encodeURL() 後才發生
container看到 getSession() 後,會同時使用Cookie和附加資訊在URL後
接著由客戶端傳來下一個請求時,getSession()會讓 container 從請求中讀取session ID
若可以藉由Cookie取得,container會忽略 encodeURL() 的呼叫,也省下重寫URL的麻煩
需要使用重導( Redirect )的場合,處理方法也很類似
以response.encodeRedirectURL("/destination")來重寫URL
URL重寫的處理方式由container的廠商提供,這就不在我們觀察的重點裡了
另外,產生URL重寫的條件之一是必須由動態網頁處理
所以不能使用靜態html網頁

session物件儲存在Server的記憶體裡
閒置的session會浪費系統資源,所以必須有方法來清除閒置的session
HttpSession 除了 getAttribute()、setAttribute()、removeAttribute()等針對屬性的method外
還有幾個method可以使用:
1.getCreationTime() 傳回session第一次被建立的時間
   此method可以計算session到目前為止的存活時間,進而做到限制session存活的時間長度

2.getLastAccessedTime() 回傳container最後一次收到與此session ID相關的請求的時間
   可以判斷客戶是否離開很長的時間,並提醒客戶或是直接用 invalidate() 砍掉session
 
3.setMaxInactiveInterval() 指定針對此session的不同請求的最長時間間隔
   客戶若沒有在指定時間內做出請求,會讓此session失效

4.getMaxInactiveInterval() 取得針對此session的不同請求的最長時間間隔
   可以用來判斷一個不活動的客戶端的session還有多少的存活時間
 
5.invalidate() 結束此session並釋放資源
   砍掉不要的session並清除其所擁有的屬性。當session被清除後再去呼叫會回傳例外

除了使用上述方法主動砍掉session外,container也提供了機制來清除session
上面提到的 setMaxInactiveInterval() ,會讓沒有在指定的秒數內作出回應的session被清除
另外也可以在DD裡組態設定讓container主動回收沒有在指定時間內做儲回應的session
範例:
web.xml================

............
  </servlet>

  <session-config>
      <session-timeout>20</session-timeout>
  </session-config>
............

不過需要注意的是,setMaxInactiveInterval()與DD兩方法的輸入參數的時間單位不同
setMaxInactiveInterval()裡需填入存活秒數,DD組態所設定的是存活的分鐘數

JavaEE的Cookie原始設計是用來協助session,但也可以利用它來做一些事
Server將Cookie傳給 Client 端,Client 端則會在產生另一個請求時將它送回
Java的Cookie預設的存活時間跟session一樣,在瀏覽器關閉時即消失
想讓它保持用長久的影響力可以手動設定其存活秒數
範例如下:
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
                                         throws ServletException, IOException {
        response.setContentType("text/html");
        String name = request.getParameter("username");

        Cookie cookie = new Cookie("username",name);
        cookie.setMaxAge(30*60);         //set Life of Cookie to 30 minutes
        response.addCookie(cookie);
    }

瀏覽器協助建立Cookie時,會依照網站別建立不同的儲存資料夾
所以不用擔心有相同命名的Cookie的值會被洗掉

另外取得Cookie的方法也需要注意一下
必須用陣列迴圈找出來,沒有用Cookie的header名稱去找的method
如果存活時間設定成 0,Cookie就會立即失效
設定成負值則是在瀏覽器關閉後失效
取得要求物件的方法也可以像範例那般自行傳入
使用方式就必須在JSP頁面中寫出如 <%=xxx.isCookieSet(request) %> 一般的程式碼
前面的 xxx 是在這個頁面中使用的 bean 名稱,request 是JSP的隱含物件
下一篇筆記會談到較詳細的JSP使用方式
範例:
    public boolean isCookieSet(HttpServletRequest request){
        Cookie[] cookies = request.getCookies();
       
        if(cookies != null){
            for(int i = 0; i < cookies.length; i++){
                Cookie cookie = cookies[i];
                .....
            }
        }
      ....
    }

HttpSession物件裡還有一個重要的部分沒有提過
那就是針對session搬遷的處理
session被搬移的實例在分散式系統中比較容易見到
分散式系統會為了達到平衡負載的目的,而可能將請求分別送往不同的JVM中
因此 Client 端對同一個Servlet所提出的複數個請求很有可能會在不同的JVM上
如何處理session也會是一個課題

HttpSession物件的處理要遵守其原則
應用程式裡的每個session ID都只會對應到一個HttpSession物件
在同個瀏覽器裡的請求一定會有發生的先後順序
所以HttpSession物件會依照請求順序搬移到該請求所在的JVM上
而偵聽器篇筆記裡提過的HttpSessionActivationListener就是為了應付搬遷要求而產生的
它能讓HttpSession物件在搬遷前作些準備,以及在搬遷後重新活化

實務運用上,只要屬性是可序列化(Serializable)的,它也可以跟著session一起被搬遷
所以HttpSessionActivationListener這個偵聽器是很有可能不會被使用到的
實作Serializable介面的物件可以選擇實作 writeObject() 和 readObject() 兩個method
writeObject()可以在序列化期間將不可序列化的欄位變成 null,並在 readObject()時將之恢復
然而,這些方法不一定會在session搬遷期間被呼叫
拿捏自身需求及時機掌握來選擇要如何實作

此外,HttpSessionBindingEvent 這個與屬性相關的事件裡有一些方法需要注意
getName() 會以String型別回傳觸發事件的屬性的名稱
getValue() 回傳觸發事件的屬性的值,但注意觸發時間點是在被修改前,也就是傳回原本的值

沒有留言:

張貼留言