Tomcat 服務器 Session的實現

session

結合《HTTP權威指南》和《How Tomcat Works》話一話我所理解的Session以及Tomcat下的實現方式!


Session是什麼?

在開始正式討論之前,我們首先探討一些原理性的問題,什麼是session,它可以用來做什麼!
我們每天都會瀏覽各種網站,不同的用戶瀏覽網站的目的不同,在該網站上留下的歷史也不同,爲了提供個性化的服務,服務器就需要記錄和識別用戶!一個用戶可以在不同地方,利用不同設備訪問網站,服務器如何知道“我是誰”呢?很多人一下子就想到用戶名和密碼,對沒錯,這的確是一種方式。但是網站訪問是一個連續的過程,每次訪問都攜帶用戶名和密碼的做法提高了用戶風險,更重要的是,如何解決非註冊用戶的問題呢,一些用戶習慣每天打開新聞站點,但是他只是瀏覽信息,不想輸入任何個人信息去註冊網站,這樣的用戶如何提供個性化服務呢!

看看現實生活中政府是如何識別“我是誰”,並且提供差異服務的。我們每個人都有一張身份證,是政府機關發給我們的。身份證只是一個人的基本信息,例如姓名,身份證號,住址。上面並不會紀錄你的學歷,你的婚姻狀況。當我需要這些信息的時候,我們提供身份證,相關的單位就會提供相關證明,例如在很多省份在購買房產的時候,需要未婚人士提供一個單身證明,我們只要向相應的政府機構提供身份證,就可以獲取到這些“被認可的”信息。
可以抽象的理解爲,政府頒發了一個身份證給個人,當你需要政府服務的時候,只需要提供你的身份證,這樣政府就會從它自己維護的數據中,獲取你的更多個人信息,提供差異化服務。
身份證會具有有效期,當身份證過期的時候,需要從新申請,否則持有的身份證就會無效。

哪麼,可否效仿這個過程,網站服務器“頒發”一個身份證給用戶,用戶每次訪問都帶着這個身份證,網站服務器根據這個身份證獲取用戶更多信息,這樣不就可以獲取個性化服務了麼!當身份證過期的時候,需要用戶從新申請。

現在我們要討論的問題漸漸浮出水面了。我們和服務器之間的交互過程,可以理解爲一個Session,直白的翻譯就是一次會話,而每次我們攜帶的身份信息,是通過Cookie的方式實現的。

在《HTTP權威指南中》這樣描述了用Cookie實現的身份認證過程。

cookie

Cookie的作用就好像服務器,給用戶發的一個身份證,上面寫着,“我叫XX,地址”,用戶訪問服務器的時候,攜帶着這些信息,服務器會讀取所有的這些信息。

用戶首次訪問Web 站點時,服務器對用戶一無所知。爲了能夠在用戶下次訪問的時候,識別出這個用戶,站點給用戶“拍上”一個獨有的cookie,這樣以後服務器就可以識別出這個用戶了。Cookie包含了一個由名字=值這樣的信息構成的列表,並通過Set-Cookie或者Set-Cookie2 HTTP響應首部返回給用戶。瀏覽器再下次訪問站點的時候,會在Cookie請求首部中,原封不動地攜帶服務器發送過來的Cookie數據。

其實Cookie的可以攜帶任意信息,它也是服務器維護用戶Session的最常用的手段的一種。服務器真對用戶的訪問,創建一個擁有唯一ID的Session,然後通過Set-Cookie把這個SessionId貼到用戶身上。用戶每次訪問服務器都帶着這個SessionId,服務器就可以通過SessionId找出對應的Session對象,識別出用戶了。

當然這仍然是很抽象的描述,不同的Web服務器實現這個機制也有很大差異。下面通過Tomcat的源代碼來看一下Tomcat作爲Web服務器,如何維護Session,如何根據Cookie來獲取用戶Session的。


Tomcat Session的實現

大概的流程可以分爲如下幾個步驟。

1. 從Cookie中獲取SessionID

針對每一次用戶的請求,HTTPProcessor負責解析用戶請求,會對HTTP請求頭部分析,如果發現存在jsessionid這樣的cookie,就把cookie值set到HttpSevletRequest對象中

