session和cookie 2

轉載地址:http://www.blogjava.net/fancydeepin/archive/2013/10/02/cookie_and_session.html


HTTP 協議 ( 超文本傳輸協議 ) 是無狀態的,不能保存客戶端與服務器之間通訊 ( 交互 ) 的信息。
打個比方,拿最常見的登錄來說,現在好多網站的操作都需要用戶登錄,假如在 a 操作時,用戶成功登錄系統,再進行 b 操作時,由於 HTTP 協議是無狀態的,
用戶之前登錄的信息並沒有被記錄下來。那麼,用戶需要再次登錄系統才能繼續操作,以此類推,每個操作都需要登錄一次系統,這是非常可怕的事情。
cookie 和 session 技術的誕生,都是爲解決 HTTP 無狀態協議所帶來的系列問題。它們可以維護用戶與服務器間的會話狀態。

Cookie

cookie 是保存在客戶端的文本文件。在這個文本文件中以鍵值對 ( key - value ) 的形式保存了一些與用戶相關的信息,如常見的有賬號信息等。


package fan.commons.web;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * Cookie 操作
 * @Author fancy
 * @Mail [email protected]
 * @Date 2013-09-29
 
*/
public class Cookies {
    
    private final HttpServletRequest request;
    private final HttpServletResponse response;
    
    public Cookies(final HttpServletRequest request, final HttpServletResponse response){
        this.request = request;
        this.response = response;
    }
    
    /**
     * 添加Cookie
     * 
@param cookie 實例
     
*/
    public void add(Cookie cookie){
        response.addCookie(cookie);
    }
    
    /**
     * 添加Cookie
     * 
@param name  名稱
     * 
@param value 值
     
*/
    public void add(String name, String value){
        add(name, value, -1);
    }
    
    /**
     * 添加Cookie
     * 
@param name  名稱
     * 
@param value 值
     * 
@param secondTimeout 設定多少秒後過期。若爲負數, 則瀏覽器關閉後Cookie失效。
     
*/
    public void add(String name, String value, int secondTimeout){
        Cookie cookie = new Cookie(name, value);
        cookie.setMaxAge(secondTimeout);
        cookie.setPath("/");
        response.addCookie(cookie);
    }
    
    /**
     * 根據名稱獲取Cookie對象。如果不存在則返回null。
     * 
@param name 名稱
     * 
@return 返回Cookie對象
     
*/
    public Cookie getCookie(String name){
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
            for(Cookie cookie : cookies){
                if(cookie.getName().equals(name)){
                    return cookie;
                }
            }
        }
        return null;
    }
    
    /**
     * 根據名稱獲取Cookie對象的值。如果不存在則返回null。
     * 
@param name  名稱
     * 
@return  返回該Cookie對象的值
     
*/
    public String getValue(String name){
        Cookie cookie = getCookie(name);
        return cookie == null ? null : cookie.getValue();
    }
    
    /**
     * 刪除Cookie
     * 
@param name 名稱
     
*/
    public void delete(String name){
        delete(new Cookie(name, null));
    }
    
    /**
     * 刪除Cookie
     * 
@param cookie 需要刪除的對象
     
*/
    public void delete(Cookie cookie){
        cookie.setMaxAge(0);  //0表示立即刪除
        cookie.setPath("/");
        response.addCookie(cookie);
    }
    
    /**
     * 清空Cookie
     
*/
    public void clear(){
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
            for(Cookie cookie : cookies){
                delete(cookie);
            }
        }
    }
}

public class CookieServlet extends HttpServlet {

    protected void service(HttpServletRequest request, HttpServletResponse response) {
        Cookies cookie = new Cookies(request, response);
        if(cookie.getValue("username") == null){
            cookie.add("username", "fancy");
        }
        //從客戶端傳遞過來的 cookie 中獲取信息
        System.out.println(cookie.getValue("username"));
    }

}



