php session 模塊的源代碼解析

image首先是擴展中的目錄結構php

首先來看幾個重要的數據結構,第一個ps_module_struct表明着PHP中session中要實現的幾個處理session的函數指針,分別的做用是open、close、read、write、destory等功能cookie

 

typedef struct ps_module_struct {
	const char *s_name;
	int (*s_open)(PS_OPEN_ARGS);
	int (*s_close)(PS_CLOSE_ARGS);
	int (*s_read)(PS_READ_ARGS);
	int (*s_write)(PS_WRITE_ARGS);
	int (*s_destroy)(PS_DESTROY_ARGS);
	int (*s_gc)(PS_GC_ARGS);
	char *(*s_create_sid)(PS_CREATE_SID_ARGS);
} ps_module;

第二個數據結構,全局的session變量,能夠經過PS(var_name)來訪問,其中部分變量表明着php.ini配置文件中的設置的參數,如save_path、session_name、use_cookie、use_http_cookie等session

typedef struct _php_ps_globals {
	char *save_path;
	char *session_name;
	char *id;
	char *extern_referer_chk;
	char *entropy_file;
	char *cache_limiter;
	long entropy_length;
	long cookie_lifetime;
	char *cookie_path;
	char *cookie_domain;
	zend_bool  cookie_secure;
	zend_bool  cookie_httponly;
	ps_module *mod;
	void *mod_data;
	php_session_status session_status;
	long gc_probability;
	long gc_divisor;
	long gc_maxlifetime;
	int module_number;
	long cache_expire;
	union {
		zval *names[6];
		struct {
			zval *ps_open;
			zval *ps_close;
			zval *ps_read;
			zval *ps_write;
			zval *ps_destroy;
			zval *ps_gc;
		} name;
	} mod_user_names;
	zend_bool bug_compat; /* Whether to behave like PHP 4.2 and earlier */
	zend_bool bug_compat_warn; /* Whether to warn about it */
	const struct ps_serializer_struct *serializer;
	zval *http_session_vars;
	zend_bool auto_start;
	zend_bool use_cookies;
	zend_bool use_only_cookies;
	zend_bool use_trans_sid;	/* contains the INI value of whether to use trans-sid */
	zend_bool apply_trans_sid;	/* whether or not to enable trans-sid for the current request */

	long hash_func;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
	php_hash_ops *hash_ops;
#endif
	long hash_bits_per_character;
	int send_cookie;
	int define_sid;
	zend_bool invalid_session_id;	/* allows the driver to report about an invalid session id and request id regeneration */
} php_ps_globals;

來看session模塊啓動過程,數據結構

static PHP_INI_MH(OnUpdateSaveHandler) /* {{{ */函數首先會根據php.ini文件中的save_handler配置,初始化PS全局變量中的mod,php默認的兩個saveHandler能夠爲file和userapp

static ps_module *ps_modules[MAX_MODULES + 1] = {
	ps_files_ptr,
	ps_user_ptr
};

咱們在php代碼中經過ini_set設置session.save_handler既能夠經過這個函數更新PS(mod)變量dom

static PHP_INI_MH(OnUpdateSaveHandler) /* {{{ */
{
	ps_module *tmp;
	SESSION_CHECK_ACTIVE_STATE;

	tmp = _php_find_ps_module(new_value TSRMLS_CC);

	if (PG(modules_activated) && !tmp) {
		int err_type;

		if (stage == ZEND_INI_STAGE_RUNTIME) {
			err_type = E_WARNING;
		} else {
			err_type = E_ERROR;
		}

		/* Do not output error when restoring ini options. */
		if (stage != ZEND_INI_STAGE_DEACTIVATE) {
			php_error_docref(NULL TSRMLS_CC, err_type, "Cannot find save handler '%s'", new_value);
		}
		return FAILURE;
	}
	PS(mod) = tmp;

	return SUCCESS;
}

……看重要的session_start函數的邏輯,N長,概略的說下本身的理解,不必定對,請輕拍~函數

