2012年10月13日 星期六

JSP 入門:Session

JSP 入門:Session

All examples are solely used for educational purposes. This document is provided as is. You are welcomed to use it for non-commercial purpose.

Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu 在以下的範例中,我們假設你已經在你自己的電腦上面安裝好了 Apache Tomcat 而且 port 為 8080。如果你還沒安裝好,你可以 參考 安裝 Tomcat 5.5.x

請勿轉貼
看其他教材

開發網頁資訊系統(web-based information systems)的人員都應該知道一件事實, 那就網頁與網頁之間是獨立;也就是說,若有兩個網頁 A 和 B,呼叫 A 網頁是一件 獨立的作業,而呼叫 B 網頁又是一個獨立的作業,兩者之間是沒有關係的,網頁 伺服器無法判斷這些網頁是由同一個使用者、或者多個使用者呼叫的;比較專業的說法, HTTP 協定是無狀態的(stateless)(或者翻譯成"不會保留狀態的"比較恰當)。 但是,這種無狀態的協定,在應用系統的使用上卻是非常不方便的; 例如,某些網頁需要知道使用者是否已經登入;若已經登入才可以看網頁的內容, 否則要求使用者登入。一般來說,有三種常用方式可以讓 web server 經由某些 資料來判斷,而這三種方法分別為隱藏資料在網頁中、使用 Cookie、和 Session。 這三種方式中,以 Session 的方式相對安全。
在以下範例中,總共有四個網頁,分別是 Session1.jsp、Sessionw1.jsp、Session2、以及 logout.jsp。Session1.jsp 中包含了 HTML 表單的元件,這些元件允許使用者 選擇某一種程式語言,然後把使用者選擇的語言傳給 Session2.jsp;Seesion2.jsp 會依據 Session1.jsp 傳過來的語言,提供建議書單;最後,使用者點選"結束"來 完成工作。如果使用者利用"加入最愛"(或者加入書籤),然後試圖不經過 Seesion1.jsp 而直接執行 Session2.jsp,Session2.jsp 會直接將使用者導向 Session1w.jsp。
為了完整說明 session 的特性,我們利用三種實例來說明,在這些情境中,我們 特別列印 session 的 id(具有唯一性)到 tomcat 的 console 上,其畫面如下:

在畫面中,三個實例由三個紅色的框框標示出來,並分別以 III、 以及 III 來註明。
  1. 首先,我們先說明實例 II:實例 II 代表正常的使用 方式,也就是使用者經由 Session1.jsp (類似登入畫面)開始使用,Session1.jsp 的畫面如下:
    Session1.jsp 除了包含畫面中的 HTML 表格元件之外,還需要進行一些跟 Session 相關的設定,再進一步說明之前,我們先把 Session1.jsp 的原始碼列示如下:
    <%@page contentType="text/html;charset=Big5" %>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html;charset=Big5">
    <title>
    Test Servlet Sessions
    </title>
    </head>
    
    <body>
    <%
      String id = session.getId();
    
      // 我們自訂 Session 的屬性名稱以及屬性值
      session.setAttribute("sid", id);
    %>
    <form method="POST" action="http://localhost:8080/test/Session2.jsp">
    <strong>請選擇你想進一步了解的電腦語言:</strong><p/>
    <input type="radio" name="lang" value="C">C</input><br/>
    <input type="radio" name="lang" value="C++">C++</input><br/>
    <input type="radio" name="lang" value="Java" checked>Java</input><br/>
    <input type="radio" name="lang" value="VB">Visual Basic</input><p/>
    <input type="submit" value="確定"/>
    <input type="reset"/>
    </form>
    </body>
    </html>
    
    有關於 Session 的程式碼,我們以綠色標示出來。首先,我們看 session 這個 物件;這是一個代表 javax.servlet.http.HttpSession 的物件,這個物件由 JSP 自動產生。這個物件提供了許多方法,其中一個是 getId(),這個方法會 回傳一個字串,該字串代表該 session 的 ID;每一個 session 都有一個唯一的 ID。在程式中,我們取得 session ID 的目的有兩個:一個是為了方便之後的說明, 另一個是介紹 session 的常見方法 -- 以 session ID 來判斷是否為同一個 session。
    session 可以翻譯成"會期";何謂"會期"?從拿起電話、撥電話、對話、一直到掛掉 電話,稱之為一個 session;又,使用者從登入開始、進行資料處理(例如,購物、 填寫問卷等)、一直到登出,這也是一個 session。由於 HTTP 是無狀態的,可是 一個 session 卻常常會使用一連串、多個網頁呼叫,而各個網頁之間又會傳遞一些 共用的資料,這時候就需要一個物件來幫忙儲存這些資料,而這個物件在 JSP/servlet 中就是 HttpSession 的物件。
    如果希望將某一特定資料(例如,帳號/密碼、session ID 等)從 A 網頁傳到 B 網頁, 由於 A 網頁和 B 網頁都是屬於同一個 session,那麼我們就可以為將該資料取一個 名稱(在本範例稱之為 sid),然後利用 setAttrbute(名稱, 資料) 在 A 網頁將該資料儲存到 session 中;之後,在必要的時候(例如在 B 網頁), 就可以利用 getAttribute(名稱) 取得該資料的值。
    假設使用者在 Session1.jsp 的畫面,選擇了 "Java" 並點選了"確定" 按鈕。點選了之後,Session2.jsp 會被執行,而執行的結果如下:

    從之前的討論,我們知道 Seesion2.jsp 可以獲得兩個資料,一個是經由 HTML 表單所算過來的 lang, 以及儲存在 session 中名稱為 sid 的資料。使用 lang 以及 sid 的方式分別在原始碼中(列示如下)以紅色以及綠色標示出來。

    <%@page contentType="text/html;charset=Big5" %>
    <%!
      private final static String names[] =
              {"C", "C++", "Java", "VB"};
      private final static String books[] =
              {"C: How to Program", "C++: How to Program",
               "Java: How to Program", "Visual Basic: How to Program"};
    %>
    
    <html>
    <head><title>Recommended Book List</title></head>
    <body>
    
    <%
    
        String id = (String) session.getAttribute("sid");
    
        // 用來觀察 session ID 的變化,直接寫到 tomcat 的 console,請仔細觀察
        System.out.println(session.getId());
    
        if(session.isNew()) {
          // 用來觀察 session ID 的變化
          System.out.println("NewSession:" + id);
          response.sendRedirect("Session1w.jsp");
    
        } else {
    
          String lang = request.getParameter("lang");
          if(lang == null) {
            // 用來觀察 session ID 的變化
            System.out.println("lang:" + id);
    
            response.sendRedirect("Session1w.jsp");
          } else {
            for(int i=0; i<names.length; i++) {
              if(lang.equals(names[i])) {
                out.println("<h1>" + books[i] + "</h1>");
                break;
              }
            }
          }
        }
    
    %>
    <a href="logout.jsp">結束</a>
    </body>
    </html>
    
    首先說明綠色的部分:由於 Session2.jsp 是一個"會期"中的第二個網頁, 所以一開始最好就檢查"使用者是不是沒有經過 Session1.jsp 而直接進入 Session2.jsp?" 如果是直接進入,我們自然需要把使用者導回 Session1.jsp; 反之,我們開始檢查 lang 的值,並開始處理。
    檢查使用者是否直接進入 Session2.jsp 的常見方式有兩種:一種是利用 Session1.jsp 傳過來的 sid 資料;如果使用者經過 Session1.jsp, 那麼 getAttribute("sid") 必然包含資料;反之,如果直接進入, 則 getAttribute("sid") 的回傳值為 null。在本範例中, 我們並未使用這種檢查方式(但是我們鼓勵讀者試試看),而是讓 sid 列印在 tomcat 的 console 上,以便追蹤。另一種檢查的方式是利用 session 的 isNew() 方法來檢查:若是新的 session,則回傳 true;反之,回傳 false。
    如果使用者是直接進入,我們希望把他導回 Session1.jsp。為了完成這項工作, 在程式碼中我們使用了 response.sendRedirect("Session1w.jsp");, 這個用法以及設計的原因,我們一一描述如下:
    • response 也是 JSP 的一個內建物件,它是用來將資料回傳給瀏覽器 用的。responsesendRedirect(URL) 的方法是將參數的 URL 傳給瀏覽器並指示瀏覽器向 URL 連結。
    • sendRedirect(URL) 中,我們將 URL 設定為 Session1w.jsp 而不是 Session1.jsp,這是因為我們經由實驗發現, 如果是 Session1.jsp 的話,就算我們在程式中加入了結束"會期"的 指令(也就是 invalidate()),"會期"是無法被適當的清除;但是如果 是除了 Session1.jsp 之外的網頁(如 Session1w.jsp),則 "會期"可以被清除乾淨。
    • 請特別注意: 在 JSP 執行了 response.sendRedirect(URL); 之後,由於畫面會直接跳到指定的 URL,大多數人都以為 JSP 程式的執行也已經結束; 但是,事實上卻不是如此,tomcat 會繼續把剩餘的 JSP 程式碼執行完。請試著在 紅色原始碼之後加上 System.out.println("還在執行");,然後觀察 tomcat console 上的訊息,你就可以確認這項說明。因此,我們建議最好在每一個 response.sendRedirect(URL); 之後,馬上加入 return; 的 敘述。我們沒有加的原因是,在這個範例中,response.sendRedirect(URL); 是最後一個被執行的敘述。
    如果 Session2.jsp 是經由 Session1.jsp 才執行的,那麼再來我們就要檢查 lang 的值來決定推薦的圖書。這一段程式碼由紅色標示出來;由於 跟之前的程式大同小異,我們就不多做說明。
    等到使用者確認了推薦圖書之後,正確的作法就是點選"結束"。請注意: 如果使用者沒有點選"結束",這個"會期"是不會立即結束的;除非,使用者關閉整個 瀏覽器(僅關掉瀏覽器的 TAB 似乎沒有辦法結束"會期"),或者等待 30 分鐘(這個 時間是 tomcat 的預設值)。 點選"結束"會執行 logout.jsp,其畫面以及原始碼分別列示如下:


    <%@page contentType="text/html;charset=Big5" %>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html;charset=Big5">
    <title>
    Test Servlet Sessions
    </title>
    </head>
    
    <body>
    <%
      String id = (String) session.getAttribute("sid");
      System.out.println("logout:" + id);
      session.invalidate();
    %>
    你已經登出,<a href="Session1.jsp">重新選擇程式語言</a>。
    </body>
    </html>
    
    logout.jsp 的程式碼中,唯一跟之前不太相同的地方,在於 session.invalidate();;這是將"會期"結束掉的方法。在結束 實例 II 的說明之前,請讀者看一下 tomcat 的 console(也是本網頁的 第一張圖):在執行到 Session2.jsp 的時候,我們刻意在程式中 把本 session 的 ID 列印出來(你如果跟著做,你的 session ID 應該跟 我們的不同,這沒關係;重點是 ID 應該在同一個"會期"是相同的,而不同的 "會期",會有不同的 ID)。等到執行 logout.jsp 的時候,我們 還是把 session ID 列印出來;從圖中,我們可以知道利用 sid 可以傳遞同一個 ID 在不同的網頁中。請緊接著看實例 III 的說明,來進一步 了解"會期"。
  2. 實例 III:假設這個時候,使用者直接執行 Session2.jsp(可以是直接 輸入 URL 或者之前已經存在"我的最愛"),這時候會發生什麼事?
    1. 首先,程式會試圖擷取 sid 的值;但是由於之前的 session 已經 被結束掉了(執行了 invalidate()),所以 sid 的值不 存在,因此 id 的值為 null
    2. 由於舊的"會期"已經結束了,因此在預設的情形下,tomcat 會產生一個新的 "會期",所以 session.getId() 會回傳一個新的 ID;請比較圖中 II 和 III 的 ID, 兩個 session 的 ID 並不相同:一個是 "A2AE13445....",另一個是 "8CF60C5...", 這代表一個新的"會期"已經產生了。
    3. 由於一個新的"會期"產生了,session.isNew() 回傳 true;又由於 id 的值為 null,所以 tomcat 的 console 上會列印 "NewSession:null",這也可以 從圖中看出。
    4. 最後,由於我們不希望使用者直接進入 Session2.jsp,所以 我們把網頁導向 Session1w.jsp
    Session1w.jsp 的執行畫面以及原始碼如下:


    <%@page contentType="text/html;charset=Big5" %>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html;charset=Big5">
    <title>
    Test Servlet Sessions
    </title>
    </head>
    
    <body>
    <%
      String id = (String) session.getAttribute("sid");
      System.out.println("Waring:" + id);
      session.invalidate();  
    %>
    你必須先選擇語言,<a href="Session1.jsp">請選擇</a>。
    </body>
    </html>
    
    Session1w.jsp 的內容幾乎跟 logout.jsp 相同; 跟 Session2.jsp,它試圖擷取 sid 的值,但是 因為是 null,所以 tomcat 的 console 上會出現 "Warning:null" 的訊息。
  3. 實例 I:實例 I 和實例 III 類似,其中唯一的差別就是執行的 時機:實例 III 代表在結束一個"會期"後,直接執行 Session2.jsp 的情形;而實例 I 代表在一開始沒有任何舊的"會期"的情形下,直接執行 Session2.jsp 的情形。從觀察圖中的結果,我們可以清楚的看出每一個新"會期" 會有一個唯一的 ID;如果"會期"結束,那麼為之前"會期"設定的屬性也會被 清除掉。
























Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu







沒有留言:

張貼留言