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
是cookiename
,可以通过$_COOKIE['cookiename']
获取它的值。time()
函数的结果加上希望过期的秒数。或者也可以用mktime()
来设置。如time()+60*60*24*30
表示Cookie30天后过期。如果设置为零,或者忽略该参数,Cookie会在会话结束时过期(也就是关闭浏览器时)。domain
有效。如果设置成'/foo/'
,Cookie仅仅对domain
中/foo/
目录及其子目录有效。默认值是设置Cookie时的当前目录。$_SERVER["HTTPS"]
判断)。【示例】
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):
可以看到返回消息头中包含了3个Set-Cookie部分,用于通知浏览器设置对应的Cookie。当我们再次刷新页面时,可以看到请求消息头中携带了Cookie信息。刷新请求得到的请求消息头如下:
可见其中已经携带了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>
程序运行结果:
访问以上通过JavaScript在客户端设置Cookie时的消息头如下:
可以看到在第一次访问页面时向服务器端发送HTTP请求时就已经携带了Cookie信息。
Cookie的工作原理:浏览器请求URL时,会首先扫描本地存储的Cookie,如果发现其中有何此URL相关联的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自动生成的文本会存在与IE浏览器的Cookies临时文件夹中,在浏览器中删除Cookie文件是比较快捷的方法,具体步骤如下:
① 在浏览器页面中选择【工具】选项,在弹出的下拉菜单中选择【Internet选项】菜单。
② 打开【Internet选项】对话框,在【常规】选项卡中单击【删除】按钮。
③ 打开【删除浏览的历史记录】对话框,选中【Cookie】复选框,单击【删除】按钮即可。
使用函数删除
删除Cookie仍然使用setcookie()
函数。当删除Cookie时,将第二个参数设置为空,第三个参数的过期时间设置为小于系统的当前时间即可。
【示例】
// 将Cookie的过期时间设置为比当前时间减少10秒
setcookie("user", "", time()-10);
?>
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] =>
)
其中各键代表的含义如下:
【注意】不是所有浏览器都支持samesite属性。
一个完整的会话包括创建会话、注册会话变量、使用会话变量和删除会话变量等。
创建session之前必须先开启Session,可使用session_start()
开启Session,同Cookie一样,在开始前不能有任何输出内容,否则会出现如下警告:
Warninig: session_start(): Cannot send session cookie - headers already sent
也可以修改php.ini中的session.auto_start=0
为session.auto_start=1
,设置自动开启Session支持,这样就不必每次在使用Session的时候都加上session_start()
了。
session_start()
函数首先检查当前是否已经存在一个会话,如果不存在,将创建一个全新的会话,并且这个会话可以访问超全局变量$_SESSION
数组。如果已经存在一个会话,函数会直接使用这个会话,加载已注册过的会话变量,然后使用。该函数的定义如下:
session_start([array $options=array()]):bool
如果通过GET或者POST方式,或者使用cookie提交了会话ID,则会重现现有会话。
参数:
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']);
当会话自动开始或者通过session_start()
手动开始的时候,PHP内部会调用会话管理器的open
和read
回调函数。会话管理器可能是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请求消息头如下:
默认情况下,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.
当我们的产品部署到生产环境后,访问量会越来越大,服务器的负载会越来越高,如果我们开启了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内置的会话存储机制之外的方式,可以使用本函数。例如,可以自定义会话存储函数来将会话数据存储到数据库。
参数:
SessionHandlerInterface
,SessionIdInterface
或SessionUpdateTimestampHandlerInterface
接口的对象,如:SessionHandler
。自PHP5.4之后可以使用。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_probability
和session.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,会在浏览器中得到如下输出: