Session 的概念

一個網站利用 session 來記得它跟某個使用者的互動。 例如, 阿福登入 Gmail, Gamil 就會建立一個它跟阿福之間的 session。 每當阿福按他的收信籃或垃圾桶連結時, session 就會告訴 Gmail 阿福要什麼樣的資訊, 然後 Gamil 就會把此資訊傳回給阿福。 如阿福登出 Gmail 或關閉瀏覽器, session 就會消失, Gmail 將不知道阿福是誰, 也不會把阿福的資訊傳出去。 Session 只有在需要知道使用者是誰時才會建立。 如果阿福只要用 Google 的收尋功能, 不用 Gmail, Google 就不需要知道他是誰, 所以也不用建 session。

在 Rails 裡,session 是怎麼建立的呢? 答案就是 cookie:當你登入一個網站時, 網站的伺服器就即時產生一個 id 給你, 當作 cookie 放在你的瀏覽器裡。 這 id 其實只是一個長度 32 位數的英文字母字串。 每次你要新的資訊時, 你的瀏覽器會把那串字給伺服器, 這樣伺服器就知道你是誰了。

在我們的示範裡頭, 有時會把一些變數存在 session 裡,方便紀錄一些有的沒有的。 這又是怎麼辦到的呢? 每個人登入網站之後, Rails 會利用剛剛說的 32 位數的字串建一個文字檔, 存放在你程式目錄下的 tmp\sessions 檔案夾。 你如果有執行過用到 session 的程式, 去看看那個檔案夾你就會看到一堆檔案命名類似 ruby_sess.bb97ac9253f7ce87。 打開其中一個, 就會看到一些資料。 這些資料就是當初那個使用者的 session 物件被伺服器處理後存放的資料。 一個使用者會產生一個 session, 一個 session就會產生一個 session 檔案。 跟伺服器溝通時, 伺服器會用你給它的字串找出你的 session 檔案, 然後把這檔案裡的值灌回你的 session。 Session 裡的值依照你的需求被更改後, 又會被存回 tmp\sessions 檔案夾。 在 tmp\sessions 檔案夾裡看到的就是不同的使用者產生的 session 檔案, 如果沒定時把這些檔案清掉的話, 檔案夾會爆掉。 清除的方法等一下再談。

在程式裡要把變數存入 session 很簡單,直接用 session 陣列就可以:

session[:current_phone] = user.phone

要取回存入的值的話也是一樣:

The phone number currently on file is <%= session[:current_phone]%>

儲存 model 在 session 裡頭

除了簡單變數的儲存外你也可以存整個 model 物件 (object)。 如要存物件,記得要先在 controller 裡頭宣告,這樣 Rails 才知道物件的 class 定義。 宣告的方式如下:

class CustomerController < ApplicationController
  model :paycheck
...

這個範例裡,我們的 controller 是 Customer, model 是 paycheck。

如果你的程式裡有好幾個 controller, 而且有兩個以上的 controller 會用到 session 裡存的物件,那麼你就應該在 application_controller.rb 裡宣告這物件。 存物件的壞處是,如果改版本的話,新的物件裡的值就跟舊的不相容了,所以要記得把 tmp\sessions 裡的檔案刪掉。

Session 的儲存方法

Session 資料其實有好幾種儲存的方式。上一小節 “Session 的概念” 裡提到的方法是 PStore。 其他方法有的是存在資料庫,有的是記憶體。 各有好有壞,以下從最簡單的到最複雜的大致介紹:

MemoryStore 跟 FileStore

MemoryStore 把 session 資料記在程式的 memory 裡面。 同時如果有很多人登入的話,記憶體將不夠用,所以這個方法不實用。 FileStore 把資料記在檔案裡,但資料的型態只能存 string, 不能存 object, 所以對 Rails 程式不實用。

PStore

PStore 是系統的預設值。 什麼都不用設系統就自動會用這個方式了。 每一個 session 的檔案就存在 tmp\sessions 裡頭。這個的壞處是如果同時有很多個 session 的話 (一萬個人以上同時登入你的系統)整個程式的效能就會變差。 效能變差的原因是 tmp\sessions 裡的檔案會越來越多,有些系統遇到檔案夾裡有好幾千萬的檔案時就會慢下來。 用 PStore 的話記得要定時把 tmp\sessions 裡的舊檔案刪掉,不然系統有一天可能因為一個檔案夾裡太多檔案而拒絕建立新的 session。 檔案要怎麼刪?總不能人工去刪吧?如果你是用 Linux/unix 的話,可以增加一個 cron 工作檔來刪除一個禮拜內沒碰過的 session 檔。 雖然有這個麻煩,但如果你的網站只需要一台伺服器, 而且同時不會有一萬個人來登入,那用 PStore 最方便了。

