2010-02-06
Railsノート - セッションまわりを読む (1) - セッションの保存/復元のタイミング
Rails のセッションまわりについて調べる。知りたいのは (1) Cookie に含まれている情報からセッション(sessionメソッドの返値となるオブジェクト)を作るところと、その逆に、(2) セッションを Cookie としてレスポンスに含めるところだ。イメージとしては、
というような関係にあると思うのだが、その辺を確認したい。セッションストアは Cookie Store とする。例のごとく、参照する Rails のコードは 2.3.5
Cookie
Cookie としてクライアントに送信されるセッションは以下のようになる。(sandbox はアプリケーションの名前)
_sandbox_session=BAh7BjoPc2Vzc2lvbl9pZCIlN2UwZTA5MTQ2NWVjY2Q4NjYxMjBlMjI4YWEzZWMxZDg%3D--8bfad135e5bb257c95eb5438625d6d058b5b9636; path=/; domain=localhost; HttpOnly
この Cookie の中身に関しては以下のエントリが非常に参考になる。感謝。
今回確認したいのはクライアントから送られてきた上記のような Cookie からセッションが復元されるところと、その逆に、セッションが Cookie に保存されるところだ。
では、ActionController::Base の sessionメソッドの定義から、順番に見ていこう。
ActionController::Base
# actionpack/lib/action_controller/base.rb # Holds a hash of objects in the session. Accessed like <tt>session[:person]</tt> to get the object tied to the "person" # key. The session will hold any type of object as values, but the key should be a string or symbol. attr_internal :session
attr_internal については前に調べた。セッションは @_session というインスタンス変数に入る。で、@_session はコントローラの initialize に相当する assign_shortcuts において以下のように定義される。
def assign_shortcuts(request, response)
@_request, @_params = request, request.parameters
@_response = response
@_response.session = request.session
@_session = @_response.session
@template = @_response.template
@_headers = @_response.headers
end
@_session = @_response.session = request.session
つまり、request.session と @_response.session は同一のオブジェクトを指しており、コントローラの sessionメソッドはそのショートカットである。
ActionController::Request
module ActionController class Request < Rack::Request # snip def session @env['rack.session'] ||= {} end def session=(session) #:nodoc: @env['rack.session'] = session end def reset_session @env['rack.session.options'].delete(:id) @env['rack.session'] = {} end def session_options @env['rack.session.options'] ||= {} end def session_options=(options) @env['rack.session.options'] = options end
CookieJar と違い、セッションはただのハッシュのようだ。
ここにはクライアントから送られてきた Cookie からセッションストアの種類に応じたやり方でセッションを復元(deserialize)するコードはない。それを探す。actionpack/lib/action_controller.rb にて autoload されているモジュールの中にそれがあるはずだ。まず、コントローラは使用するセッションストアの種類を知る必要がある。
ActionController::SessionManagement
# actionpack/lib/action_controller/session_management.rb module ActionController #:nodoc: module SessionManagement #:nodoc: def self.included(base) base.class_eval do extend ClassMethods end end module ClassMethods # Set the session store to be used for keeping the session data between requests. # By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>), # but you can also specify one of the other included stores (<tt>:active_record_store</tt>, # <tt>:mem_cache_store</tt>, or your own custom class. def session_store=(store) if store == :active_record_store self.session_store = ActiveRecord::SessionStore else @@session_store = store.is_a?(Symbol) ? Session.const_get(store.to_s.camelize) : store end end # Returns the session store class currently used. def session_store if defined? @@session_store @@session_store else Session::CookieStore end end
session_store が返すのはセッションの保存/復元ロジックを持ったクラス。これを別のものに置き換えることでコントローラ側はコード修正なしに複数のセッションストアに対応できる。いわゆる Strategyパターン。
では、この session_store が返すクラスを使ってセッションの保存/復元を行っている箇所はどこだろう。
middlewares.rb
# actionpack/lib/action_controller/middlewares.rb use "Rack::Lock", :if => lambda { !ActionController::Base.allow_concurrency } use "ActionController::Failsafe" use lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options } use "ActionController::ParamsParser" use "Rack::MethodOverride" use "Rack::Head" use "ActionController::StringCoercion"
なんと、セッションの復元/保存はミドルウェアの層で処理されていた。ということは、セッションストアクラス(ここでは CookieStore)は Rack application object ということになる。call(env) にセッションの保存と復元のコードがあるはず。
ActionController::Session::CookieStore
# action_controller/session/cookie_store.rb module ActionController module Session class CookieStore # snip def call(env) env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env) env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup status, headers, body = @app.call(env) session_data = env[ENV_SESSION_KEY] options = env[ENV_SESSION_OPTIONS_KEY] if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after] session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?) session_data = marshal(session_data.to_hash) raise CookieOverflow if session_data.size > MAX cookie = Hash.new cookie[:value] = session_data unless options[:expire_after].nil? cookie[:expires] = Time.now + options[:expire_after] end cookie = build_cookie(@key, cookie.merge(options)) unless headers[HTTP_SET_COOKIE].blank? headers[HTTP_SET_COOKIE] << "\n#{cookie}" else headers[HTTP_SET_COOKIE] = cookie end end [status, headers, body] end
ここだな。
と非常にシンプルだ。
セッションの保存/復元(※実際には遅延ロード → 補足)がミドルウェアの層で行われることがわかったので、次は保存と復元のロジックを個別に見ていこうと思う。
補足:SessionHash は Cookie からのセッションのロード(復元)をハッシュの要素が実際に参照されるまで遅延するので、実際の復元タイミングはもう少し後になる。コントローラがセッションを一切使わなければ、当然復元もされない。上記のコードにおいて、セッションを Cookie に保存するにあたり、session_data.send(:loaded?) でセッションが実際にロードされたかどうかを確かめているのもそのため。
- 3 http://www.google.co.jp/search?source=ig&hl=ja&rlz=&=&q=wxruby+付箋&btnG=Google+検索&meta=lr=&aq=f&oq=
- 1 http://b.hatena.ne.jp/entry/d.hatena.ne.jp/h1mesuke/20080322/p1
- 1 http://b.hatena.ne.jp/entry/d.hatena.ne.jp/h1mesuke/20100205/p1
- 1 http://d.hatena.ne.jp/minghai/20090608/p1
- 1 http://kefunetter.com/archives/id/1120
- 1 http://search.auone.jp/?q=ScrolledWindow&sr=0201&ie=UTF-8&start=10&nb=q=&hl=ja&lr=&client=kddi-auone-pcsv&channel=main&ad=w2&gl=jp&output=xml_no_dtd&ie=UTF-8&oe=UTF-8&start=10&sa=N
- 1 http://search.yahoo.co.jp/search?p=cygwin+コピペ&search.x=1&fr=top_ga1_sa&tid=top_ga1_sa&ei=UTF-8&aq=&oq=
- 1 http://www.google.co.jp/reader/view/
- 1 http://www.google.co.jp/search?hl=ja&client=firefox-a&rls=org.mozilla:ja:official&q=スクリプト+ニコニコ+検索結果&btnG=検索&lr=&aq=f&oq=
- 1 http://www.google.co.jp/search?hl=ja&lr=lang_ja&client=firefox-a&rls=org.mozilla:ja:official&ei=C0ttS_G2OtGTkAX4wpTUBw&sa=X&oi=spell&resnum=0&ct=result&cd=1&ved=0CAYQBSgA&q=c++/cli+keydown&spell=1
