Created
November 25, 2013 20:40
-
-
Save GregoireHebert/7648489 to your computer and use it in GitHub Desktop.
Custom handler, mysql, one row per session, with a mysql advisory lock per session and per var.
the "standard" session handler one would write to store session data in a MySQL database. Each row stores the data for one session, the serialized session data is stored in a field of type text. Before the session is read, a mysql advisory lock is acq…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* ------------------------------------------------------------------------ | |
* session_mysql.php | |
* ------------------------------------------------------------------------ | |
* PHP4 MySQL Session Handler | |
* Version 1.00 | |
* by Ying Zhang ([email protected]) | |
* Last Modified: May 21 2000 | |
* Modified in 2006 by Andy Bakun (abakun ! thwartedefforts , org) to | |
* enforce session locking | |
* | |
* ------------------------------------------------------------------------ | |
* TERMS OF USAGE: | |
* ------------------------------------------------------------------------ | |
* You are free to use this library in any way you want, no warranties are | |
* expressed or implied. This works for me, but I don't guarantee that it | |
* works for you, USE AT YOUR OWN RISK. | |
* | |
* While not required to do so, I would appreciate it if you would retain | |
* this header information. If you make any modifications or improvements, | |
* please send them via email to Ying Zhang <[email protected]>. | |
* | |
* ------------------------------------------------------------------------ | |
* DESCRIPTION: | |
* ------------------------------------------------------------------------ | |
* This library tells the PHP4 session handler to write to a MySQL database | |
* instead of creating individual files for each session. | |
* | |
* Create a new database in MySQL called "sessions" like so: | |
* | |
* CREATE TABLE `sessions` ( | |
* `sesskey` varchar(32) NOT NULL default '', | |
* `expiry` int(11) unsigned NOT NULL default '0', | |
* `lastused` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, | |
* `value` text NOT NULL, | |
* UNIQUE KEY `sesskey` (`sesskey`), | |
* ) | |
* | |
* ------------------------------------------------------------------------ | |
* USAGE: | |
* ------------------------------------------------------------------------ | |
* Include this file in your scripts before you call session_start(), you | |
* don't have to do anything special after that. | |
* | |
*/ | |
$SESS_DBHOST = DBHOST; /* database server hostname */ | |
$SESS_DBNAME = DATABASE; /* database name */ | |
$SESS_DBUSER = DBUSER; /* database user */ | |
$SESS_DBPASS = DBPASS; /* database password */ | |
$SESS_DBH = ""; | |
$SESS_LIFE = get_cfg_var("session.gc_maxlifetime"); | |
$SESS_LOCK = ''; | |
function sess_open($save_path, $session_name) { | |
global $SESS_DBHOST, $SESS_DBNAME, $SESS_DBUSER, $SESS_DBPASS, $SESS_DBH; | |
if (! $SESS_DBH = mysql_connect($SESS_DBHOST, $SESS_DBUSER, $SESS_DBPASS)) { | |
echo "<li>Can't connect to $SESS_DBHOST as $SESS_DBUSER"; | |
echo "<li>MySQL Error: ", mysql_error(); | |
die; | |
} | |
if (! mysql_select_db($SESS_DBNAME, $SESS_DBH)) { | |
echo "<li>Unable to select database $SESS_DBNAME"; | |
die; | |
} | |
return true; | |
} | |
function sess_close() { | |
global $SESS_LOCK; | |
$qid = mysql_query("select release_lock('$SESS_LOCK')"); | |
mysql_free_result($qid); | |
return true; | |
} | |
function sess_read($key) { | |
global $SESS_DBH, $SESS_LIFE, $SESS_LOCK; | |
$SESS_LOCK = mysql_real_escape_string('sess_'.$key, $SESS_DBH); | |
$qid = mysql_query("select get_lock('$SESS_LOCK', 60)"); | |
$ret = mysql_fetch_row($qid); | |
mysql_free_result($qid); | |
if (!isset($ret[0])) { | |
echo "unable to obtain session lock ($SESS_LOCK)"; | |
die; | |
} | |
$qry = "SELECT value FROM sessions WHERE sesskey = '$key'"; | |
$qid = mysql_query($qry, $SESS_DBH); | |
$ret = mysql_fetch_row($qid); | |
mysql_free_result($qid); | |
if (list($value) = $ret) { | |
$ret = $value; | |
} else { | |
$ret = ''; | |
} | |
return $ret; | |
} | |
function sess_write($key, $val) { | |
global $SESS_DBH, $SESS_LIFE; | |
$expiry = time() + $SESS_LIFE; | |
$value = mysql_real_escape_string($val, $SESS_DBH); | |
$x = isset($_SESSION['sessid']) ? intval($_SESSION['sessid']) : 0; | |
$qry = "REPLACE INTO sessions (sesskey, expiry, value) VALUES ('$key', $expiry, '$value')"; | |
$success = mysql_query($qry, $SESS_DBH); | |
return $success; | |
} | |
function sess_destroy($key) { | |
global $SESS_DBH; | |
$qry = "DELETE FROM sessions WHERE sesskey = '$key'"; | |
$success = mysql_query($qry, $SESS_DBH); | |
return $success; | |
} | |
function sess_gc($maxlifetime) { | |
global $SESS_DBH; | |
$qry = "DELETE FROM sessions WHERE expiry < " . time(); | |
$qid = mysql_query($qry, $SESS_DBH); | |
return mysql_affected_rows($SESS_DBH); | |
} | |
session_set_save_handler( | |
"sess_open", | |
"sess_close", | |
"sess_read", | |
"sess_write", | |
"sess_destroy", | |
"sess_gc"); | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* ------------------------------------------------------------------------ | |
* session_mysqlpervar.php | |
* ------------------------------------------------------------------------ | |
* PHP5 MySQL Session Handler | |
* Requires: PHP 5.1 | |
* Version 1.00 | |
* by Andy Bakun (abakun ! thwartedefforts , org) | |
* Last Modified: 2006-10-30 | |
* | |
* ------------------------------------------------------------------------ | |
* TERMS OF USAGE: | |
* ------------------------------------------------------------------------ | |
* You are free to use this library in any way you want, no warranties are | |
* expressed or implied. This works for me, but I don't guarantee that it | |
* works for you, USE AT YOUR OWN RISK. | |
* | |
* While not required to do so, I would appreciate it if you would retain | |
* this header information. If you make any modifications or improvements, | |
* please send them to me at abakun ! thwartedefforts , org. | |
* | |
* ------------------------------------------------------------------------ | |
* DESCRIPTION: | |
* ------------------------------------------------------------------------ | |
* This file implements a PHP5.1 session handler that uses object | |
* overloading (via PHP's SPL) to provide per-variable storage in a MySQL | |
* table. | |
* It works just like the normal session handler, except it provides | |
* locking primitives using MySQL's advisory lock functions. | |
* | |
* If you can, I suggest turning off persistent MySQL connections to | |
* ensure that all locks are released when your script ends. You may | |
* be able to get around this by ensuring that $_SESSION->__destruct is | |
* called using register_shutdown_function() or using ignore_user_abort(). | |
* I have not experimented with either of these, however it is important | |
* be aware of this limitation. | |
* | |
* ------------------------------------------------------------------------ | |
* SETUP: | |
* ------------------------------------------------------------------------ | |
* Create a new database in MySQL called "sessvar" like so: | |
* | |
* mysql> desc sessvar; | |
* +----------+-------------+------+-----+-------------------+-------+ | |
* | Field | Type | Null | Key | Default | Extra | | |
* +----------+-------------+------+-----+-------------------+-------+ | |
* | sesskey | varchar(32) | | PRI | | | | |
* | lastused | timestamp | YES | | CURRENT_TIMESTAMP | | | |
* | varkey | varchar(40) | | PRI | | | | |
* | varval | text | YES | | NULL | | | |
* +----------+-------------+------+-----+-------------------+-------+ | |
* 4 rows in set (0.00 sec) | |
* | |
* CREATE TABLE `sessvar` ( | |
* `sesskey` varchar(32) NOT NULL default '', | |
* `lastused` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, | |
* `varkey` varchar(40) NOT NULL default '', | |
* `varval` text, | |
* UNIQUE KEY `sessvar` (`sesskey`,`varkey`) | |
* ) | |
* | |
* ------------------------------------------------------------------------ | |
* USAGE: | |
* ------------------------------------------------------------------------ | |
* remove any calls to session_start() from your code, then add this code | |
* in the global scope: | |
* | |
* require_once "session_mysqlpervar.php"; | |
* $_SESSION = new ThreadSafeSessionArray(); | |
* | |
* It works just like the normal PHP session handler, session variables are | |
* accessible through the $_SESSION super-global array. | |
* | |
* to get exclusive (advisory) access to a session variable, use: | |
* | |
* $_SESSION->acquire('variablename'); | |
* | |
* and release the lock with: | |
* | |
* $_SESSION->releease('variablename'); | |
* | |
* you can also do the following, which may make your code easier to | |
* maintain: | |
* | |
* foreach ($_SESSION->synchronize('variablename') as $lock) { | |
* if (!$lock) break; | |
* | |
* ... put your critical section code that uses | |
* ... $_SESSION['variablename'] here | |
* } | |
* | |
*/ | |
$SESS_DBHOST = DBHOST; /* database server hostname */ | |
$SESS_DBNAME = DATABASE; /* database name */ | |
$SESS_DBUSER = DBUSER; /* database user */ | |
$SESS_DBPASS = DBPASS; /* database password */ | |
$SESS_DBH = ""; | |
$SESS_LIFE = get_cfg_var("session.gc_maxlifetime"); | |
class ThreadSafeSessionLock { | |
private $locking = ''; | |
function __construct($varname) { | |
$this->locking = $varname; | |
$_SESSION->acquire($varname); | |
} | |
function __destruct() { | |
$_SESSION->release($this->locking); | |
$this->locking = false; | |
} | |
} | |
class ThreadSafeSessionLock2 implements Countable, Iterator { | |
private $locking = ''; | |
private $c = 1; | |
function __construct($varname) { | |
$this->locking = $varname; | |
} | |
function count() { | |
return 1; | |
} | |
function rewind() { | |
$_SESSION->acquire($this->locking); | |
return; | |
} | |
function key() { | |
return 0; | |
} | |
function current() { | |
if ($this->c > 0) return 1; | |
return 0; | |
} | |
function next() { | |
if ($this->c > 0) $this->c--; | |
if ($this->c < 1) { | |
$_SESSION->release($this->locking); | |
$this->locking = false; | |
} | |
return $this->c; | |
} | |
function valid() { | |
return ($this->c > 0); | |
} | |
} | |
class ThreadSafeSessionArray implements Countable, ArrayAccess, Iterator { | |
private $_data; | |
private $_focus; | |
private $_curelm; | |
private $_alllocks = array(); | |
function __construct() { | |
global $SESS_DBHOST, $SESS_DBNAME, $SESS_DBUSER, $SESS_DBPASS, $SESS_DBH; | |
if (! $SESS_DBH = mysql_connect($SESS_DBHOST, $SESS_DBUSER, $SESS_DBPASS)) { | |
print "Can't connect to $SESS_DBHOST as $SESS_DBUSER"; | |
print "MySQL Error: ". mysql_error(); | |
die; | |
} | |
if (! mysql_select_db($SESS_DBNAME, $SESS_DBH)) { | |
print "Unable to select database $SESS_DBNAME"; | |
die; | |
} | |
# sends the headers and whatnot | |
session_start(); | |
} | |
function __destruct() { | |
# since only one advisory lock can be acquired at a time per connection | |
# with get_lock(), this is a no-op, but it's a nice abstraction if | |
# we're using a locking system that will allow multiple locks to be | |
# held at the same time | |
foreach ($this->_alllocks as $k=>$v) { | |
$this->release($k); | |
} | |
} | |
function lock_name($k) { | |
$v = sprintf('sess_%s_%s', session_id(), $k); | |
return $v; | |
} | |
function synchronize2($k) { | |
return new ThreadSafeSessionLock2($k); | |
} | |
function synchronize($k) { | |
$la = array(new ThreadSafeSessionLock($k), 0); | |
return $la; | |
} | |
function locked($k) { | |
return isset($this->_alllocks[$k]); | |
} | |
function acquire($k, $timeout = 60) { | |
global $SESS_DBH; | |
if (!isset($this->_alllocks[$k])) { | |
$this->_alllocks[$k] = 1; | |
$varlock = mysql_real_escape_string($this->lock_name($k), $SESS_DBH); | |
$timeout = intval($timeout); | |
$qid = mysql_query("select get_lock('$varlock', $timeout)"); | |
$ret = mysql_fetch_row($qid); | |
mysql_free_result($qid); | |
if (!isset($ret[0])) { | |
echo "unable to obtain session variable lock ($varlock)"; | |
die; | |
} | |
return true; | |
} | |
return false; | |
} | |
function release($k) { | |
global $SESS_DBH; | |
unset($this->_alllocks[$k]); | |
$varlock = mysql_real_escape_string($this->lock_name($k), $SESS_DBH); | |
$qid = mysql_query("select release_lock('$varlock')"); | |
mysql_free_result($qid); | |
return false; | |
} | |
function count() { | |
global $SESS_DBH; | |
$s = mysql_real_escape_string(session_id()); | |
$q = "select count(1) from sessvar where sesskey = '$s'"; | |
$q = mysql_query($q, $SESS_DBH); | |
$r = mysql_fetch_row($q); | |
mysql_free_result($q); | |
return $r[0]; | |
} | |
function rewind() { | |
$this->_focus = 0; | |
$this->_get_curelm(); | |
} | |
function _get_curelm() { | |
global $SESS_DBH; | |
$s = mysql_real_escape_string(session_id()); | |
$q = "select varkey, varval from sessvar where sesskey = '$s' limit ".$this->_focus.",1"; | |
$q = mysql_query($q, $SESS_DBH); | |
$r = mysql_fetch_row($q); | |
mysql_free_result($q); | |
if (is_array($r) && count($r) == 2) { | |
$this->_curelm = $r; | |
} else { | |
$this->_curelm = array(NULL, NULL); | |
} | |
} | |
function key() { | |
return $this->_curelm[0]; | |
} | |
function current() { | |
return unserialize($this->_curelm[1]); | |
} | |
function next() { | |
$this->_focus++; | |
$this->_get_curelm(); | |
} | |
function valid() { | |
if ($this->_curelm[0] === NULL) { | |
return false; | |
} | |
return true; | |
} | |
function offsetSet($k, $v) { | |
global $SESS_DBH; | |
$s = mysql_real_escape_string(session_id()); | |
$k = mysql_real_escape_string($k); | |
$v = mysql_real_escape_string(serialize($v)); | |
$q = "replace into sessvar (sesskey, varkey, varval) values ('$s', '$k', '$v')"; | |
mysql_query($q, $SESS_DBH); | |
} | |
function offsetGet($k) { | |
global $SESS_DBH; | |
$k = mysql_real_escape_string($k); | |
$s = mysql_real_escape_string(session_id()); | |
$q = "select varval from sessvar where sesskey = '$s' and varkey = '$k'"; | |
$q = mysql_query($q, $SESS_DBH); | |
$r = mysql_fetch_row($q); | |
mysql_free_result($q); | |
if (is_array($r) && count($r)) { | |
$r = array_shift($r); | |
$r = unserialize($r); | |
} else { | |
$r = NULL; | |
} | |
$x =& $r; | |
return $x; | |
} | |
function offsetUnset($k) { | |
global $SESS_DBH; | |
$s = mysql_real_escape_string(session_id()); | |
$k = mysql_real_escape_string($k); | |
$q = "delete from sessvar where sesskey = '$s' and varkey = '$k'"; | |
mysql_query($q, $SESS_DBH); | |
} | |
function offsetExists($k) { | |
global $SESS_DBH; | |
$s = mysql_real_escape_string(session_id()); | |
$k = mysql_real_escape_string($k); | |
$q = "select varval from sessvar where sesskey = '$s' and varkey = '$k'"; | |
$q = mysql_query($q, $SESS_DBH); | |
$r = mysql_fetch_row($q); | |
mysql_free_result($q); | |
if (is_array($r) && count($r)) { | |
if ($r[0] == 'N;') { # minor optimization, we know what NULL serializes to | |
return false; | |
} | |
return true; | |
} | |
return false; | |
} | |
# include these here to avoid some global function namespace polution | |
function sess_open($save_path, $session_name) { | |
return true; | |
} | |
function sess_close() { | |
return true; | |
} | |
function sess_read($id) { | |
return ""; # return an empty session, this doesn't matter anyway | |
} | |
function sess_write($id, $sess_data) { | |
return true; | |
} | |
function sess_destroy($id) { | |
return true; | |
} | |
function sess_gc($maxlifetime) { | |
return true; | |
} | |
} | |
session_set_save_handler(array('ThreadSafeSessionArray', "sess_open"), | |
array('ThreadSafeSessionArray', "sess_close"), | |
array('ThreadSafeSessionArray', "sess_read"), | |
array('ThreadSafeSessionArray', "sess_write"), | |
array('ThreadSafeSessionArray', "sess_destroy"), | |
array('ThreadSafeSessionArray', "sess_gc")); | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment