Express之Session使用詳解
阿新 • • 發佈:2019-01-29
Session是什麼
Session一般譯作會話,牛津詞典對其的解釋是進行某活動連續的一段時間。從不同的層面看待session,它有著類似但不全然相同的含義。比如,在web應用的使用者看來,他開啟瀏覽器訪問一個電子商務網站,登入、並完成購物直到關閉瀏覽器,這是一個會話。而在web應用的開發者開來,使用者登入時我需要建立一個數據結構以儲存使用者的登入資訊,這個結構也叫做session。因此在談論session的時候要注意上下文環境。而本文談論的是一種基於HTTP協議的用以增強web應用能力的機制或者說一種方案,它不是單指某種特定的動態頁面技術,而這種能力就是保持狀態,也可以稱作保持會話。
為什麼需要session
談及session一般是在web應用的背景之下,我們知道web應用是基於HTTP協議的,而HTTP協議恰恰是一種無狀態協議。也就是說,使用者從A頁面跳轉到B頁面會重新發送一次HTTP請求,而服務端在返回響應的時候是無法獲知該使用者在請求B頁面之前做了什麼的。
對於HTTP的無狀態性的原因,相關RFC裡並沒有解釋,但聯絡到HTTP的歷史以及應用場景,我們可以推測出一些理由:
1. 設計HTTP最初的目的是為了提供一種釋出和接收HTML頁面的方法。那個時候沒有動態頁面技術,只有純粹的靜態HTML頁面,因此根本不需要協議能保持狀態;
2. 使用者在收到響應時,往往要花一些時間來閱讀頁面,因此如果保持客戶端和服務端之間的連線,那麼這個連線在大多數的時間裡都將是空閒的,這是一種資源的無端浪費。所以HTTP原始的設計是預設短連線,即客戶端和服務端完成一次請求和響應之後就斷開TCP連線,伺服器因此無法預知客戶端的下一個動作,它甚至都不知道這個使用者會不會再次訪問,因此讓HTTP協議來維護使用者的訪問狀態也全然沒有必要;
3. 將一部分複雜性轉嫁到以HTTP協議為基礎的技術之上可以使得HTTP在協議這個層面上顯得相對簡單,而這種簡單也賦予了HTTP更強的擴充套件能力。事實上,session技術從本質上來講也是對HTTP協議的一種擴充套件。
總而言之,HTTP的無狀態是由其歷史使命而決定的。但隨著網路技術的蓬勃發展,人們再也不滿足於死板乏味的靜態HTML,他們希望web應用能動起來,於是客戶端出現了指令碼和DOM技術,HTML裡增加了表單,而服務端出現了CGI等等動態技術。
而正是這種web動態化的需求,給HTTP協議提出了一個難題:一個無狀態的協議怎樣才能關聯兩次連續的請求呢?也就是說無狀態的協議怎樣才能滿足有狀態的需求呢?
此時有狀態是必然趨勢而協議的無狀態性也是木已成舟,因此我們需要一些方案來解決這個矛盾,來保持HTTP連線狀態,於是出現了cookie和session。
對於此部分內容,讀者或許會有一些疑問,筆者在此先談兩點:
1. 無狀態性和長連線
可能有人會問,現在被廣泛使用的HTTP1.1預設使用長連線,它還是無狀態的嗎?
連線方式和有無狀態是完全沒有關係的兩回事。因為狀態從某種意義上來講就是資料,而連線方式只是決定了資料的傳輸方式,而不能決定資料。長連線是隨著計算機效能的提高和網路環境的改善所採取的一種合理的效能上的優化,一般情況下,web伺服器會對長連線的數量進行限制,以免資源的過度消耗。
2. 無狀態性和session
Session是有狀態的,而HTTP協議是無狀態的,二者是否矛盾呢?
Session和HTTP協議屬於不同層面的事物,後者屬於ISO七層模型的最高層應用層,前者不屬於後者,前者是具體的動態頁面技術來實現的,但同時它又是基於後者的。在下文中筆者會分析Servlet /Jsp技術中的session機制,這會使你對此有更深刻的理解。
Cookie和Session
上面提到解決HTTP協議自身無狀態的方式有cookie和session。二者都能記錄狀態,前者是將狀態資料儲存在客戶端,後者則儲存在服務端。
首先看一下cookie的工作原理,這需要有基本的HTTP協議基礎。
cookie是在RFC2109(已廢棄,被RFC2965取代)裡初次被描述的,每個客戶端最多保持三百個cookie,每個域名下最多20個Cookie(實際上一般瀏覽器現在都比這個多,如Firefox是50個),而每個cookie的大小為最多4K,不過不同的瀏覽器都有各自的實現。對於cookie的使用,最重要的就是要控制cookie的大小,不要放入無用的資訊,也不要放入過多資訊。
無論使用何種服務端技術,只要傳送回的HTTP響應中包含如下形式的頭,則視為伺服器要求設定一個cookie:
Set-cookie:name=name;expires=date;path=path;domain=domain
支援cookie的瀏覽器都會對此作出反應,即建立cookie檔案並儲存(也可能是記憶體cookie),使用者以後在每次發出請求時,瀏覽器都要判斷當前所有的cookie中有沒有沒失效(根據expires屬性判斷)並且匹配了path屬性的cookie資訊,如果有的話,會以下面的形式加入到請求頭中發回服務端:
Cookie: name="zj"; Path="/linkage"
服務端的動態指令碼會對其進行分析,並做出相應的處理,當然也可以選擇直接忽略。
這裡牽扯到一個規範(或協議)與實現的問題,簡單來講就是規範規定了做成什麼樣子,那麼實現就必須依據規範來做,這樣才能互相相容,但是各個實現所使用的方式卻不受約束,也可以在實現了規範的基礎上超出規範,這就稱之為擴充套件了。無論哪種瀏覽器,只要想提供cookie的功能,那就必須依照相應的RFC規範來實現。所以這裡伺服器只管發Set-cookie頭域,這也是HTTP協議無狀態性的一種體現。
需要注意的是,出於安全性的考慮,cookie可以被瀏覽器禁用。
再看一下session的原理:
筆者沒有找到相關的RFC,因為session本就不是協議層面的事物。它的基本原理是服務端為每一個session維護一份會話資訊資料,而客戶端和服務端依靠一個全域性唯一的標識來訪問會話資訊資料。使用者訪問web應用時,服務端程式決定何時建立session,建立session可以概括為三個步驟:
1. 生成全域性唯一識別符號(sessionid);
2. 開闢資料儲存空間。一般會在記憶體中建立相應的資料結構,但這種情況下,系統一旦掉電,所有的會話資料就會丟失,如果是電子商務網站,這種事故會造成嚴重的後果。不過也可以寫到檔案裡甚至儲存在資料庫中,這樣雖然會增加I/O開銷,但session可以實現某種程度的持久化,而且更有利於session的共享;
3. 將session的全域性唯一標示符傳送給客戶端。
問題的關鍵就在服務端如何傳送這個session的唯一標識上。聯絡到HTTP協議,資料無非可以放到請求行、頭域或Body裡,基於此,一般來說會有兩種常用的方式:cookie和URL重寫。
1. Cookie
讀者應該想到了,對,服務端只要設定Set-cookie頭就可以將session的識別符號傳送到客戶端,而客戶端此後的每一次請求都會帶上這個識別符號,由於cookie可以設定失效時間,所以一般包含session資訊的cookie會設定失效時間為0,即瀏覽器程序有效時間。至於瀏覽器怎麼處理這個0,每個瀏覽器都有自己的方案,但差別都不會太大(一般體現在新建瀏覽器視窗的時候);
2. URL重寫
所謂URL重寫,顧名思義就是重寫URL。試想,在返回使用者請求的頁面之前,將頁面內所有的URL後面全部以get引數的方式加上session識別符號(或者加在path info部分等等),這樣使用者在收到響應之後,無論點選哪個連結或提交表單,都會在再帶上session的識別符號,從而就實現了會話的保持。讀者可能會覺得這種做法比較麻煩,確實是這樣,但是,如果客戶端禁用了cookie的話,URL重寫將會是首選。
到這裡,讀者應該明白我前面為什麼說session也算作是對HTTP的一種擴充套件了吧。如下兩幅圖是筆者在Firefox的Firebug外掛中的截圖,可以看到,當我第一次訪問index.jsp時,響應頭裡包含了Set-cookie頭,而請求頭中沒有。當我再次重新整理頁面時,圖二顯示在響應中不在有Set-cookie頭,而在請求頭中卻有了Cookie頭。注意一下Cookie的名字:jsessionid,顧名思義,就是session的識別符號,另外可以看到兩幅圖中的jsessionid的值是相同的,原因筆者就不再多解釋了。另外讀者可能在一些網站上見過在最後附加了一段形如jsessionid=xxx的URL,這就是採用URL重寫來實現的session。