HTTP協(xié)議( <
http://www.w3.org/Protocols/>
http://www.w3.org/Protocols/)是
"一次性單向"協(xié)議。
服務(wù)端不能主動(dòng)連接客戶端,只能被動(dòng)等待并答復(fù)客戶端請(qǐng)求??蛻舳诉B接服務(wù)端,發(fā)
出一個(gè)HTTP Request,服務(wù)端處理請(qǐng)求,并且返回一個(gè)HTTP Response給客戶端,本次
HTTP Request-Response Cycle結(jié)束。
我們看到,HTTP協(xié)議本身并不能支持服務(wù)端保存客戶端的狀態(tài)信息。于是,Web Server
中引入了session的概念,用來(lái)保存客戶端的狀態(tài)信息。
這里用一個(gè)形象的比喻來(lái)解釋session的工作方式。假設(shè)Web Server是一個(gè)商場(chǎng)的存包
處,HTTP Request是一個(gè)顧客,第一次來(lái)到存包處,管理員把顧客的物品存放在某一個(gè)
柜子里面(這個(gè)柜子就相當(dāng)于Session),然后把一個(gè)號(hào)碼牌交給這個(gè)顧客,作為取包
憑證(這個(gè)號(hào)碼牌就是Session ID)。顧客(HTTP Request)下一次來(lái)的時(shí)候,就要把
號(hào)碼牌(Session ID)交給存包處(Web Server)的管理員。管理員根據(jù)號(hào)碼牌
(Session ID)找到相應(yīng)的柜子(Session),根據(jù)顧客(HTTP Request)的請(qǐng)求,Web
Server可以取出、更換、添加柜子(Session)中的物品,Web Server也可以讓顧客
(HTTP Request)的號(hào)碼牌和號(hào)碼牌對(duì)應(yīng)的柜子(Session)失效。顧客(HTTP
Request)的忘性很大,管理員在顧客回去的時(shí)候(HTTP Response)都要重新提醒顧客
記住自己的號(hào)碼牌(Session ID)。這樣,顧客(HTTP Request)下次來(lái)的時(shí)候,就又
帶著號(hào)碼牌回來(lái)了。
Session ID實(shí)際上是在客戶端和服務(wù)端之間通過(guò)HTTP Request和HTTP Response傳來(lái)傳
去的。號(hào)碼牌(Session ID)必須包含在HTTP Request里面。關(guān)于HTTP Request的具體
格式,請(qǐng)參見(jiàn)HTTP協(xié)議( <
http://www.w3.org/Protocols/>
http://www.w3.org/Protocols/)。這里只做一個(gè)簡(jiǎn)單的介紹。
在Java Web Server(即Servlet/JSP Server)中,Session ID用jsessionid表示(請(qǐng)
參見(jiàn)Servlet規(guī)范)。
HTTP Request一般由3部分組成:
(1)Request Line
這一行由HTTP Method(如GET或POST)、URL、和HTTP版本號(hào)組成。
例如,GET
http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1
GET
http://www.google.com/search?q=Tomcat HTTP/1.1
POST
http://www.google.com/search HTTP/1.1
GET
http://www.somsite.com/menu.do;jsessionid=1001 HTTP/1.1
(2)Request Headers
這部分定義了一些重要的頭部信息,如,瀏覽器的種類,語(yǔ)言,類型。Request
Headers中還可以包括Cookie的定義。例如:
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)
Accept-Language: en-us
Cookie: jsessionid=1001
(3)Message Body
如果HTTP Method是GET,那么Message Body為空。
如果HTTP Method是POST,說(shuō)明這個(gè)HTTP Request是submit一個(gè)HTML Form的結(jié)果,
那么Message Body為HTML Form里面定義的Input屬性。例如,
user=guest
password=guest
jsessionid=1001
主意,如果把HTML Form元素的Method屬性改為GET。那么,Message Body為空,所有的
Input屬性都會(huì)加在URL的后面。你在瀏覽器的URL地址欄中會(huì)看到這些屬性,類似于
http://www.somesite/login.do?user=guest&password=guest&jsessionid=1001從理論上來(lái)說(shuō),這3個(gè)部分(Request URL,Cookie Header, Message Body)都可以用
來(lái)存放Session ID。由于Message Body方法必須需要一個(gè)包含Session ID的HTML
Form,所以這種方法不通用。
一般用來(lái)實(shí)現(xiàn)Session的方法有兩種:
(1)URL重寫。
Web Server在返回Response的時(shí)候,檢查頁(yè)面中所有的URL,包括所有的連接,和HTML
Form的Action屬性,在這些URL后面加上";jsessionid=XXX"。
下一次,用戶訪問(wèn)這個(gè)頁(yè)面中的URL。jsessionid就會(huì)傳回到Web Server。
(2)Cookie。
如果客戶端支持Cookie,Web Server在返回Response的時(shí)候,在Response的Header部
分,加入一個(gè)"set-cookie: jsessionid=XXXX"header屬性,把jsessionid放在
Cookie里傳到客戶端。
客戶端會(huì)把Cookie存放在本地文件里,下一次訪問(wèn)Web Server的時(shí)候,再把Cookie的信
息放到HTTP Request的"Cookie"header屬性里面,這樣jsessionid就隨著HTTP
Request返回給Web Server。
二、相關(guān)資料
前面是我自作聰明的一段個(gè)人淺見(jiàn),下面我來(lái)找點(diǎn)權(quán)威資料支持。
<
http://www.w3.org/Protocols/>
http://www.w3.org/Protocols/Use of HTTP State Management (
<
ftp://ftp.rfc-editor.org/in-notes/rfc2964.txt> RFC 2964).
<
ftp://ftp.rfc-editor.org/in-notes/rfc2964.txt>
ftp://ftp.rfc-editor.org/in-notes/rfc2964.txt這個(gè)文件是定義"HTTP State Management"擴(kuò)展協(xié)議部分。里面有這么一段,
It's important to realize that similar capabilities may also be????achieved
using the "bare" HTTP protocol, and/or dynamically-generated
HTML, without the State Management extensions.??For example, state
information can be transmitted from the service to the user by????embedding
a session identifier in one or more URLs which appear in HTTP redirects, or
dynamically generated HTML; and the state information may be returned from
the user to the service when such URLs appear in a GET or POST request.
HTML forms can also be used to pass state information from the service to
the user and back, without the user being aware of this happening.
這段話的意思是說(shuō),不用這個(gè) "HTTP State Management"擴(kuò)展協(xié)議部分,我們也可以
用"純粹"的HTTP協(xié)議實(shí)現(xiàn)Session -- 比如URL重寫,HTML Form等。
這里面沒(méi)有提到Cookie。因?yàn)?HTTP State Management" 擴(kuò)展協(xié)議部分本身包括了關(guān)
于Cookie的內(nèi)容。這一點(diǎn)可以通過(guò)
HTTP State Management Mechanism (RFC 2965
<
ftp://ftp.rfc-editor.org/in-notes/rfc2965.txt> ),
<
ftp://ftp.rfc-editor.org/in-notes/rfc2965.txt>
ftp://ftp.rfc-editor.org/in-notes/rfc2965.txt看出來(lái)。
STATE AND SESSIONS
This document describes a way to create stateful sessions with HTTP
requests and responses.??Currently, HTTP servers respond to each????client
request without relating that request to previous or????subsequent requests;
the state management mechanism allows clients????and servers that wish to
exchange state information to place HTTP????requests and responses within a
larger context, which we term a????"session".??This context might be used to
create, for example, a????"shopping cart", in which user selections can be
aggregated before????purchase, or a magazine browsing system, in which a
user's previous????reading affects which offerings are presented.
Neither clients nor servers are required to support cookies.??A????server
MAY refuse to provide content to a client that does not return????the
cookies it sends.
后面還給出了例子(其中的漢語(yǔ)部分是我加的)。
4.1??Example 1
Most detail of request and response headers has been omitted.??Assume
the user agent has no stored cookies.
???? 1. User Agent -> Server
?????? POST /acme/login HTTP/1.1
?????? [form data]
?????? User identifies self via a form.
???? 2. Server -> User Agent
?????? HTTP/1.1 200 OK
?????? Set-Cookie2: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"
?????? Cookie reflects user's identity.
(這里面的 Customer="WILE_E_COYOTE" 應(yīng)該就是從Form Data里面來(lái)的,這時(shí)候又傳
回給了客戶端)
???? 3. User Agent -> Server
?????? POST /acme/pickitem HTTP/1.1
?????? Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"
?????? [form data]
?????? User selects an item for "shopping basket".
???? 4. Server -> User Agent
?????? HTTP/1.1 200 OK
?????? Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1";
?????????????? Path="/acme"
?????? Shopping basket contains an item.
(這個(gè)火箭發(fā)射器顯然也是從Form Data來(lái)的。但為什么
Part_Number="Rocket_Launcher_0001"也需要傳回給客戶端?
Customer="WILE_E_COYOTE"; 應(yīng)該是在傳統(tǒng)的"Set-Cookie"里面?zhèn)骰亟o客戶端的。"
Set-Cookie2" 的作用應(yīng)該是向Cookie里面添加?xùn)|西。)
???? 5. User Agent -> Server
?????? POST /acme/shipping HTTP/1.1
?????? Cookie: $Version="1";
?????????????? Customer="WILE_E_COYOTE"; $Path="/acme";
?????????????? Part_Number="Rocket_Launcher_0001"; $Path="/acme"
?????? [form data]
?????? User selects shipping method from form.
(客戶傳給服務(wù)器的Cookie里面包括了Customer和Part_Number)
???? 6. Server -> User Agent
?????? HTTP/1.1 200 OK
?????? Set-Cookie2: Shipping="FedEx"; Version="1"; Path="/acme"
?????? New cookie reflects shipping method.
???? 7. User Agent -> Server
?????? POST /acme/process HTTP/1.1
?????? Cookie: $Version="1";
?????????????? Customer="WILE_E_COYOTE"; $Path="/acme";
?????????????? Part_Number="Rocket_Launcher_0001"; $Path="/acme";
?????????????? Shipping="FedEx"; $Path="/acme"
?????? [form data]
?????? User chooses to process order.
???? 8. Server -> User Agent
?????? HTTP/1.1 200 OK
?????? Transaction is complete.
??The user agent makes a series of requests on the origin server, after
each of which it receives a new cookie.??All the cookies have the same Path
attribute and (default) domain.??Because the request-URIs all path-match
/acme, the Path attribute of each cookie, each request contains all the
cookies received so far.
(看到這里,我才大致明白,原來(lái)那個(gè)$Path="/acme" 大致起著 JSessionID的作用)
三、Tomcat5的HTTP Session實(shí)現(xiàn)
下面我們來(lái)看Tomcat5的源代碼如何支持HTTP 1.1 Session。
我們可以用jsessionid, Set-Cookie等關(guān)鍵字搜索Tomcat5源代碼。
首先,我們來(lái)看常量定義:
org.apache.catalina.Globals
?? /**
????* The name of the cookie used to pass the session identifier back
????* and forth with the client.
????*/
?? public static final String SESSION_COOKIE_NAME = "JSESSIONID";
?? /**
????* The name of the path parameter used to pass the session identifier
????* back and forth with the client.
????*/
public static final String SESSION_PARAMETER_NAME = "jsessionid";
Cookie里面用大寫的JSESSIONID,URL后綴用的是小寫的jsessionid。
Session的具體實(shí)現(xiàn)類是org.apache.catalina.session.StandardSession。一個(gè)Tomcat
Server的所有Session都由一個(gè)Manager(擁有一個(gè)Context)統(tǒng)一管理。我估計(jì)有一個(gè)
Session Listener 專門管理Cluster之間的Session數(shù)據(jù)復(fù)制,具體的我沒(méi)有追查下
去。
另外幾個(gè)重要的相關(guān)類是org.apache.coyote.tomcat5包下面的CoyoteRequest ,
CoyoteResponse, CoyoteAdapter三個(gè)類。
org.apache.coyote.tomcat5.CoyoteResponse類的toEncoded()方法支持URL重寫。
String toEncoded(String url, String sessionId) {
...
?????? StringBuffer sb = new StringBuffer(path);
?????? if( sb.length() > 0 ) { // jsessionid can't be first.
?????????? sb.append(";jsessionid=");
?????????? sb.append(sessionId);
?????? }
?????? sb.append(anchor);
?????? sb.append(query);
?????? return (sb.toString());
}
我們來(lái)看org.apache.coyote.tomcat5.CoyoteRequest的兩個(gè)方法
configureSessionCookie()
doGetSession()用Cookie支持jsessionid.
?? /**
????* Configures the given JSESSIONID cookie.
????*
????* @param cookie The JSESSIONID cookie to be configured
????*/
?? protected void configureSessionCookie(Cookie cookie) {
??????...
?? }
?? HttpSession doGetSession(boolean create){
???? ...
?????? // Creating a new session cookie based on that session
?????? if ((session != null) && (getContext() != null)
??????????????&& getContext().getCookies()) {
?????????? Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,
??????????????????????????????????????session.getId());
?????????? configureSessionCookie(cookie);
?????????? ((HttpServletResponse) response).addCookie(cookie);
?????? }
???? ...
?? }
四、More
HTTP Session的協(xié)議、規(guī)范、實(shí)現(xiàn)大概就是這些。
另外,在frameset中使用Session的情況有些復(fù)雜,不同環(huán)境表現(xiàn)可能不同。
其余相關(guān)的概念有"單點(diǎn)登錄"(Sing Sign On - SSO), Domain, Cluster, Proxy,
Cache等。