“中国要复兴、富强,必须在开源软件领域起到主导作用,为了国家安全和人类发展,责无旁贷,我们须为此而奋斗”——By:云客
10. Session进阶
在本系列之前写过《Session系统》,但那部分仅仅讲到了drupal8会话的基础:Symfony的Session组件
至于drupal怎么去使用这个基础就是本主题的内容,本主题是延续篇,将讲述drupal8的全部Session知识
请先看上篇,再继续
关于drupal8的Session代码除了Symfony的Session组件外,全部都放在了:\core\lib\Drupal\Core\Session
在这个文件夹里不仅仅存放了Session的核心代码,还存放了和用户账户相关的一些代码,因为登陆多和Session有关。
drupal8系统的Session子系统是何时初始化并注入到请求对象中的呢?
这个工作是在Drupal\Core\StackMiddleware\Session里完成的,也就是http堆栈中的http_middleware.session层
详情请见本系列前面的《HttpKernel堆栈》
一切从这里开始:$this->container->get("session"); (其实源代码为:$session = $this->container->get($this->sessionServiceName);)
我们来看一看session服务的定义:
使用了类:Symfony\Component\HttpFoundation\Session\Session 有三个参数:
服务:session_manager
属性包:Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag
闪存包:Symfony\Component\HttpFoundation\Session\Flash\FlashBag
(还不知道如何看运行时容器的容器定义数据吗?请见本系列前文的服务容器主题末尾部分)
可见基本使用了Symfony的Session组件,但不同的是使用了不同的SessionStorage 储存管理器
Session处理的核心就是这个储存管理器,它就是本主题的重点,在drupal8的源代码中作者注释到使用SessionStorage
这个名字不是太准确,从它的功能来看,叫做SessionManager更为合适
因此在drupal8中实现SessionStorageInterface接口的SessionStorage被命名为了SessionManager
SessionManager继承自Symfony的NativeSessionStorage,
所以请记住SessionManager就是Symfony概念中的SessionStorage,下文将称为会话管理器。
在会话系统中有三个部分:数据处理器(储存数据包)、会话管理器(负责大部分功能)、会话处理器(负责会话的底层读写)
SessionManager怎么存储数据就看它里面注入的SessionHandler,这个会话处理器就是Drupal\Core\Session\SessionHandler
这个SessionHandler并不直接传递给SessionManager
而是经过层层包装,每一层包装提供一个额外功能,包装情况如下:
SessionHandler:最里层的原始的会话处理器
Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler:第一层包装,提供性能上的优化,如果数据未变避免再次写操作
Drupal\Core\Session\WriteSafeSessionHandler:第二次包装,提供控制是否可写的能力
如果在模块中使用: \Drupal::service('session_handler.write_safe')->setSessionWritable(false);将导致会话无法保存
最后才把经过两次包装的会话处理器传给SessionManager,在服务定义yaml文件里面看不出这种包装关系
其实这个包装关系是在容器编译里面体现的,编译器位于:
Drupal\Core\DependencyInjection\Compiler\StackedSessionHandlerPass
我们看一看SessionHandler,它决定着drupal会话数据怎么储存读写,
看代码可以知道drupal8的Session数据并不是像原生php那样存在文件系统中,
而是放在了数据库里面,数据表名为:sessions,同时储存的还有sessions对应的用户id、客户端主机ip、时间戳、会话哈希id
会话元数据MetadataBag:
MetadataBag用于存放会话本身的属性,drupal的MetadataBag继承了symfonyd MetadataBag,为什么要继承?因为它添加了对跨站请求伪造CSRF的支持
在MetadataBag里面存放了防范CSRF的二次授权种子,称为CsrfTokenSeed,为什么要这个种子请学习CSRF吧
现在我们知道了drupal8会话数据的存放,此外会话管理器用到了SessionConfiguration,这比较简单不多讲述。
drupal8会话延迟启动特性:
drupal8具备会话延迟启动功能,这个是drupal为了提高性能额外增加的,在Symfony中并没有
如果在请求中没有发现会话id那么不会启动会话,这个允许匿名缓存发挥作用,匿名缓存系统如果在请求中发现会话id即被认为不是匿名的,将不工作
那我们需要真实的启动怎么办?没有关系,我们其实没有必要这样做,会话对象会在没有启动的情况下会建立$_SESSION
所以我们可以照常使用会话对象,而不会感觉到没有真正启动,如果有会话数据产生那么会话对象在保存的时候会真正启动会话去保存数据
其原理可以使用下面的代码展示:
<?php
echo isset($_SESSION)?"isStart":"noStart"; // 并未调用session_start()显示未开始
function yunke()
{
$_SESSION=array();//变量名$_SESSION具有特殊性,可用超全局,如果圆通$_YUNKE就不行
}
yunke();
echo isset($_SESSION)?"isStart":"noStart"; //超全局会话对象存在了,虽然有$_SESSION超全局变量存在,但会话并没有真正启动,并未调用session_start()
echo session_status (); //返回PHP_SESSION_NONE
在php中表示会话状态有三个常量:
PHP_SESSION_DISABLED 会话功能被禁用。
PHP_SESSION_NONE 会话功能可用,但不存在当前会话(没有启用)。
PHP_SESSION_ACTIVE 会话功能可用,而且存在当前会话(已经启用)。
下面我们来看一看\core\lib\Drupal\Core\Session文件夹里面和会话相关的其他代码:
AccountInterface:账户接口,提供基本的账户信息接口
UserSession:默认的账户接口实现
AnonymousUserSession:匿名账户接口
AccountProxyInterface:账户代理接口,其实就是一个账户对象的包装,为什么要设计他?因为可以做账户切换
AccountProxy:账户代理接口的默认实现
AccountSwitcherInterface:账户切换接口
AccountSwitcher:账户切换接口的默认实现
PermissionsHashGeneratorInterface:权限哈希产生器接口
PermissionsHashGenerator:权限哈希产生器
以上就是drupal8的基本session全貌了
额外信息:
1:在站点配置文件里面可以设置会话的访问更新阀值,也就是多长时间需要更新一次会话数据的最后访问时间
这个话有点绕,其实就是在会话的元数据包里面记录了会话的最后使用时间,源代码:$this->meta[self::UPDATED],这个时间保存在$_SESSION里
开启会话的用户可能连续打开多个页面,每个页面都会访问会话,而会话数据又没有变化,无变化是不会重写会话的
那么每次都去记录一下访问时间,就导致会话$_SESSION改变,需要数据库连接重写会话数据,这样则有些损失性能
那么就设定一个阀值,超过这个阀值时间才记录一次,这样就有比较好的性能,在会话无改变的情况下不用每次都重写数据
格式如下:
$settings['session_write_interval'] =秒数;默认为180秒,
2:默认的会话储存选项是:
gc_probability: 1 与下一项合用
gc_divisor: 100 与上面的选项一起决定会话开始时启动垃圾回收进程的概率
gc_maxlifetime: 200000 寿命超过该值的会话会被清除,在会话处理器中的gc($lifetime)就是用的该值,超期会在数据库中删除
cookie_lifetime: 2000000 以秒数指定了发送到浏览器的 cookie 的生命周期。值为 0 表示“直到关闭浏览器”。默认为 0
这个默认值可以在/sites/default/services.yml中修改,(对应到自己的站点目录,如果没有services.yml文件请复制一份default.services.yml修改为services.yml)
关于更多会话选项请查看官方手册:http://php.net/manual/zh/session.configuration.php
3:如果你直接从数据库下载会话数据,使用unserialize方法会产生错误,这是由于会话数据的序列化不同于serialize方法,可由session.serialize_handler指定
如果你很好奇想看一看数据库里面保存的会话数据请用以下方法反序列化:
session_start();
session_decode(file_get_contents("sessions-session.bin"));
print_r($_SESSION);
4:如何使用drupal8自己的会话对象:
有容器时:$this->container->get("session");
有请求对象时:$request->getSession();
全局使用:\Drupal::service('session');
5:drupal的会话系统和第三方是不兼容的,也就是说如果第三方程序已经开启会话,那么使用drupal会话将报错,请记住必需由drupal来开启会话
也和php.ini 中的自动开始指令 session.auto_start = 1 不兼容,该指令必须设置为0关闭
drupal的会话数据是放数据库的,第三方程序可能不能,而php只能注册一个SessionHandler保存处理器,这是不兼容的根源
这样的不兼容性在Symfony中有介绍,给出了解决办法,请看:http://symfony.com/doc/current/components/http_foundation/session_php_bridge.html
我是云客,【云游天下,做客四方】,微信号:indrupal,欢迎转载,但须注明出处,讨论请加qq群203286137
反馈互动