當客戶端發起 http 請求時,相關的 cookie 信息將被放在 http 請求頭 ( Request Headers ) 中,併發送至服務器端。
當服務器向客戶端寫回 ( 新建、更新、刪除 ) cookie 時,這些 cookie 信息將被放到 http 響應頭 ( Response Headers ) 中,併發回給客戶端瀏覽器。
客戶端瀏覽器接收並解析服務器發回的 cookie 信息,然後將它保存到文件或保存到內存當中。




Session

Session 是保存在服務器端,用於存放一些與用戶相關的信息,如登錄狀態等。

Session 的創建時機

首先,Session 不是在客戶機首次訪問服務器時被創建的。
Session 的創建時機是當服務器端程序首次執行 HttpServletRequest.getSession() 或 HttpServletRequest.getSession(true) 時被創建的。
HttpServletRequest.getSession() 和 HttpServletRequest.getSession(true) 是等效的,
調這兩個方法的時候,若 Session 已經存在,則直接返回使用。若不存在,則創建一個 Session 對象並返回使用。
HttpServletRequest.getSession(false) ,調用該方法的時候,若 Session 已經存在,則返回使用。若不存在,則返回 null。
下面藉助 Session 監聽器來監聽 Session 的創建時機:

package fan.core.listener;

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class SessionListener implements HttpSessionListener {
    
    public SessionListener(){
        System.out.println("*******************************");
        System.out.println("Startup SessionListener");
        System.out.println("*******************************");
    }

    public void sessionCreated(HttpSessionEvent e) {
        System.out.println("*******************************");
        System.out.println("Create Session");
        System.out.println("*******************************");
    }

    public void sessionDestroyed(HttpSessionEvent e) {
        System.out.println("*******************************");
        System.out.println("Destroy Session");
        System.out.println("*******************************");
    }

}

public class TestServlet extends HttpServlet {

    protected void service(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("*******************************");
        System.out.println("TestServlet");
        System.out.println("*******************************");
    }

}

<listener>
  <listener-class>fan.core.listener.SessionListener</listener-class>
</listener>

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

訪問 TestServlet,發現控制檯並沒有打印出 "Create Session"。
修改 TestServlet 代碼如下:

public class TestServlet extends HttpServlet {

    protected void service(HttpServletRequest request, HttpServletResponse response) {
        request.getSession(true);
    }

}

再次訪問該 TestServlet,控制檯打印出 "Create Session"。

重啓服務器,在瀏覽器中首次訪問服務器中的任一 JSP 資源,發現控制檯也打印出 "Create Session"。這是怎麼回事呢?
是這樣的,JSP 引擎在將 JSP 文件編譯成類 Servlet 類文件 ( 該文件可以在 %Tomcat%/work/%工程目錄% 下找到 ) 時,還是打開 index_jsp.java 源碼來對照着說吧:



可以看到源碼中有一行 session = pageContext.getSession()。
這裏的 pageContext 是 org.apache.jasper.runtime.PageContextImpl 的一個實例 ( 這個驗證非常簡單,有興趣的可以動手試試 ),
打開 PageContextImpl 源碼看一眼:



可以看到,JSP 默認的實現方式中去調了 HttpServletRequest.getSession() 方法,因此,當第一次訪問某個 JSP 文件時,
如果服務器之前還沒有爲該用戶創建過 Session,則會創建一個,而不管 Session 會不會被用到。
這裏可以通過 <%@page session="false"%> 來關閉 JSP 中默認創建 Session 的行爲。
當在 jsp 文件中設置 <%@page session="false"%>,訪問這個 jsp 文件,如 index.jsp,那麼再打開 index_jsp.java 文件,
發現 session = pageContext.getSession() 這一行不見了,控制檯也沒有打印出 "Create Session" 的字樣。

JSESSIONID ( session cookie / sessionid )

