PHP7语言基础——Cookie与Session

文章目录

  • Cookie详解
    • 基本概念和设置
    • Cookie的应用和存储机制
    • 删除Cookie
  • Session详解
    • 会话管理
      • 创建会话
      • PHP中的session工作机制
      • PHP中的Session存储机制
      • 注销和注销会话变量
  • 扩展——使用Redis存储Session

HTTP协议是无状态协议,对于事物处理没有记忆能力。缺少状态意味着后续处理需要前面的信息,就必须重新传送数据,这样可能导致每次连接传送的数据量逐渐增大。为了弥补HTTP协议无状态的不足,两种用于保持HTTP连接状态的技术应运而生了,一个是Cookie,另一个是Session。其中Cookie存储在客户端,并显示永久的数据存储。Session将数据存储在服务器端,保证数据在程序的单词访问中持续有效。

Cookie详解

基本概念和设置

Cookie是独立语言而存在的,各种编程语言都有操作Cookie的能力。在实现过程中,编程语言通过指令告之浏览器,然后浏览器实现设置Cookie的功能。读取Cookie则是通过浏览器请求服务端时携带的HTTP头部中的Cookie信息的来的。

在PHP中可以使用setcookie()函数来设置cookie,其语法如下:

setcookie(string $name[, string $value=""[, int $expire=0[, string $path=""[, string $domain=""[, bool $secure=false[, bool $httponly=false]]]]]]):bool

该函数定义了Cookie,会和剩下的HTTP头一起发送给客户端。和其他HTTP头一样,必须在脚本产生任意输出之前发送Cookie(由于协议限制)(包括或者空格)之前调用该函数。一旦设置Cookie后,下次打开页面时可以使用$_COOKIE读取。Cookie值同样也存在与$_REQUEST中。如果在调用本函数以前就产生了输出,setcookie() 会调用失败并返回 FALSE。 如果 setcookie() 成功运行,返回 TRUE

参数:

  • name:Cookie名称。
  • value:Cookie值。这个值存储与用户的电脑中,因此不建议存储敏感信息。如namecookiename,可以通过$_COOKIE['cookiename']获取它的值。
  • expire:Cookie过期时间,这是个Unix时间戳,也就是说,基本可以用time()函数的结果加上希望过期的秒数。或者也可以用mktime()来设置。如time()+60*60*24*30表示Cookie30天后过期。如果设置为零,或者忽略该参数,Cookie会在会话结束时过期(也就是关闭浏览器时)。
  • path:Cookie有效的服务器路径。设置成’/'时,Cookie对整个域名domain有效。如果设置成'/foo/',Cookie仅仅对domain/foo/目录及其子目录有效。默认值是设置Cookie时的当前目录。
  • domain:Cookie的有效域名/子域名。设置成子域名(如www.example.com),会使Cookie对这个子域名和它的三级域名有效(包括它的全部子域名),只要设置成域名就可以了。
  • secure:设置这个Cookie是否仅仅通过安全的HTTPS连接传给客户端。设置成TRUE时,只有安全连接存在时才会设置Cookie。如果是在服务器端处理这个需求,程序员需要仅仅在安全连接上发送此类Cookie(通过$_SERVER["HTTPS"]判断)。
  • httponly:设置成TRUE,Cookie仅可通过HTTP协议访问。就是说Cookie无法通过类似JavaScript这样的脚本语言访问。要有效减少XSS攻击时的身份窃取行为,建议用此设置(注意:这个选项不是所有浏览器都支持)。

【示例】


setcookie('name', 'ib-top');
setcookie('num', '100', time()+100, '/foo/');
setcookie('gender', 'male', time()+100,'', 'www.ib-top.com');
print_r($_COOKIE);

程序运行结果:

在这里插入图片描述

也许想看到的结果跟程序运行的结果不一样,我们来详细解释一下以上代码。首先第一个Cookie设置名为name、值为ib-top,其他参数都是默认值,表示在当前目录和域名下都有效,切有效时间持续到浏览器关闭,所以,$_COOKIE中输出了这个Cookie。第二个和第三个Cookie设置只在特定的目录域名和有效时间内才能看到。所以我们在$_COOKIE中看不到相应信息。

注意:第一次运行程序看不到任何Cookie信息,因为设置完Cookie后需要刷新页面,在下次HTTP请求时头部才会携带上一次设置的Cookie信息。

可以通过开发者工具箱来看一下当前请求的消息头(Request Headers)和响应消息头(Response Headers):

PHP7语言基础——Cookie与Session_第1张图片

