类 CGI::Session

概述

此文件提供 CGI::Session 类,该类为 CGI 脚本提供会话支持。会话是由 HTTP 请求和响应序列链接在一起并与单个客户端关联的。与会话相关联的信息在请求之间存储在服务器上。会话 ID 在每次请求和响应中都在客户端和服务器之间传递,对用户来说是透明的。这会向原本无状态的 HTTP 请求/响应协议添加状态信息。

生命周期

CGI 对象创建 CGI::Session 实例。默认情况下,如果当前不存在会话,此 CGI::Session 实例将启动新会话,如果存在,则继续此客户端的当前会话。new_session 选项可用于始终或从不创建新会话。有关更多详细信息,请参阅 new()。

delete() 从会话存储中删除会话。但它不会从客户端中删除会话 ID。如果客户端使用相同的 ID 发出另一个请求,则效果将是使用旧会话的 ID 启动新会话。

设置和检索会话数据。

Session 类将数据与会话关联起来,作为键值对。可以通过使用“[]”索引 Session 实例来设置和检索此数据,这与哈希非常相似(尽管不支持其他哈希方法)。

针对某个请求完成会话处理后,应使用 close() 方法关闭会话。这会将会话状态存储到持久性存储中。如果您想将会话状态存储到持久性存储中,而不完成此请求的会话处理,请调用 update() 方法。

存储会话状态

调用者可以使用 database_manager 选项为 CGI::Session::new 指定用于会话数据的存储形式。以下存储类作为标准库的一部分提供

CGI::Session::FileStore

将数据作为纯文本存储在平面文件中。仅适用于 String 数据。这是默认存储类型。

CGI::Session::MemoryStore

将数据存储在内存哈希中。数据仅持续到当前 Ruby 解释器实例持续的时间。

CGI::Session::PStore

以编组格式存储数据。由 cgi/session/pstore.rb 提供。支持任何类型的数据,并提供文件锁定和事务支持。

还可以通过定义具有以下方法的类来创建自定义存储类型

new(session, options)
restore  # returns hash of session data.
update
close
delete

在会话期间更改存储类型不起作用。特别注意,默认情况下,FileStorePStore 会话数据文件具有相同的名称。如果您的应用程序在不确保文件名不同且客户端仍有旧会话存在于 cookie 中的情况下从一个会话切换到另一个会话,那么会出现严重问题!

维护会话 ID。

大多数会话状态都维护在服务器上。但是,必须在客户端和服务器之间来回传递会话 ID,以维护对该会话状态的引用。

最简单的方法是通过 cookie。如果客户端启用了 cookie,则 CGI::Session 类会通过 cookie 透明地支持会话 ID 通信。

如果客户端禁用了 cookie,则必须将会话 ID 作为客户端发送给服务器的所有请求的参数包含在内。CGI::Session 类与 CGI 类结合使用,会透明地将会话 ID 作为隐藏的输入字段添加到使用 CGI#form() HTML 生成方法生成的所有表单。不提供对其他机制(例如 URL 重写)的内置支持。调用者负责从 session_id 属性中提取会话 ID,并手动将其编码到 URL 中,并将其作为隐藏的输入添加到通过其他机制创建的 HTML 表单中。此外,不会自动处理会话过期。

使用示例

设置用户名

require 'cgi'
require 'cgi/session'
require 'cgi/session/pstore'     # provides CGI::Session::PStore

cgi = CGI.new("html4")

session = CGI::Session.new(cgi,
    'database_manager' => CGI::Session::PStore,  # use PStore
    'session_key' => '_rb_sess_id',              # custom session key
    'session_expires' => Time.now + 30 * 60,     # 30 minute timeout
    'prefix' => 'pstore_sid_')                   # PStore option
if cgi.has_key?('user_name') and cgi['user_name'] != ''
    # coerce to String: cgi[] returns the
    # string-like CGI::QueryExtension::Value
    session['user_name'] = cgi['user_name'].to_s
elsif !session['user_name']
    session['user_name'] = "guest"
end
session.close

安全地创建新会话

require 'cgi'
require 'cgi/session'

cgi = CGI.new("html4")

# We make sure to delete an old session if one exists,
# not just to free resources, but to prevent the session
# from being maliciously hijacked later on.
begin
    session = CGI::Session.new(cgi, 'new_session' => false)
    session.delete
rescue ArgumentError  # if no old session
end
session = CGI::Session.new(cgi, 'new_session' => true)
session.close

属性

new_session[R]

此会话的 ID。

session_id[R]

此会话的 ID。

公共类方法

new(request, option={}) 单击以切换源

request 创建新的 CGI::Session 对象。

requestCGI 类的实例(参见 cgi.rb)。option 是用于初始化此 CGI::Session 实例的选项哈希。识别以下选项

session_key

用于会话 ID 的参数名称。默认为“_session_id”。

session_id

要使用的会话 ID。如果未提供,则从请求的 session_key 参数中检索,或为新会话自动生成。

new_session

