HTTP協議 Tomcat Session管理的工作原理

HTTP是一個屬於應用層的面向對象的協議,由於其簡捷、快速的方式,適用於分佈式超媒體信息系統。

HTTP協議的主要特點:
1.支持客戶/服務器模式。
2.簡單快速:客戶向服務器請求服務時,只需傳送請求方法和路徑。請求方法常用的有GET、HEAD、POST。由於HTTP協議簡單,使得HTTP服務器的程序規模小,因而通信速度很快。
3.靈活:HTTP允許傳輸任意類型的數據對象。正在傳輸的類型由Content-Type加以標記。
4.無連接:無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開連接。採用這種方式可以節省傳輸時間。
5.無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺少狀態意味着如果後續處理需要前面的信息,則它必須重傳,這樣可能導致每次連接傳送的數據量增大。另一方面,在服務器不需要先前信息時它的應答就較快。 

HTTP協議詳解之請求篇

http請求由三部分組成,分別是:請求行、消息報頭、請求正文。



挖掘Session的原理和tomcat實現機制。   
由於HTTP是無狀態的協議,客戶程序每次都去web頁面,都打開到web服務器的單獨的連接,並且不維護客戶的上下文信息。如果需要維護上下文信息,比如用戶登錄系統後,每次都能夠知道操作的是此登錄用戶,而不是其他用戶。對於這個問題,存在三種解決方案:cookie,url重寫和隱藏表單域。

1、cookie
   cookie是一個服務器和客戶端相結合的技術,服務器可以將會話ID發送到瀏覽器,瀏覽器將此cookie信息保存起來,後面再訪問網頁時,服務器又能夠從瀏覽器中讀到此會話ID,通過這種方式判斷是否是同一用戶。

 1 請求:  
 2 POST /ibsm/LoginAction.do HTTP/1.1  
 3 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*  
 4 Referer: http://192.168.1.20:8080/crm/  
 5 Accept-Language: zh-cn  
 6 Content-Type: application/x-www-form-urlencoded  
 7 UA-CPU: x86  
 8 Accept-Encoding: gzip, deflate  
 9 User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2)  
10 Host: 192.168.1.20:8080  
11 Content-Length: 13 
12 Connection: Keep-Alive  
13 Cache-Control: no-cache  
14   
15 username=jack 
16   
17 響應:  
18 HTTP/1.1 200 OK  
19 Server: Apache-Coyote/1.1  
20 Set-Cookie: JSESSIONID=3267A671BFEAA147A2383B7E083D4G7E; Path=/crm 
21 Content-Type: text/html;charset=GBK  
22 Content-Length: 436  
23 Date: Sat, 10 June 2009 12:43:26 GMT

生成響應的時候,服務器向客戶端發送cookie。cookie的屬性是JSESSIONID,值是267A671BFEAA147A2383B7E083D4G7E。以後每次客戶端請求時,都會附上此cookie,服務器端就可以讀取到。

1    1. GET /ibsm/ApplicationFrame.frame HTTP/1.1  
2    2. Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*  
3    3. Accept-Language: zh-cn  
4    4. UA-CPU: x86  
5    5. Accept-Encoding: gzip, deflate  
6    6. User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2)  
7    7. Host: 192.168.1.20:8080  
8    8. Connection: Keep-Alive  
9    9. Cookie: JSESSIONID=267A671BFEAA147A2383B7E083D4G7E 

服務器端根據讀取到的JSESSIONID,在一個map裏面查找其對應的session對象,這個map的key是jsessionid的值,value是session對象。


2、URL重寫
重寫這種方式,客戶端程序在每個URL的尾部自動添加一些額外數據,這些數據以表示這個會話,比如
http://192.168.1.20:8080/crm/getuserprofile.html;jsessionid=abc123。URL重寫的額外數據是服務器自動添加的,那麼服務器是怎麼添加的呢?Tomcat在返回Response的時候,檢查JSP頁面中所有的URL,包括所有的鏈接,和 Form的Action屬性,在這些URL後面加上“;jsessionid=xxxxxx”。添加url後綴的代碼片段如下:
org.apache.coyote.tomcat5.CoyoteResponse類的toEncoded()方法支持URL重寫。   