可以看到返回消息头中包含了3个Set-Cookie部分,用于通知浏览器设置对应的Cookie。当我们再次刷新页面时,可以看到请求消息头中携带了Cookie信息。刷新请求得到的请求消息头如下:

PHP7语言基础——Cookie与Session_第2张图片

可见其中已经携带了Cookie信息,但是只有设置的name这一个Cookie,因为其他两个Cookie不在这目录或本域名下有效。

PHP和客户端JavaScript都可以操作Cookie,用PHP设置的Cookie也可以用JavaScript读取到,用JavaScript设置的Cookie也可以由PHP读取到。不同的是,PHP设置的Cookie需要在刷新页面后的下一次请求中才有效,而JavaScript设置的Cookie在本次请求中就有效。

【示例】

<script>
function setCookie(name, value) {
	var Days = 30;
	var exp = new Date();
	exp.setTime(exp.getTime() + Days*24*60*60*1000);
	document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
}
function getCookie(name) {
	var arr,reg=new RegExp("(^|)"+name+"=([^;]*)(;|$)");
	if(arr=document.cookie.match(reg))
		return unescape(arr[2]);
	else
		return null;
}
setCookie('test', 'testhahaha');
alert(getCookie('test'));
</script>

程序运行结果:

PHP7语言基础——Cookie与Session_第3张图片

访问以上通过JavaScript在客户端设置Cookie时的消息头如下:

PHP7语言基础——Cookie与Session_第4张图片

可以看到在第一次访问页面时向服务器端发送HTTP请求时就已经携带了Cookie信息。

Cookie的应用和存储机制

Cookie的工作原理:浏览器请求URL时,会首先扫描本地存储的Cookie,如果发现其中有何此URL相关联的Cookie,就会把它们返回给服务器端。

Cookie通常应用于以下几个方面:

  • 在页面之间传递变量。因为浏览器不会保存当前页面上的任何变量信息,如果页面被关闭,那么页面上的所有变量信息也会消失。通过Cookie,可以把变量值在Cookie中保存下来,然后另外的页面可以重新读取这个值。
  • 记录访客信息。利用Cookie可以记录客户曾经输入的信息或记录访问页面的次数。
  • 把所查看的页面保存在Cookie临时文件夹中,可以提高以后的浏览速度。
  • 用来存储一些不敏感信息,如用来防止刷票、记录用户名、限制重复提交等。

下面用一个JavaScript的例子来看一下如果通过设置Cookie来防止用户重复提交,之所以是用JavaScript实现,是因为这样可以减少服务器压力。

<html>
<head>
	<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
	function setCookie(name, value) {
		var Days = 30;
		var exp = new Date();
		exp.setTime(exp.getTime() + 60*100);	// 设置过期时间为1分钟
		document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
	}
	function getCookie(name) {
		var arr,reg=new RegExp("(^|)"+name+"=([^;]*)(;|$)");
		if(arr=document.cookie.match(reg))
			return unescape(arr[2]);
		else
			return null;
	}
	function submit() {
		if(getCookie('submit')){
			alert('你已经提交过了,请一分钟之后再提交');
		} else {
			setCookie('submit','yes');
		}
	}
</script>
<button onclick='submit();'>提交</button>
</body>
<html>

以上代码实现了防止用户在一分钟之内多次提交表单,当用户第一次提交表单时,设置Cookie有效期为1分钟,当再次提交时判断Cookie是否过期来限制用户的提交。

虽然,Cookie是存储在客户端的一段数据,但是不同的浏览器存储Cookie的地方不同:一种是将Cookie数据保存在文件中,另一种是保存在浏览器内存中。

在Windows系统上,IE浏览器Cookie数据位于%APPDATA%\Microsoft\Windows\Cookies\目录中的xxx.txt文件中。在IE浏览器中,IE将各个站点的Cookie分别保存为一个xxx.txt这样的纯文本文件;而Firefox和Chrome是将素有的Cookie都保存在一个文件中,该文件的格式为SQLite数据库格式的文件。Firefox的Cookie数据位于%APPDATA%\Mozilla\Firefox\Profiles\目录中的xxx.default目录下,名为Cookies.sqlite的文件中。

在Firefox中查看Cookie,可以选择“工具>选项>隐私>显示Cookie”。Chrome的Cookie数据位于%LOCALAPPDATA%\Google\Chrome\User Data\Default\目录中名为Cookies的文件中。

