类 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
在会话期间更改存储类型不起作用。特别注意,默认情况下,FileStore
和 PStore
会话数据文件具有相同的名称。如果您的应用程序在不确保文件名不同且客户端仍有旧会话存在于 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
属性
此会话的 ID。
此会话的 ID。
公共类方法
为 request
创建新的 CGI::Session
对象。
request
是 CGI
类的实例(参见 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
(默认)、MemoryStore
和PStore
(来自 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
的会话数据。
# File lib/cgi/session.rb, line 350 def [](key) @data ||= @dbman.restore @data[key] end
Set
键 key
的会话数据。
# File lib/cgi/session.rb, line 356 def []=(key, val) @write_lock ||= true @data ||= @dbman.restore @data[key] = val end
在服务器上存储会话数据并关闭会话存储。对于某些会话存储类型,这是一个空操作。
# File lib/cgi/session.rb, line 370 def close @dbman.close @dbprot.clear end
从存储中删除会话。还会关闭存储。
请注意,会话过期后会话数据不会自动删除。
# File lib/cgi/session.rb, line 379 def delete @dbman.delete @dbprot.clear end
在服务器上存储会话数据。对于某些会话存储类型,这是一个空操作。
# File lib/cgi/session.rb, line 364 def update @dbman.update end
私有实例方法
创建新的会话 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