来自1.6.4

整体流程

整体流程定义在Rack::Session::Abstract::ID#call(或者说context)中,它所做的就是,先prepare_session,包装出一个session对象,塞到env[ENV_SESSION_KEY]中,然后call下层应用,最后commit_session,将下层应用塞到env[ENV_SESSION_KEY]的东西取出,再塞到headers里。至于具体的存储、编码、session_id策略,则由子类决定,如Rack::Session::Cookie

def call(env)
  context(env)
end

def context(env, app=@app)
  prepare_session(env)
  status, headers, body = app.call(env)
  commit_session(env, status, headers, body)
end

def prepare_session(env)
  session_was                  = env[ENV_SESSION_KEY]
  env[ENV_SESSION_KEY]         = session_class.new(self, env)
  env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
  env[ENV_SESSION_KEY].merge! session_was if session_was
end


ENV_SESSION_KEY就是rack.session

ENV_SESSION_KEY = 'rack.session'.freeze


而rack request取seesion也是从env中找rack.session

def session;         @env['rack.session'] ||= {}              end


commit_session具体是这样的

def commit_session(env, status, headers, body)
  session = env[ENV_SESSION_KEY]
  options = session.options

  if options[:drop] || options[:renew]
    session_id = destroy_session(env, session.id || generate_sid, options)
    return [status, headers, body] unless session_id
  end

  return [status, headers, body] unless commit_session?(env, session, options)

  session.send(:load!) unless loaded_session?(session)
  session_id ||= session.id
  session_data = session.to_hash.delete_if { |k,v| v.nil? }

  if not data = set_session(env, session_id, session_data, options)
    env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
  elsif options[:defer] and not options[:renew]
    env["rack.errors"].puts("Deferring cookie for #{session_id}") if $VERBOSE
  else
    cookie = Hash.new
    cookie[:value] = data
    cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
    cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
    set_cookie(env, headers, cookie.merge!(options))
  end

  [status, headers, body]
end


session_id的生成

session_id的初始生成在load!,它所调用的load_session会调用get_session,而get_session是由子类实现的,例如Cookie就这么做:

def get_session(env, sid)
  data = unpacked_cookie_data(env)
  data = persistent_session_id!(data)
  [data["session_id"], data]
end

def persistent_session_id!(data, sid=nil)
  data ||= {}
  data["session_id"] ||= sid || generate_sid
  data
end


当没有session_id这个键时,就generate_sid,它定义在Abstract::ID

def generate_sid(secure = @sid_secure)
  if secure
    secure.hex(@sid_length)
  else
    "%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1)
  end
rescue NotImplementedError
  generate_sid(false)
end


存放到cookie

在data = set_session这步,不同子类的实现是不同的,像memcache,数据保存在server,client只保留session_id,所以它的set_session只返回session_id

def set_session(env, session_id, new_session, options)
  expiry = options[:expire_after]
  expiry = expiry.nil? ? 0 : expiry + 1

  with_lock(env) do
    @pool.set session_id, new_session, expiry
    session_id
  end
end


而Cookie是完整地发给client的,它会给整个hash编码:

def set_session(env, session_id, session, options)
  session = session.merge("session_id" => session_id)
  session_data = coder.encode(session)

  if @secrets.first
    session_data << "--#{generate_hmac(session_data, @secrets.first)}"
  end

  if session_data.size > (4096 - @key.size)
    env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
    nil
  else
    session_data
  end
end


之后没啥问题的话,就是set_cookie了,实际调用的是Utils.set_cookie_header来修改headers

Utils.set_cookie_header!(headers, @key, cookie)


最后,call/context/commit_session返回[status, headers, body]