删除Cookie

  1. 在浏览器中手动删除
    由于Cookie自动生成的文本会存在与IE浏览器的Cookies临时文件夹中,在浏览器中删除Cookie文件是比较快捷的方法,具体步骤如下:
    ① 在浏览器页面中选择【工具】选项,在弹出的下拉菜单中选择【Internet选项】菜单。
    ② 打开【Internet选项】对话框,在【常规】选项卡中单击【删除】按钮。
    ③ 打开【删除浏览的历史记录】对话框,选中【Cookie】复选框,单击【删除】按钮即可。

  2. 使用函数删除
    删除Cookie仍然使用setcookie()函数。当删除Cookie时,将第二个参数设置为空,第三个参数的过期时间设置为小于系统的当前时间即可。
    【示例】

    
    // 将Cookie的过期时间设置为比当前时间减少10秒
    setcookie("user", "", time()-10);
    ?>
    

Session详解

Session和Cookie一样,都是针对HTTP协议的局限性而提出的一种保持客户端和服务端间会话状态的机制。

在PHP中,每一个Session都有一个ID。这个Session ID是一个由PHP随机生成的加密数字。这个Session ID通过Cookie存储在客户端浏览器中,或者直接通过URL传递到客户端,如果在某个URL后面看到一长串加密的数字,这很有可能就是Session ID了。

使用Session ID打开服务器端相对应的session变量,跟用户相关的会话数据便一目了然。默认情况下,在服务器端的session变量数据是以文件的形式加以存储的,但是会话变量数据也经常通过数据库进行保存。

在浏览器中,有些用户处于安全性的考虑,关闭了其浏览器的Cookie功能。Cookie将不能正常工作。使用Session可以不需要手动设置Cookie,PHP Session可以自动处理。可以使用会话管理,及PHP中的session_get_cookie_params()函数来访问Cookie的内容。该函数返回一个数组,包括Cookie的生存周期、路径、域名、secure等。函数定义如下:

session_get_cookie_params(void):array

函数返回一个如下形式的数组:

Array
(
    [lifetime] => 0
    [path] => /
    [domain] => 
    [secure] => 
    [httponly] => 
    [samesite] => 
)

其中各键代表的含义如下:

  • lifetime:cookie的生命周期,以秒为单位。
  • path:cookie的访问路径。
  • domain:cookie的域。
  • secure:近在使用安全连接时发送cookie。
  • httponly:只能通过http协议访问cookie。
  • samesite:该属性用来限制第三方cookie,从而减少安全风险,它有三个值:
    • Strict:完全禁止第三方cookie,跨站点时,任何情况下都不会发送cookie,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
    • Lax:规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
    • None:无限制。

【注意】不是所有浏览器都支持samesite属性。

会话管理

一个完整的会话包括创建会话、注册会话变量、使用会话变量和删除会话变量等。

创建会话

创建session之前必须先开启Session,可使用session_start()开启Session,同Cookie一样,在开始前不能有任何输出内容,否则会出现如下警告:

Warninig: session_start(): Cannot send session cookie - headers already sent

也可以修改php.ini中的session.auto_start=0session.auto_start=1,设置自动开启Session支持,这样就不必每次在使用Session的时候都加上session_start()了。

session_start()函数首先检查当前是否已经存在一个会话,如果不存在,将创建一个全新的会话,并且这个会话可以访问超全局变量$_SESSION数组。如果已经存在一个会话,函数会直接使用这个会话,加载已注册过的会话变量,然后使用。该函数的定义如下:

session_start([array $options=array()]):bool

如果通过GET或者POST方式,或者使用cookie提交了会话ID,则会重现现有会话。

参数:

  • options:该参数是衣蛾关联数组,如果提供,那么会用其中的项目覆盖会话配置指示中的配置项。次数组中的键无需包含session前缀。除了常规的会话配置指示项,还可以在此数组中包含 read_and_close选项。如果将此选项的值设置为TRUE,那么会话文件会在读取完毕之后马上关闭,因此,可以在会话数据没有变动的时候,避免不必要的文件锁。

还可以通过session_register()函数创建session。在使用session_register()函数之前,需要在php.ini文件中将register_globals选项设置为on,session_register()函数通过为会话登陆一个变量来隐含地启动会话。

【示例1】

请求page1.php页面之后,第二个页面page2.php会包含会话数据。


// page1.php
// 开启session
session_start();
header('charset=utf-8');
echo '欢迎来到page1';

// 注册session变量
$_SESSION['favcolor'] = 'green';
$_SESSION['animal'] = 'cat';
$_SESSION['time'] = time();

// 如果使用cookie方式传送会话ID
echo '
page 2'
; // 如果不是使用cookie方式传送会话ID,则使用URL改写的方式传送会话ID echo '
. SID . '">page 2'
;

// page2.php
// 开启session
session_start();

