PHP源码阅读笔记三十七:PHP中的SESSION实现

PHP源码阅读笔记三十七:PHP中的SESSION实现
源码版本:php5.3.1
环境:VS2008
本文包括PHP中SESSION用到的COOKIE管理,缓存限制,序列化
【COOKIE管理】
在浏览器未关闭cookie的情况下,我们可以看到当有session id生成并返回给发送请求的客户端时,会有一些cookie信息写入到浏览器。其实现代码在session.c 1191行开始。

1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
/* *********************
   * Cookie Management *
   ********************* */
 
#define COOKIE_SET_COOKIE "Set-Cookie: "
#define COOKIE_EXPIRES	"; expires="
#define COOKIE_PATH	"; path="
#define COOKIE_DOMAIN	"; domain="
#define COOKIE_SECURE	"; secure"
#define COOKIE_HTTPONLY	"; HttpOnly"
 
static void php_session_send_cookie(TSRMLS_D) /* {{{ */
{
	smart_str ncookie = {0};
	char *date_fmt = NULL;
	char *e_session_name, *e_id;
 
	if (SG(headers_sent)) {
		char *output_start_filename = php_get_output_start_filename(TSRMLS_C);
		int output_start_lineno = php_get_output_start_lineno(TSRMLS_C);
 
		if (output_start_filename) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cookie - headers already sent by (output started at %s:%d)", output_start_filename, output_start_lineno);
		} else {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cookie - headers already sent");
		}
		return;
	}
 
	/* URL encode session_name and id because they might be user supplied */
	e_session_name = php_url_encode(PS(session_name), strlen(PS(session_name)), NULL);
	e_id = php_url_encode(PS(id), strlen(PS(id)), NULL);
 
	smart_str_appends(&ncookie, COOKIE_SET_COOKIE);
	smart_str_appends(&ncookie, e_session_name);
	smart_str_appendc(&ncookie, '=');
	smart_str_appends(&ncookie, e_id);
 
	efree(e_session_name);
	efree(e_id);
 
	if (PS(cookie_lifetime) > 0) {
		struct timeval tv;
		time_t t;
 
		gettimeofday(&tv, NULL);
		t = tv.tv_sec + PS(cookie_lifetime);
 
		if (t > 0) {
			date_fmt = php_format_date("D, d-M-Y H:i:s T", sizeof("D, d-M-Y H:i:s T")-1, t, 0 TSRMLS_CC);
			smart_str_appends(&ncookie, COOKIE_EXPIRES);
			smart_str_appends(&ncookie, date_fmt);
			efree(date_fmt);
		}
	}
 
	if (PS(cookie_path)[0]) {
		smart_str_appends(&ncookie, COOKIE_PATH);
		smart_str_appends(&ncookie, PS(cookie_path));
	}
 
	if (PS(cookie_domain)[0]) {
		smart_str_appends(&ncookie, COOKIE_DOMAIN);
		smart_str_appends(&ncookie, PS(cookie_domain));
	}
 
	if (PS(cookie_secure)) {
		smart_str_appends(&ncookie, COOKIE_SECURE);
	}
 
	if (PS(cookie_httponly)) {
		smart_str_appends(&ncookie, COOKIE_HTTPONLY);
	}
 
	smart_str_0(&ncookie);
 
	/*	'replace' must be 0 here, else a previous Set-Cookie
		header, probably sent with setcookie() will be replaced! */
	sapi_add_header_ex(ncookie.c, ncookie.len, 0, 0 TSRMLS_CC);
}
/* }}} */

第1195~1200行 定义常量宏,这些定义的内容我们可以通过查看http协议的应答头中可以看到,Set-Cookie是字段名,其余的为此字段中可以包含的变量,我们可以通过与PHP自带的setcookie操作函数的参数比对进行学习,此函数的定义如下:

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

相关参数说明请参考:http://docs.php.net/manual/zh/function.setcookie.php
第1208~1218行 关于是否已经发送了cookie应答的情况处理
第1221~1222行 使用PHP中的函数urlencode的C程序实现,以防止人为添加的session name和session id出现混乱
第1224~1227行 将Set-Cookie字段名,session name和session id的值添加到将要发送的cookie字符串中。
第1232~1245行 cookie的过期时间处理
第1247~1250行 cookie的路径处理
第1252~1255行 cookie的域名处理
第1257~1259行 cookie是否仅仅通过安全连接(https)发送cookie。
第1261~1263行 cookie是否在cookie中添加httpOnly标志(仅允许HTTP协议访问)的处理
最后通过sapi_add_header_ex函数将生成的字符串添加到应答头中。
【缓存限制器】
缓存限制器的结构定义:

1041
1042
1043
1044
typedef struct {
	char *name;	
	void (*func)(TSRMLS_D);
} php_session_cache_limiter_t;

如上所示代码,一个限制器包括一个名称和一个处理函数。
缓存限制器的实现原理
从其代码实现看,所谓的缓存限制器是通过返回不同的http请求的缓存控制字段内容达到缓存控制的目的。
默认情况下,session.cache_limiter = nocache
即使用CACHE_LIMITER_FUNC(nocache)
其中关于缓存控制器总共有4种方案,分别为:

1154
1155
1156
1157
1158
1159
1160
static php_session_cache_limiter_t php_session_cache_limiters[] = {
	CACHE_LIMITER_ENTRY(public)
	CACHE_LIMITER_ENTRY(private)
	CACHE_LIMITER_ENTRY(private_no_expire)
	CACHE_LIMITER_ENTRY(nocache)
	{0}
};

其调用代码在session.c1180 行,如下:

1180
1181
1182
1183
1184
1185
	for (lim = php_session_cache_limiters; lim->name; lim++) {
		if (!strcasecmp(lim->name, PS(cache_limiter))) {
			lim->func(TSRMLS_C);
			return 0;
		}
	}

以上代码为一个遍历缓存控制器所在数组,并判断其方案名与PS(cache_limiter)是否一致,如果一致则执行此缓存控制,并返回。

【序列化实现】
PHP的session存放的都是序列化后的数据。
默认情况下使用session.serialize_handler = php
在源码中默认给出了两种序列化的实现。其最多可以有11种序列化的方法,相关代码如下:

982
983
984
985
986
987
988
#define MAX_SERIALIZERS 10
#define PREDEFINED_SERIALIZERS 2
 
static ps_serializer ps_serializers[MAX_SERIALIZERS + 1] = {
	PS_SERIALIZER_ENTRY(php),
	PS_SERIALIZER_ENTRY(php_binary)
};

ps_serializer结构定义如下 :

161
162
163
164
165
166
167
168
#define PS_SERIALIZER_ENCODE_ARGS char **newstr, int *newlen TSRMLS_DC
#define PS_SERIALIZER_DECODE_ARGS const char *val, int vallen TSRMLS_DC
 
typedef struct ps_serializer_struct {
	const char *name;
	int (*encode)(PS_SERIALIZER_ENCODE_ARGS);
	int (*decode)(PS_SERIALIZER_DECODE_ARGS);
} ps_serializer;

序列化通过调用php_session_register_serializer函数实现序列化的注册过程
php_session_register_serializer函数遍历ps_serializers数组,并判断数组元素的name属性是否为NULL,如果为NULL,则将新的序列化函数添加到ps_serializers数组。

【其它维度的文章】
PHP5 Session 浅析I
PHP5 Session 浅析II
作者从另外一个维度分析了session的一些原理,值得学习一下。

PHP源码阅读笔记三十七:PHP中的SESSION实现》上有1条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注


*

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>