ActiveRecordStore

比 PStore 更進一層的就是 ActiveRecordStore。如果只有少數的人登入的話,它的效能不會比 PStore 好,但是如果越來越多人同時用,它的效能不會變得差,反而比 Pstore 好。會用到 ActiveRecordStore,就表示你的程式有分散到好幾台伺服器,或是同時有一萬人以上來登入。

ActiveRecordStore 會把 session 的資料存在資料庫裡,所以你要在資料庫裡建一個叫做 “sessions” 的 table,裡面要有四個欄位: id, sessid, data, 跟 updated_at. id 的屬性是 integer。 sessid 就是我們常說的 session 的 id, 欄位的長度至少要 32 個字母。 要記得把這個欄位設為 index,這樣資料庫找資料時才會快。 data 就是放 session 資料,屬性為 text 才夠用。 updated_at 這欄位不是一定要的,但有了它 Active Record 會自動把上次變動過的時間填進去。此欄位的 datatype 是 timestamp。

DrbStore

DrbStore 又比 ActiveRecordStore 更好。 如果只是幾個 session 在跑的話,DrbStore 的效能跟 PStore 一樣好。 越來越多人登入時效能也不會下降。 DrbStore 可以透過區域網路來分享物件 (objects)。 它的 session 資料是由 DRb 伺服器來管的。 你的程式如果分散到不同的伺服器上,每個程式的 instance 都能跟同一個 DrbStore 溝通。 Rails 有包含一個簡單的 DRb 伺服器。 列如我的就在

/usr/local/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/session

依照你灌 Ruby 的地方,你的路徑跟版本可能跟以上有點不同。

MemCacheStore

MemCacheStore 是最厲害也是最難的一個 session 管理方法。 有些大的網站,像 Wikipedia, LiveJournal 等等,都是用 MemCacheStore。 LiveJournal 的網站一天要伺服兩千萬客製化的網頁,並有一百萬使用者。 除非你的網站跟這些網站有的比,否則最複雜的用到 DrbStore 就可以了。 更多資訊請到 www.danga.com/memcached/ 。

變更 session 的儲存方法

看了這幾個不同的方法後,如想變更要怎麼做呢? 開啟程式裡的 config/environment.rb。 在檔案的最底部你會看到一行字

# Include your application configuration below

在這行字下,貼上

ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] = CGI::Session:: ActiveRecordStore

這行就會把儲存方法改為 ActiveRecordStore 了。 如要 MemCacheStore, 把 ActiveRecordStore 改為 MemCacheStore, 依此類推。 如要 PStore, 把整行刪掉,因為預設值是 PStore.

不想用 session

如果不要用 session,打開 config/environment.rb 然後變更或加入下面這行:

ActionController::CgiRequest::DEFAULT_SESSION_OPTION = false

要注意的是,沒有 session 的話,就不能用使用者登錄等等功能喔。

session 管理

除了在 PStore 裡提到的定時刪除 session 檔案之外,程式裡面在使用者登出時多加個 reset_session, 這樣伺服器就會把剛剛用過的 session 檔案刪掉,檔案夾也比較不會爆掉:

class MyOwnStoreController < ApplicationController

  ...

  def logout
    reset_session
  end

end

一台伺服器多個 Rails 程式

伺服器上如果有跑兩個或兩個以上的 rails 程式的話,你要叫每一個程式把他們用的 cookie 前面加上程式的名稱,這樣程式才不會使用互相的 cookie,造成一個程式登入後,在另一個程式沒有登入就可以進去了。 打開 config/environment.rb 然後變更或加入下面這行。

ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_key] = ‘my_mouse_diary’

把 my_mouse_diary 改成你要加的名字。

限制只有 https 能用 session

程式如果要說只有透過 https 才可啟動 session 的話,打開 config/environment.rb 然後變更或加入下面這行

ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_secure] = true