class CGI::Session

概述

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

生命周期

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

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

设置和检索会话数据。

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

当请求的会话处理完成后,应使用 close() 方法关闭会话。这会将该会话的状态存储到持久存储中。如果要将该会话的状态存储到持久存储中,而无需完成此请求的会话处理,请调用 update() 方法。

存储会话状态

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

CGI::Session::FileStore

将数据以纯文本形式存储在平面文件中。仅适用于字符串数据。这是默认的存储类型。

CGI::Session::MemoryStore

将数据存储在内存中的哈希中。数据仅在当前 Ruby 解释器实例运行时存在。

CGI::Session::PStore

以 Marshalled 格式存储数据。由 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 表中,该表用于将隐藏的输入元素添加到表单中。

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

cgi = CGI.new("html4")
class << cgi
    undef_method :fieldset
end
# File 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 cgi/session.rb, line 350
def [](key)
  @data ||= @dbman.restore
  @data[key]
end
[]=(key, val) 点击以切换源代码

设置键 key 的会话数据。

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

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

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

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

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

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

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

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

私有实例方法

create_new_id() 点击以切换源代码

创建新的会话 ID。

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

# File 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