echo '欢迎来到page 2
'
; // 使用session变量 echo $_SESSION['favcolor']; // green echo $_SESSION['animal']; // cat echo date('Y m d H:i:s', $_SESSION['time']);

PHP中的session工作机制

当会话自动开始或者通过session_start()手动开始的时候,PHP内部会调用会话管理器的openread回调函数。会话管理器可能是PHP默认的,也可能是扩展提供的(SQLite或者Memcached扩展),也可能是通过session_set_save_handler()函数指定的用户自定义会话管理器。通过read回调函数返回的现有会话数据(使用特殊的序列化格式存储),PHP会自动反序列化数据并填充$_SESSION超全局变量。

如果需要对会话进行命名,需在调用session_start()函数之前调用session_name函数。

如果在php.ini中启用了session.use_trans_sid选项,session_start()函数会注册一个颞部输出管理器,该输出管理器完成URL重写的工作。如果用户联合使用了ob_start()ob_gzhandler函数,那么函数的调用顺序会影响输出结果。例如,必须在会话开始之前先调用ob_gzhandler函数完成注册。

当在代码中设置了Session时,在HTTP请求的消息头中会携带一个名为PHPSESSID的Cookie,其值是一个32位十六进制的字符串。每个客户端向服务器请求时都会产生一个不同的值,如果清楚浏览器Cookie,再次刷新页面时将会重新设置一个PHPSESSID的值。服务端接收到这个Cookie,根据其值在服务器中找到对应的Session文件,从而实现保持与客户端链接状态的信息,其中Session中存储着序列化的Session键值等信息。设置了Session的HTTP请求消息头如下:

PHP7语言基础——Cookie与Session_第5张图片

PHP中的Session存储机制

默认情况下,Session是存储在服务器硬盘上的,在php.ini中可通过session.save_path设置Session文件的存储路径,默认为服务器上的/tmp目录。此配置指令还有一个可选的N参数来决定会话文件分布的目录深度。如,设定为5;/tmp将使用创建的会话文件和路径类似于/tmp/4/b/1/e/3/sess_4b1e384ad74619bd212e236e52a5a174If。要使用N参数,必须在使用前先创建好这些目录。在ext/session目录下有个小的shell脚本,即mod_files.sh。windows版本下为mod_files.bat可以用来做这件事。此外,如果使用了N参数并且大于0,那么将不会执行自动垃圾回收。

注销和注销会话变量

注销会话变量使用unset()函数即可。如unset($_SESSION['name'])。该函数用于释放指定的Session变量。如果要注销所有的会话变量,只需要给$_SESSION赋值一个空数组就可以了。注销完成后,使用session_destroy()销毁会话即可,其实就是清除相应的session ID.

扩展——使用Redis存储Session

当我们的产品部署到生产环境后,访问量会越来越大,服务器的负载会越来越高,如果我们开启了session,那么就会生成很多的session文件,由于session默认是存储在硬盘上的,因此每次服务器去读取这些session文件都要经过许多I/O操作。PHP中可以使用session_set_save_handle()函数自定义session保存函数(如打开、关闭、写入、读取等)。该函数的定义如下:

session_set_save_handler(callable $open, callable $close, callable $read, callable $write, callable $destroy, callable $gc[, callable $create_sid[, callable $validate_sid[, callable $update_timestamp]]]):bool

自PHP5.4开始,可以使用下面的方式来注册自定义会话存储函数:

session_set_save_handler(object $sessionhandler [, bool $register_shutdown=TRUE]):bool

session_set_save_handler()设置用户定义会话存储函数。如果想使用PHP内置的会话存储机制之外的方式,可以使用本函数。例如,可以自定义会话存储函数来将会话数据存储到数据库。

参数:

  • sessionhandler:实现了SessionHandlerInterfaceSessionIdInterfaceSessionUpdateTimestampHandlerInterface接口的对象,如:SessionHandler。自PHP5.4之后可以使用。
  • register_shutdown:将函数session_write_close()注册为register_shutdown_function()函数。

