2014年1月10日 星期五

Web 技術中的 Session 是什麼?

Standard
很久沒更新了,自從十二月底退伍之後,一直忙著將書完稿給出版社,沒空坐下來好好在 Blog 上寫點東西。沒辦法,在當兵時閒不下來,就答應了出版社的邀約開始寫 Node.js 的書,因此欠下了書的債。還好,如預期的寫完了,預計這一兩個月應該就會上市,還請各界多多捧場!

會寫這篇文章的原故,是因為在 Facebook 的社群上,有人問起了關於 Session 和 Cookie 相關性的問題。其實,社群的人都很好,有許多熱心的網友願意幫助別人。不過,為了幫忙回答問題,大家七嘴八舌的東說一句西說一句,甚至引起莫明的論戰,實在不是一個好的現象。況且,使用不同語言、有不同技術背景的人,都各自用自己的角度去描述和解說,讓原本就已經不懂的人更有如瞎子摸象,見不到全貌,搞更糊塗了。對發問的人來說,也不知該聽誰的好。

說來湊巧,筆者剛好在剛截稿的 Node.js 書籍中,對 Session 概念也有許多著墨。所以之前稍微在網路上查過,發現鮮少有人提出對 Session 技術的完整說明,而多半是討論 ASP、.Net、Java、PHP 等語言或框架中如何使用 Session,以及一些延伸的特性,以致很多人對 Session 的概念相當片面。因此這次的事件後,一些網友也建議我把在 Facebook 上回應的完整內容,在 Blog 發表記錄下來,讓更多人對 Session 有個比較完整且正確的認識。

本文目標

在 Web 的世界裡,Session 已經是被廣泛使用的一種技術,大多數的 Web 開發者,肯定都能運用自如。在現今的各類 Web 應用程式的開發框架和工具中,Session 也已經被包裝成容易使用的模式,所以本文也就不再多討論 Session 怎麼去使用,而是著重在 Session 的原理和實現上。要知道,無論是使用哪一種語言,這 Session 概念上都是通用的,沒有什麼不一樣。

Session 是什麼?

Session 之所以會存在,是因為 HTTP 為 stateless 的設計,Server 和 Client 不會一直保持連線狀態,也不會有雙方狀態的即時更新,所以,Server 並不知道 Client 的狀態(像是是否已經登入)。因此,後來的網站開發者,採用 Session 這樣的設計來解決這問題。有趣的是,Session 機制在最早的 CGI 年代,是完全要程式設計師徒手寫出來的,搞死了很多人,不像現在的網站開發者有現成的許多解決方案可以選擇,不必完全了解也可以使用。

Session 的原理

很多人在討論 Session 時,不免與 Cookie 牽扯上關係,反而不小心偏了重點,所以在了解 Session 時必須有個觀念,就算沒有 Cookie 的存在,Session 機制也可以正常運作。Cookie 在 Session 機制中,可以扮演許多角色,也可以對原始的 Session 機制做許多改良,但不用急著過早討論。從 Session 為什麼被需要來切入,自運作原理來了解,反而比較實際也易懂。最後再來討論 Cookie 在裡面所扮演的角色,才會比較清楚。

簡單來說,Session 的機制就像是你去飲料店下了單以後,得到號碼牌,然後你走開幾步,店員就忘了你是誰。所以,如果你想去取飲料,你就得靠這張號碼牌,去跟店員領,店員會跟據這號碼牌,認定你是顧客、是否點過餐、知道你點了什麼東西,然後可以接著給你屬於你的飲料。

理解 Session 的原理後,回到 HTTP 上就是一樣的。只是,在網頁技術中,有兩種方法讓 Client 取得號碼牌,一個是用 Cookie,另一個是直接輸出並嵌入頁面之中的方法(就是要你把號碼背起來)。

拿號碼牌去 Server 要資料,主要也分為兩種方法,Cookie 和運用標準的 Query string/POST body方法。(其實只要能把號碼傳到 Server 上,任何方法都行)

只不過,因為實作上的困難度考量,還有現今的 Browser 預設都支援 Cookie,所以在現有的網站框架中,都預設採用 Cookie 來發號碼牌和兌換資料。Cookie 的交換會在建立連線時,在背景自動完成,因此開發者不必考慮Client/Server的號碼牌交換問題。因為 Browser 會在建立連線後,第一時間就自動在背景把 Cookie 上傳到 Server,Server 也在回傳資料時,第一時間自動把 Cookie 回傳給 Client。

所以,除非是有必要(像是 Browser/Client 不支援 Cookie 的情況),才會保留另一種實作。

BTW, 飲料店拿號碼牌的例子應該很容易解釋清楚 Session 的原理。

Cookie-based Session

這邊要注意的是,有一種 Session 會讓很多很多人感到混亂,就是 cookie-based session。問題在於名稱上的誤導,你可能會想,既然幾乎所有的 Session 機制都會用到 cookie,是否都可以稱做 cookie-based session?

其實大多數人說的 cookie-based session ,指的是儲存資料方式的不同,不是指領號碼牌時,是否有用到 cookie。(不過如果早個十年,當時講的 cookie-based session,可能多半指的就是領號碼牌時,有沒有用到 cookie。摔碗!)

很多人在討論 Cookie 與 Session 的關係時,兩種用到 Cookie 的地方,時常混在一起講,這就不免要雞同鴨講筆戰一番了。

事實上,在最原始的 Session 設計,大多開發者都將資料存在 Server 上,也就是你點了什麼飲料,都是記錄在 Server 裡,可能是 Database、記憶體或是檔案,可以以任何一種形式儲存。然後,當你去領飲料時,店員會輸入你的號碼,用你的號碼得知你是否點過餐、點了什麼東西。

一般的小網站,這樣的解決方案並沒有什麼問題。但是對今天這種超大流量的網站服務來說,因為他們有無數台對外的 Server,有如無數個服務窗口,讓顧客總是隨機進入其中一個窗口來兌換飲料,所以後端怎麼存放和共享這個 session 資料,又要兼顧效能和方便維護,就變成是很大的問題。

因此 Cookie-based Session 就被提出為一個解決方案,把資料暫存放在 Cookie 中,讓 Client 自己負責保存。簡單來說,就是把你點什麼飲料,通通直接寫在號碼牌上。Server 就可以直接看你的號碼牌上寫了什麼,而不必花大量時間去後面建立大規模的 Server 來處理 Session 。

不過,這邊要特別提到,因為 cookie 有 4K 資料大小的限制,很多網站服務會選擇 cookie-based 和後端儲存並行的方案。

或許有人會問到 Cookie 是否有可能被篡改?這類安全問題,通常會使用加密手段來解決。一般來說,Cookie-based session 的 cookie 會被加密,只有 Server 才知道如何解開,Client 並沒有能力可以存取,只是得到一個看不懂的包裹,所以不會有安全性的問題(當然還是有機會被破)。

由於仍然存在風險,這也是其中一個原因,為什麼有些網站仍然會採用 cookie-based 和後端儲存 Session 並行的解決方案,或是會避免把敏感資料放在 Cookie-based Session 上。

什麼是 Session 傳值?

時常會聽到『使用 Session 傳值』這類說法,其實就是利用 Session 機制儲存資料,讓不同頁面之間可以互相傳遞資料。其原理通常是使用 Query String 或 POST body 等方法,把資料往 Server 傳之後,在 Server 端將 Client 上傳的資料存在 Session 之中。之後的連線或開啟其它頁面時,因為你拿的號碼牌是同一個,所以在不同的頁面之下,仍然可以讀到前一次所儲存在 Session 的狀態。

後記

當完兵回來了,腦袋還好還算靈活呀。:-)