1 StringBuffer sb = new StringBuffer(path);
2         if( sb.length() > 0 ) { // jsessionid can't be first.
3             sb.append(";jsessionid=");
4             sb.append(sessionId);
5         }
6         sb.append(anchor);
7         sb.append(query);
8         return (sb.toString());

從上面URL的實現原理可知,URL重寫有一個缺點:在你的站點上不能有任何靜態的HTML頁面(至少靜態頁面中不能有任何鏈接到站點動態頁面的鏈接)。因此,每個頁面都必須使用servlet或JSP動態生成。即使所有的頁面都動態生成,如果用戶離開了會話並通過書籤或鏈接再次回來,會話的信息都會丟失,因爲存儲下來的鏈接含有錯誤的標識信息-該URL後面的SESSION ID已經過期了。

3、隱藏表單域
   這種方式藉助html表單中的hidden來實現,適用特定的一個流程,但是不適用於通常意義的會話跟蹤。

綜上所述,session實現會話跟蹤通常是cookie和url重寫,如果瀏覽器不禁止cookie的話,tomcat優先使用cookie實現。

服務器端實現原理

Session在服務器端具體是怎麼實現的呢?我們使用session的時候一般都是這麼使用的:

request.getSession()或者request.getSession(true)。

這個時候,服務器就檢查是不是已經存在對應的Session對象,見HttpRequestBase類
doGetSession(boolean create)方法:

 1  if ((session != null&& !session.isValid())
 2             session = null;
 3         if (session != null)
 4             return (session.getSession());
 5 
 6 
 7         // Return the requested session if it exists and is valid
 8         Manager manager = null;
 9         if (context != null)
10             manager = context.getManager();
11         if (manager == null)
12             return (null);      // Sessions are not supported
13         if (requestedSessionId != null) {
14             try {
15                 session = manager.findSession(requestedSessionId);
16             } catch (IOException e) {
17                 session = null;
18             }
19             if ((session != null&& !session.isValid())
20                 session = null;
21             if (session != null) {
22                 return (session.getSession());
23             }
24         }


requestSessionId從哪裏來呢?這個肯定是通過Session實現機制的cookie或URL重寫來設置的。見HttpProcessor類中的parseHeaders(SocketInputStream input):

 1 for (int i = 0; i < cookies.length; i++) {
 2                     if (cookies[i].getName().equals
 3                         (Globals.SESSION_COOKIE_NAME)) {
 4                         // Override anything requested in the URL
 5                         if (!request.isRequestedSessionIdFromCookie()) {
 6                             // Accept only the first session id cookie
 7                             request.setRequestedSessionId
 8                                 (cookies[i].getValue());
 9                             request.setRequestedSessionCookie(true);
10                             request.setRequestedSessionURL(false);
11                             
12                         }
13                     }
14 }

或者HttpOrocessor類中的parseRequest(SocketInputStream input, OutputStream output)

 1 // Parse any requested session ID out of the request URI
 2         int semicolon = uri.indexOf(match);  //match 是";jsessionid="字符串
 3         if (semicolon >= 0) {
 4             String rest = uri.substring(semicolon + match.length());
 5             int semicolon2 = rest.indexOf(';');
 6             if (semicolon2 >= 0) {
 7                 request.setRequestedSessionId(rest.substring(0, semicolon2));
 8                 rest = rest.substring(semicolon2);
 9             } else {
10                 request.setRequestedSessionId(rest);
11                 rest = "";
12             }
13             request.setRequestedSessionURL(true);
14             uri = uri.substring(0, semicolon) + rest;
15             if (debug >= 1)
16                 log(" Requested URL session id is " +
17                     ((HttpServletRequest) request.getRequest())
18                     .getRequestedSessionId());
19         } else {
20             request.setRequestedSessionId(null);
21             request.setRequestedSessionURL(false);
22         }
23 


裏面的manager.findSession(requestSessionId)用於查找此會話ID對應的session對象。Tomcat實現
是通過一個HashMap實現,見ManagerBase.java的findSession(String id):

1         if (id == null)
2             return (null);
3         synchronized (sessions) {
4             Session session = (Session) sessions.get(id);
5             return (session);
6         }

Session本身也是實現爲一個HashMap,因爲Session設計爲存放key-value鍵值對,Tomcat裏面Session實現類是StandardSession,裏面一個attributes屬性:

1     /**
2      * The collection of user data attributes associated with this Session.
3      */
4     private HashMap attributes = new HashMap();

所有會話信息的存取都是通過這個屬性來實現的。Session會話信息不會一直在服務器端保存,超過一定的時間期限就會被刪除,這個時間期限可以在web.xml中進行設置,不設置的話會有一個默認值,Tomcat的默認值是60。那麼服務器端是怎麼判斷會話過期的呢?原理服務器會啓動一個線程,一直查詢所有的Session對象,檢查不活動的時間是否超過設定值,如果超過就將其刪除。見StandardManager類,它實現了Runnable接口,裏面的run方法如下:

 1     /**
 2      * The background thread that checks for session timeouts and shutdown.
 3      */
 4     public void run() {
 5 
 6         // Loop until the termination semaphore is set
 7         while (!threadDone) {
 8             threadSleep();
 9             processExpires();
10         }
11 
12     }
13 
14     /**
15      * Invalidate all sessions that have expired.
16      */
17     private void processExpires() {
18 
19         long timeNow = System.currentTimeMillis();
20         Session sessions[] = findSessions();
21 
22         for (int i = 0; i < sessions.length; i++) {
23             StandardSession session = (StandardSession) sessions[i];
24             if (!session.isValid())
25                 continue;
26             int maxInactiveInterval = session.getMaxInactiveInterval();
27             if (maxInactiveInterval < 0)
28                 continue;
29             int timeIdle = // Truncate, do not round up
30                 (int) ((timeNow - session.getLastUsedTime()) / 1000L);
31             if (timeIdle >= maxInactiveInterval) {
32                 try {
33                     expiredSessions++;
34                     session.expire();
35                 } catch (Throwable t) {
36                     log(sm.getString("standardManager.expireException"), t);
37                 }
38             }
39         }
40 
41     }

Session信息在create,expire等事情的時候都會觸發相應的Listener事件,從而可以對session信息進行監控,這些Listener只需要繼承HttpSessionListener,並配置在web.xml文件中。如下是一個監控在線會話數的Listerner:

import java.util.HashSet;

import javax.servlet.ServletContext;

import javax.servlet.http.HttpSession;

import javax.servlet.http.HttpSessionEvent;

import javax.servlet.http.HttpSessionListener; 

public class MySessionListener implements HttpSessionListener {       

 
public void sessionCreated(HttpSessionEvent event) {             

 HttpSession session 
= event.getSession();             

ServletContext application 
= session.getServletContext();                           

 
// 在application範圍由一個HashSet集保存所有的session             

 HashSet sessions 
= (HashSet) application.getAttribute("sessions");             

if (sessions == null) {                    

sessions 
= new HashSet();                    

application.setAttribute(
"sessions", sessions);             

}                           

// 新創建的session均添加到HashSet集中             

 sessions.add(session);             

// 可以在別處從application範圍中取出sessions集合             

// 然後使用sessions.size()獲取當前活動的session數,即爲“在線人數”      

}       

public void sessionDestroyed(HttpSessionEvent event) {             

HttpSession session 
= event.getSession();             

 ServletContext application 
= session.getServletContext();             

 HashSet sessions 
= (HashSet) application.getAttribute("sessions");                           

 
// 銷燬的session均從HashSet集中移除             

sessions.remove(session);      

}

}


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