或者:

  • open(string $savePath, string $sessionName)
    open回调函数,类似于类的构造函数,在会话打开的时候会调用。这是自动开始会话或者通过调用session_start()手动开始会话之后第一个被调用的回调函数。此回调函数操作成功返回TRUE,失败返回FALSE。

  • close()
    close回调函数,类似于类的析构函数。在write回调函数调用之后调用。当调用session_write_close()函数之后,也会调用close回调函数。此回调函数操作成功返回TRUE,失败返回FALSE。

  • read(string $sessionId)
    如果会话中有数据,read回调函数必须返回将会话数据编码(序列化)后的字符串。如果会话中没有数据,read回调函数返回空字符串。
    在自动开始会话或者通过调用session_start()函数手动开始会话之后,PHP内部调用read回调函数来获取会话数据。在调用read之前,PHP会调用open回调函数。
    read回调函数返回的序列化之后的字符串格式必须与write回调函数保存数据时的格式完全一致。PHP会自动反序列化返回的字符串并填充$_SESSION超全局变量。虽然数据看起来和serialize()函数很相似,但是需要提醒的是,他们是不同的。

  • write(stirng $sessionId, string $data)
    在会话保存数据时会调用write回调函数。此回调函数接收当前会话ID以及$_SESSION中数据序列化之后的字符串作为参数。序列化会话数据的过程由PHP根据session.serialize_handler设定值来完成。
    序列化后的数据将和会话ID关联在一起进行保存。当调用read回调函数获取数据时,所返回的数据必须要和传入wirte回调函数的数据完全保持一致。
    PHP会在脚本执行完毕或调用session_write_close()函数之后调用此函数。注意,调用完成回调函数之后,PHP内部会调用close回调函数。

    PHP 会在输出流写入完毕并且关闭之后才调用 write 回调函数,所以在 write 回调函数中的调试信息不会输出到浏览器中。如果需要在 write 回调函数中使用调试输出,建议将调试输出写入到文件。

  • destroy($sessionId)
    当调用session_destroy()函数,或者调用session_regenerate_id()函数并且设置destroy参数为TRUE时,会调用此回调函数。此回调函数操作成功返回TRUE,失败返回FALSE。

  • gc($lifetime)
    为了清理会话中的旧数据,PHP会不时的调用垃圾收集回调函数。调用周期由session.gc_probabilitysession.gc_divisor参数控制。传入到此回调函数的lifttime参数由session.gc_maxlifetiime设置。此回调函数操作成功返回TRUE,失败返回FASLE。

  • create_sid()
    当需要新的会话ID时被调用的回调函数。回调函数被调用时无传入参数,其返回值应该是一个字符串格式的、有效的会话ID。

为了更好的理解自定义Session存储机制,我们使用Redis代替文件存储Session。

我们使用session_set_save_handler()的第一种原型来实现自定义session存储。

首先我们需要创建 一个用于session管理的类MySessionHandler,代码如下:


// filename:MySessionHandler.class.php
class MySessionHandler {
    // 私有属性
    private $redis;
    private $sessionsavepath;
    private $sessionname;

    // 类构造方法,初始化类属性
    public function __construct()
    {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6400);
        $reval = session_set_save_handler(
            array($this,"open"),
            array($this,"close"),
            array($this,"read"),
            array($this, "write"),
            array($this, "destroy"),
            array($this, "gc")
        );
        session_start();
    }

    public function open($patn, $name)
    {
        return true;
    }

    public function close()
    {
        return true;
    }

    public function read($id)
    {
        $value = $this->redis->get($id);
        if($value) {
            return $value;
        } else {
            return false;
        }
    }

    public function write($id, $data)
    {
        if($this->redis->set($id, $data)) {
            $this->redis->expire($id, 60);
            return true;
        } else {
            return false;
        }
    }

    public function destroy($id)
    {
        if($this->redis->delete($id)) {
            return true;
        }
        return false;
    }

    public function gc($maxlifetime)
    {
        return true;
    }

    public function __destruct()
    {
        session_write_close();
        // TODO:Implement __destruct() method.
    }
}

在该类的构造函数中,我们使用了session_set_save_handler()设置session的处理函数,实例化该类时便完成了用指定函数接管系统处理session的工作。在write回调函数中,以传入的session ID作为key,以session的值作为redis中key的值存入redis,并设置过期时间为60秒;read方法以传入的session ID为key从redis中取出相应的session值。destroy可根据传入的ID删除redis中的session。

接下来我们编写另外一个设置session的脚本,将MySessionHandler引入,以便于使用我们的session管理类。


// setmysession.php
include 'MySessionHandler.class.php';

new MySessionHandler();
$_SESSION['name1'] = 'ib-top.com';
$_SESSION['name2'] = 'ib-top.cn';
$_SESSION['name3'] = 'ib-top.top';
$_SESSION['name4'] = 'ib-top.gov';
$_SESSION['name5'] = array('a' => 1, 2, 3, 4, 5);

最后我们编写一个读取session的脚本:


// getmysession.php
include 'MySessionHandler.class.php';

new MySessionHandler();
var_dump($_SESSION);

我们首先访问setmysession.php,然后访问getmysession.php,会在浏览器中得到如下输出:

PHP7语言基础——Cookie与Session_第6张图片

你可能感兴趣的:(PHP学习笔记)