結合《HTTP權威指南》和《How Tomcat Works》話一話我所理解的Session以及Tomcat下的實現方式!
Session是什麼?
在開始正式討論之前,我們首先探討一些原理性的問題,什麼是session,它可以用來做什麼!
我們每天都會瀏覽各種網站,不同的用戶瀏覽網站的目的不同,在該網站上留下的歷史也不同,爲了提供個性化的服務,服務器就需要記錄和識別用戶!一個用戶可以在不同地方,利用不同設備訪問網站,服務器如何知道“我是誰”呢?很多人一下子就想到用戶名和密碼,對沒錯,這的確是一種方式。但是網站訪問是一個連續的過程,每次訪問都攜帶用戶名和密碼的做法提高了用戶風險,更重要的是,如何解決非註冊用戶的問題呢,一些用戶習慣每天打開新聞站點,但是他只是瀏覽信息,不想輸入任何個人信息去註冊網站,這樣的用戶如何提供個性化服務呢!
看看現實生活中政府是如何識別“我是誰”,並且提供差異服務的。我們每個人都有一張身份證,是政府機關發給我們的。身份證只是一個人的基本信息,例如姓名,身份證號,住址。上面並不會紀錄你的學歷,你的婚姻狀況。當我需要這些信息的時候,我們提供身份證,相關的單位就會提供相關證明,例如在很多省份在購買房產的時候,需要未婚人士提供一個單身證明,我們只要向相應的政府機構提供身份證,就可以獲取到這些“被認可的”信息。
可以抽象的理解爲,政府頒發了一個身份證給個人,當你需要政府服務的時候,只需要提供你的身份證,這樣政府就會從它自己維護的數據中,獲取你的更多個人信息,提供差異化服務。
身份證會具有有效期,當身份證過期的時候,需要從新申請,否則持有的身份證就會無效。
哪麼,可否效仿這個過程,網站服務器“頒發”一個身份證給用戶,用戶每次訪問都帶着這個身份證,網站服務器根據這個身份證獲取用戶更多信息,這樣不就可以獲取個性化服務了麼!當身份證過期的時候,需要用戶從新申請。
現在我們要討論的問題漸漸浮出水面了。我們和服務器之間的交互過程,可以理解爲一個Session,直白的翻譯就是一次會話,而每次我們攜帶的身份信息,是通過Cookie的方式實現的。
在《HTTP權威指南中》這樣描述了用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. 瀏覽器驗證
可以看出來,瀏覽器發送的Cookie中,不存在JSESSIONID,因此此刻Session還並沒有創建和維護起來。
不過在服務器的Response中可以看到Set-Cookie 首部
此時,服務器已經把會話建立起來,並且分配了唯一的標示,並且告訴瀏覽器:“下次來要告訴我你是誰哦”
然後我們刷一下瀏覽器,再次請求服務器,可以看到Request如下:
可以看到,這次訪問Cookie中已經加上了JSESSIONID,服務器可以利用這個SessionID,從本地找到Session對象,拿到記錄下來的用戶行爲或者用戶信息了。