1.前言
PHP的源生session默认的文件存储机制,我想大家用起来肯定遇到不少坑,laravel框架的作者也不怎么建议使用源生的session机制,而代替它的方法也很简单,自己实现了一个文件session存储机制,比起源生的session灵活性很高,也能很好的控制原来session的诡异生命周期。今天,我想介绍下session的原理,并且自己实现一个文件session机制。
2.什么是session
session翻译过来就是会话,我们的客户端浏览器,每次发送请求给Web服务器就可以看作是一个会话。
3.session诞生的技术背景
HTTP协议是无状态的协议,我们web服务器需要记忆不同的HTTP请求,那么势必会引入一个离线存储的概念。HTTP的RFC文档中,并没有提及记忆的概念,于是浏览器厂商加入了cookie的概念,用于维护和web服务器的会话状态。
4.一次session的生命周期
浏览器第一次请求web服务器,同一个域名下是没有cookie的存储,而web服务器上的PHP程序开启会话机制,如:session_start()之后,在响应头中会自动添加会话唯一标识的cookie信息。浏览器拿到cookie响应头后,就会自动存储在客户端,所以,我们知道浏览器每次请求Web服务器,会自动的把cookie信息跟着请求一起发送给Web服务器,这样,Web服务器就能识别发起HTTP协议的浏览器了。这里有一个问题:cookie过期了可以自动销毁,因为浏览器默认是存储在内存中的。而后端的session,默认是文件机制,所以过期后,并不会自动删除。(这一点很坑)
5.为什么要自己重写session?
1.源生的session生命周期难以维护,同一个会话的所有key共享过期时间
2.现在的前端除了浏览器以外,还有移动端、物联网等待。它们没有浏览器的cookie机制,所以,有必要重新去定义一个独立于cookie的session机制。
6.代码实现
<?php
declare(strict_types=1);
interface SessionInterface
{
public function get($key);
public function set($key, $value, $expires);
public function del($key);
public function setSessionId($session_id);
public function getSessionId();
}
trait CacheTrait
{
public function isExpires($expires_at)
{
return (time() - $expires_at) >= 0;
}
}
class FileSessionException extends \Exception
{
}
class FileSession implements SessionInterface
{
use CacheTrait;
const EXPIRES = 65535;
private $configs = [
'saveDir' => '.' . DIRECTORY_SEPARATOR,
'fileExpires' => self::EXPIRES
];
private $data;
private $session_id;
private function generateUniqueName()
{
return uniqid('session');
}
private function getSessionPath($session_id)
{
return $this->configs['saveDir'] . $session_id;
}
private function getAllSession()
{
return unserialize($this->data);
}
private function loadSessionFromFile($fileName)
{
if (is_file($file = $this->getSessionPath($fileName))) {
if ($handle = fopen($file, 'r')) {
$caches = fread($handle, filesize($file));
fclose($handle);
return $caches;
}
}
throw new FileSessionException('加载文件失败,文件可能不存在');
}
public function __construct(array $config = [])
{
$this->configs = array_merge($this->configs, $config);
$this->session_id = $this->configs['session_id'] ?: $this->generateUniqueName();
$this->data = isset($this->configs['session_id']) ? $this->loadSessionFromFile($this->configs['session_id']) : '';
}
public function persistent()
{
if ($handle = fopen($this->getSessionPath($this->getSessionId()), 'w')) {
$cnt = fwrite($handle, $this->data);
fclose($handle);
if (false === $cnt) {
goto ERROR;
}
return [$cnt, $this->session_id];
}
ERROR:
throw new FileSessionException('缓存文件写入失败');
}
public function get($key)
{
if ($cache = $this->getAllSession()) {
if (isset($cache[$key])) {
if ($this->isExpires($cache[$key]['expires_at'])) {
$this->del($cache[$key]);
throw new FileSessionException(sprintf('缓存%s,已失效', $key));
}
return $cache[$key]['value'];
}
throw new FileSessionException(sprintf('缓存%s,不存在', $key));
}
throw new FileSessionException('获取缓存失败');
}
public function set($key, $value, $expires = self::EXPIRES)
{
$oldCache = $this->getAllSession();
$oldCache[$key] = ['value' => $value, 'expires_at' => $expires + time()];
$this->data = serialize($oldCache);
}
public function del($key)
{
if (is_file($this->getSessionPath($this->session_id))) {
$oldCache = $this->getAllSession();
if (isset($oldCache[$key])) {
unset($oldCache[$key]);
}
}
throw new FileSessionException('缓存文件不存在,或者未找到指定key');
}
public function setSessionId($session_id)
{
$this->session_id = $session_id;
}
public function getSessionId()
{
return $this->session_id;
}
}
try {
$file = new FileSession(array_merge([], ['session_id' => $_COOKIE['session_id']]));
$file->set('cnt', 1);
$file->set('nick_name', 'jack');
var_dump($file->get('nick_name'));
list(, $session_id) = $file->persistent();
setcookie('session_id', $session_id, time() + FileSession::EXPIRES);
$file->setSessionId($session_id);
var_dump($file->get('cnt'));
} catch (FileSessionException $exception) {
var_dump($exception->getMessage());
$file->persistent();
}
如无特殊说明,文章均为本站原创,转载请注明出处。如发现有什么不对的地方,希望得到您的指点。