同一時間裏,服務器端存在許多 Session 對象。假如一個 Session 對象代表一個用戶,那麼,當某客戶機請求服務器時,服務器如何知道哪個 Session 屬於 哪個用戶呢?
這是通過唯一標識 id 來識別的。服務器首先檢查客戶端請求裏是否包含 session 標識,如果已經包含,說明服務器之前已經爲該用戶創建了 session,接着服務器根據這個 session 標識去檢索對應的 session 對象。如果檢索不到 (可能因爲 session 過期已被銷燬),則可能創建一個新的 session 對象 (HttpServletRequest.getSession(false) 的時候不創建)。如果客戶端請求裏不包含 session 標識,則創建一個新的 session 對象。服務器在創建 session 對象的時候會爲每個 session 對象關聯一個唯一的標識 sessionid,並將該標識的值以 cookie 的方式寫回給客戶端瀏覽器,保存在客戶端瀏覽器內存中。當客戶端再次向服務器發起請求時,根據 HTTP 協議,cookie 會被攜帶到服務器端,而 cookie 裏面保存了一個能唯一標識用戶自己身份的 sessionid,服務器端根據這個 id 就能找得到屬於用戶自己的 session 了。
從上面描述可以知道,session 是基於 cookie 機制來實現的,因此,上面所提到的 session 標識、sessionid 通常被稱爲 session cookie。我們平常時候所說的 cookie 被稱爲 persistent cookie。session cookie 是保存在客戶端瀏覽器的內存當中,僅在本次會話中有效,當瀏覽器關閉之後,session cookie 也就失效並隨之消失了。
cookie 是通過鍵值對 ( Key-Value ) 的形式來保存文本信息的,在 Java Web 運用中,session cookie 的鍵的名稱爲 JSESSIONID。看到 JSESSIONID,相信很多人都有一種親切感,一般情況下,通常我們是看不到 JSESSIONID 的,因爲它保存在 cookie 中,每次隨請求默默的發送至服務器端。

修改 SessionListener 的 sessionCreated 方法:

public void sessionCreated(HttpSessionEvent e) {
    System.out.println("*******************************");
    System.out.println("Create Session, id = " + e.getSession().getId());
    System.out.println("*******************************");
}

public class TestServlet extends HttpServlet {

    protected void service(HttpServletRequest request, HttpServletResponse response) {
        request.getSession(true);
    }

}

訪問 TestServlet,結果如圖:



public class TestServlet extends HttpServlet {

    protected void service(HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession(true);
        System.out.println("[ Session ] " + session.getId());
        Cookies cookies = new Cookies(request, response);
        System.out.println("[ Cookies ] " + cookies.getValue("JSESSIONID"));
    }

}



據說,在客戶端禁用 cookie 的環境下,服務器會通過 URL 重寫的方式,將 JSESSIONID 作爲參數傳遞到服務器端。
本人不才,用了好些個瀏覽器禁用 cookie 幾經折騰,並沒有發現目標 URL 被重寫,也沒有發現請求參數中含有 JSESSIONID。什麼情況? 

 Session 銷燬的時機

當 session 有效期已過或服務器端程序調用了 HttpSession.invalidate(),則 session 對象被銷燬,session 的內存空間也隨之被 GC 收回。
客戶端關閉瀏覽器時,存儲在服務器端的 session 並不會被立刻銷燬,直到 session 有效期過了,服務器纔來銷燬 session 對象。

Session 與瀏覽器的關係

根據上面對 JSESSIONID 的闡述,客戶端瀏覽器每次請求服務器時,都會將能唯一識別用戶 session 的 JSESSIONID 攜帶至服務器,以作爲用戶識別的身份憑據。
另外也知道,JSESSIONID 是保存在客戶端瀏覽器的 cookie 文件中,存儲在瀏覽器內存當中,其生命週期與瀏覽器相同。當多個瀏覽器請求同一臺服務器時,在首次請求中,服務器發現這些瀏覽器都沒有攜帶能標識用戶 Session 的 JSESSIONID,因此,服務器分別爲這些瀏覽器創建一個 Session 對象,並通過 HTTP 的響應頭消息將 Session 的 id 以 cookie 的方式寫回給客戶端瀏覽器。因此,不同的瀏覽器所使用的 session 是不一樣的 ( 這裏只針對一般的情況 )。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章