if (name.equals("cookie")) {
   Cookie cookies[] = RequestUtil.parseCookieHeader(value);
   for (int i = 0; i < cookies.length; i++) {
      if (cookies[i].getName().equals("jsessionid")) {
         // Override anything requested in the URL
         if (!request.isRequestedSessionIdFromCookie()) {
            // Accept only the first session id cookie
            request.setRequestedSessionId(cookies[i].getValue());
            request.setRequestedSessionCookie(true);
            request.setRequestedSessionURL(false);
         }
      }
      request.addCookie(cookies[i]);
   }
}

2. 獲取Session,如果不存在創建新Session

當需要使用Session訪問其中內容的時候,會調用Request的getSession方法

private HttpSession doGetSession(boolean create) {
        // There cannot be a session if no context has been assigned yet
        if (context == null)
            return (null);

        // Return the current session if it exists and is valid
        if ((session != null) && !session.isValid())
            session = null;
        if (session != null)
            return (session.getSession());

        // Return the requested session if it exists and is valid
        Manager manager = null;
        if (context != null)
            manager = context.getManager();

        if (manager == null)
            return (null);      // Sessions are not supported

        if (requestedSessionId != null) {
            try {
                session = manager.findSession(requestedSessionId);
            } catch (IOException e) {
                session = null;
            }
            if ((session != null) && !session.isValid())
                session = null;
            if (session != null) {
                return (session.getSession());
            }
        }

        // Create a new session if requested and the response is not committed
        if (!create)
            return (null);
        if ((context != null) && (response != null) &&
            context.getCookies() &&
            response.getResponse().isCommitted()) {
            throw new IllegalStateException
              (sm.getString("httpRequestBase.createCommitted"));
        }

        session = manager.createSession();
        if (session != null)
            return (session.getSession());
        else
            return (null);

}

簡而言之如果之前設置過SessionId,那麼request會在Manager中尋找Session,如果有則直接使用,如果不存在,那麼就讓Manager創建一個Session。

需要補充一點,Tomcat的Session的創建和維護是通過實現Manager接口實現的,其中StandardManager是標準實現,當然真對不同應用場景,例如集羣Tomcat,Session需要各個服務器共享,則需要使用不同的Manager來實現Session的維護。

3. 如果Session存在,在Response中,設置SetCookie
在HTTPResponseBase中的SetHeader方法中,檢查Request中Session對象是否存在,如果存在,則把SessionId寫在Set-Cookie首部

if ((session != null) && session.isNew() && (getContext() != null) && getContext().getCookies()) {
	Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, session.getId());
	cookie.setMaxAge(-1);
	String contextPath = null;
	if (context != null)
	    contextPath = context.getPath();
	if ((contextPath != null) && (contextPath.length() > 0))
	    cookie.setPath(contextPath);
	else
	    cookie.setPath("/");
	if (hreq.isSecure())
		cookie.setSecure(true);
	addCookie(cookie);
}

	// Send all specified cookies (if any)
synchronized (cookies) {
	Iterator items = cookies.iterator();
	while (items.hasNext()) {
		Cookie cookie = (Cookie) items.next();
		outputWriter.print(CookieTools.getCookieHeaderName(cookie));
		outputWriter.print(": ");
		outputWriter.print(CookieTools.getCookieHeaderValue(cookie));
		outputWriter.print("\r\n");
	}
}

其實

SESSION_COOKIE_NAME

就是JSESSIONID,至於tomcat爲什麼使用這個字符串,可能就要和開發tomcat的程序員探討一下了,其實她可以叫任何名字。
我們注意到在設置Cookie的時候,有一個CookieTools.getCookieHeaderName的方法,該方法根據HTTP版本號,決定使用Set-Cookie還是Set-Cookie2

public static String getCookieHeaderName(Cookie cookie) {
    int version = cookie.getVersion();

    if (version == 1) {
        return "Set-Cookie2";
    } else {
        return "Set-Cookie";
    }
}

 4. 瀏覽器驗證

首先我初次請求服務器,瀏覽器發送的數據如下:
request

可以看出來,瀏覽器發送的Cookie中,不存在JSESSIONID,因此此刻Session還並沒有創建和維護起來。

不過在服務器的Response中可以看到Set-Cookie 首部
response
此時,服務器已經把會話建立起來,並且分配了唯一的標示,並且告訴瀏覽器:“下次來要告訴我你是誰哦”

然後我們刷一下瀏覽器,再次請求服務器,可以看到Request如下:
nextrequest
可以看到,這次訪問Cookie中已經加上了JSESSIONID,服務器可以利用這個SessionID,從本地找到Session對象,拿到記錄下來的用戶行爲或者用戶信息了。


 

 

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