PHPAPI void php_session_start(TSRMLS_D) /* {{{ */
{
	zval **ppid;
	zval **data;
	char *p, *value;
	int nrand;
	int lensess;

	if (PS(use_only_cookies)) {
		PS(apply_trans_sid) = 0;
	} else {
		PS(apply_trans_sid) = PS(use_trans_sid);
	}
	switch (PS(session_status)) {
		case php_session_active:
			php_error(E_NOTICE, "A session had already been started - ignoring session_start()");
			return;
			break;

		case php_session_disabled:
			value = zend_ini_string("session.save_handler", sizeof("session.save_handler"), 0);
			if (!PS(mod) && value) {
				PS(mod) = _php_find_ps_module(value TSRMLS_CC);
				if (!PS(mod)) {
					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot find save handler '%s' - session startup failed", value);
					return;
				}
			}
			value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler"), 0);
			if (!PS(serializer) && value) {
				PS(serializer) = _php_find_ps_serializer(value TSRMLS_CC);
				if (!PS(serializer)) {
					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot find serialization handler '%s' - session startup failed", value);
					return;
				}
			}
			PS(session_status) = php_session_none;
			/* fallthrough */

		default:
		case php_session_none:
			PS(define_sid) = 1;
			PS(send_cookie) = 1;
	}

	lensess = strlen(PS(session_name));

	/* Cookies are preferred, because initially
	 * cookie and get variables will be available. */

	if (!PS(id)) {
		if (PS(use_cookies) && zend_hash_find(&EG(symbol_table), "_COOKIE", sizeof("_COOKIE"), (void **) &data) == SUCCESS &&
				Z_TYPE_PP(data) == IS_ARRAY &&
				zend_hash_find(Z_ARRVAL_PP(data), PS(session_name), lensess + 1, (void **) &ppid) == SUCCESS
		) {
			PPID2SID;
			PS(apply_trans_sid) = 0;
			PS(send_cookie) = 0;
			PS(define_sid) = 0;
		}

		if (!PS(use_only_cookies) && !PS(id) &&
				zend_hash_find(&EG(symbol_table), "_GET", sizeof("_GET"), (void **) &data) == SUCCESS &&
				Z_TYPE_PP(data) == IS_ARRAY &&
				zend_hash_find(Z_ARRVAL_PP(data), PS(session_name), lensess + 1, (void **) &ppid) == SUCCESS
		) {
			PPID2SID;
			PS(send_cookie) = 0;
		}

		if (!PS(use_only_cookies) && !PS(id) &&
				zend_hash_find(&EG(symbol_table), "_POST", sizeof("_POST"), (void **) &data) == SUCCESS &&
				Z_TYPE_PP(data) == IS_ARRAY &&
				zend_hash_find(Z_ARRVAL_PP(data), PS(session_name), lensess + 1, (void **) &ppid) == SUCCESS
		) {
			PPID2SID;
			PS(send_cookie) = 0;
		}
	}

	/* Check the REQUEST_URI symbol for a string of the form
	 * '<session-name>=<session-id>' to allow URLs of the form
	 * http://yoursite/<session-name>=<session-id>/script.php */

	if (!PS(use_only_cookies) && !PS(id) && PG(http_globals)[TRACK_VARS_SERVER] &&
			zend_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER]), "REQUEST_URI", sizeof("REQUEST_URI"), (void **) &data) == SUCCESS &&
			Z_TYPE_PP(data) == IS_STRING &&
			(p = strstr(Z_STRVAL_PP(data), PS(session_name))) &&
			p[lensess] == '='
	) {
		char *q;

		p += lensess + 1;
		if ((q = strpbrk(p, "/?\\"))) {
			PS(id) = estrndup(p, q - p);
			PS(send_cookie) = 0;
		}
	}

	/* Check whether the current request was referred to by
	 * an external site which invalidates the previously found id. */

	if (PS(id) &&
			PS(extern_referer_chk)[0] != '\0' &&
			PG(http_globals)[TRACK_VARS_SERVER] &&
			zend_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_REFERER", sizeof("HTTP_REFERER"), (void **) &data) == SUCCESS &&
			Z_TYPE_PP(data) == IS_STRING &&
			Z_STRLEN_PP(data) != 0 &&
			strstr(Z_STRVAL_PP(data), PS(extern_referer_chk)) == NULL
	) {
		efree(PS(id));
		PS(id) = NULL;
		PS(send_cookie) = 1;
		if (PS(use_trans_sid) && !PS(use_only_cookies)) {
			PS(apply_trans_sid) = 1;
		}
	}

	php_session_initialize(TSRMLS_C);

	if (!PS(use_cookies) && PS(send_cookie)) {
		if (PS(use_trans_sid) && !PS(use_only_cookies)) {
			PS(apply_trans_sid) = 1;
		}
		PS(send_cookie) = 0;
	}

	php_session_reset_id(TSRMLS_C);

	PS(session_status) = php_session_active;

	php_session_cache_limiter(TSRMLS_C);

	if (PS(mod_data) && PS(gc_probability) > 0) {
		int nrdels = -1;

		nrand = (int) ((float) PS(gc_divisor) * php_combined_lcg(TSRMLS_C));
		if (nrand < PS(gc_probability)) {
			PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &nrdels TSRMLS_CC);
#ifdef SESSION_DEBUG
			if (nrdels != -1) {
				php_error_docref(NULL TSRMLS_CC, E_NOTICE, "purged %d expired session objects", nrdels);
			}
#endif
		}
	}
}
  1. session_start的流程能夠歸納爲如下幾個步驟
    1. 首先檢查session全局配置參數的值use_cookie_only和use_trans_sid,二者分別表示sessionid在客戶端只能經過cookie保存和只能經過url傳遞
    2. 檢查PS(session_status)狀態,如果已經啓動了session,則會打印一條NOTICE日誌,也就是咱們常見的A session had already been started
    3. 如果session被禁用狀態,則會檢查save_handler,試圖初始化save_handler
    4. 接下來就是最重要的獲取session_id的值的流程了,概略的說就是根據客戶端保存cookie的方式,一次遍歷$_COOKIE、$_GET、$_POST、$_SERVER這些Hashtable獲取key爲PS(session_name)也就是通常爲PHPSESSID的值
  2. …未完待續
相關文章
相關標籤/搜索