如果为 true,则强制创建新会话。如果未设置,则仅在当前不存在会话时才创建新会话。如果为 false,则永远不会创建新会话,并且如果当前不存在会话且未设置 session_id 选项,则会引发 ArgumentError

database_manager

提供会话状态持久性存储设施的类的名称。内置支持为 FileStore(默认)、MemoryStorePStore(来自 cgi/session/pstore.rb)。有关更多详细信息,请参见这些类的文档。

还识别以下选项,但仅当会话 ID 存储在 cookie 中时才适用。

session_expires

当前会话到期的时间,作为 Time 对象。如果未设置,则会话将在用户关闭浏览器时终止。

session_domain

此会话有效的域名。如果未设置,则默认为服务器的主机名。

session_secure

如果为 true,则此会话仅适用于 HTTPS。

session_path

此会话适用的路径。默认为 CGI 脚本的目录。

option 也传递给会话存储类初始化器;有关它们支持的选项,请参见每个会话存储类的文档。

检索或创建的会话会自动作为 cookie 添加到 request,也会添加到其 output_hidden 表中,该表用于向表单添加隐藏的输入元素。

警告在 HTML 4 生成中,output_hidden 字段被 <fieldset> 标记包围,这在许多浏览器中并非不可见;你可能希望使用类似于以下的代码禁用字段集(参见 blade.ruby-lang.org/ruby-list/37805

cgi = CGI.new("html4")
class << cgi
    undef_method :fieldset
end
# File lib/cgi/session.rb, line 289
def initialize(request, option={})
  @new_session = false
  session_key = option['session_key'] || '_session_id'
  session_id = option['session_id']
  unless session_id
    if option['new_session']
      session_id = create_new_id
      @new_session = true
    end
  end
  unless session_id
    if request.key?(session_key)
      session_id = request[session_key]
      session_id = session_id.read if session_id.respond_to?(:read)
    end
    unless session_id
      session_id, = request.cookies[session_key]
    end
    unless session_id
      unless option.fetch('new_session', true)
        raise ArgumentError, "session_key `%s' should be supplied"%session_key
      end
      session_id = create_new_id
      @new_session = true
    end
  end
  @session_id = session_id
  dbman = option['database_manager'] || FileStore
  begin
    @dbman = dbman::new(self, option)
  rescue NoSession
    unless option.fetch('new_session', true)
      raise ArgumentError, "invalid session_id `%s'"%session_id
    end
    session_id = @session_id = create_new_id unless session_id
    @new_session=true
    retry
  end
  request.instance_eval do
    @output_hidden = {session_key => session_id} unless option['no_hidden']
    @output_cookies =  [
      Cookie::new("name" => session_key,
      "value" => session_id,
      "expires" => option['session_expires'],
      "domain" => option['session_domain'],
      "secure" => option['session_secure'],
      "path" =>
      if option['session_path']
        option['session_path']
      elsif ENV["SCRIPT_NAME"]
        File::dirname(ENV["SCRIPT_NAME"])
      else
      ""
      end)
    ] unless option['no_cookies']
  end
  @dbprot = [@dbman]
  ObjectSpace::define_finalizer(self, Session::callback(@dbprot))
end

公共实例方法

[](key) 单击以切换源

检索键 key 的会话数据。

# File lib/cgi/session.rb, line 350
def [](key)
  @data ||= @dbman.restore
  @data[key]
end
[]=(key, val) 单击以切换源

Setkey 的会话数据。

# File lib/cgi/session.rb, line 356
def []=(key, val)
  @write_lock ||= true
  @data ||= @dbman.restore
  @data[key] = val
end
close() 单击以切换源

在服务器上存储会话数据并关闭会话存储。对于某些会话存储类型,这是一个空操作。

# File lib/cgi/session.rb, line 370
def close
  @dbman.close
  @dbprot.clear
end
delete() 单击以切换源

从存储中删除会话。还会关闭存储。

请注意,会话过期后会话数据不会自动删除。

# File lib/cgi/session.rb, line 379
def delete
  @dbman.delete
  @dbprot.clear
end
update() 单击以切换源

在服务器上存储会话数据。对于某些会话存储类型,这是一个空操作。

# File lib/cgi/session.rb, line 364
def update
  @dbman.update
end

私有实例方法

create_new_id() 单击以切换源

创建新的会话 ID。

如果可能,会话 ID 是 SecureRandom 的安全随机数,否则是基于时间、随机数和常量字符串的 SHA512 哈希。此例程在内部用于自动生成的会话 ID。

# File lib/cgi/session.rb, line 171
def create_new_id
  require 'securerandom'
  begin
    # by OpenSSL, or system provided entropy pool
    session_id = SecureRandom.hex(16)
  rescue NotImplementedError
    # never happens on modern systems
    require 'digest'
    d = Digest('SHA512').new
    now = Time::now
    d.update(now.to_s)
    d.update(String(now.usec))
    d.update(String(rand(0)))
    d.update(String($$))
    d.update('foobar')
    session_id = d.hexdigest[0, 32]
  end
  session_id
end