From 43f6cdba6c22290c69e795168e326fe1aa8743f6 Mon Sep 17 00:00:00 2001 From: Andrey Andreev Date: Wed, 27 Aug 2014 22:26:40 +0300 Subject: feature/session (#3073): Add Redis session driver Seems like I forgot to 'git add' it in previous commit. --- .../Session/drivers/Session_redis_driver.php | 311 +++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 system/libraries/Session/drivers/Session_redis_driver.php (limited to 'system/libraries/Session') diff --git a/system/libraries/Session/drivers/Session_redis_driver.php b/system/libraries/Session/drivers/Session_redis_driver.php new file mode 100644 index 000000000..6c013a657 --- /dev/null +++ b/system/libraries/Session/drivers/Session_redis_driver.php @@ -0,0 +1,311 @@ +_save_path)) + { + log_message('error', 'Session: No Redis save path configured.'); + } + elseif (preg_match('#(?:tcp://)?([^:]+)(?:\:(\d+))?(\?.+)?#', $this->_save_path, $matches)) + { + $this->_save_path = array( + 'host' => $matches[1], + 'port' => empty($matches[2]) ? NULL : $matches[2], + 'password' => preg_match('#auth=([^\s&]+)#', $matches[3], $match) ? $match[1] : NULL, + 'database' => preg_match('#database=(\d+)#', $matches[3], $match) ? (int) $match[1] : NULL, + 'timeout' => preg_match('#timeout=(\d+\.\d+)#', $matches[3], $match) ? (float) $match[1] : NULL + ); + + preg_match('#prefix=([^\s&]+)#', $matches[3], $match) && $this->_key_prefix = $match[1]; + } + else + { + log_message('error', 'Session: Invalid Redis save path format: '.$this->_save_path); + } + + if ($this->_match_ip === TRUE) + { + $this->_key_prefix .= $_SERVER['REMOTE_ADDR'].':'; + } + } + + // ------------------------------------------------------------------------ + + public function open($save_path, $name) + { + if (empty($this->_save_path)) + { + return FALSE; + } + + $redis = new Redis(); + if ( ! $redis->connect($this->_save_path['host'], $this->_save_path['port'], $this->_save_path['timeout'])) + { + log_message('error', 'Session: Unable to connect to Redis with the configured settings.'); + } + elseif (isset($this->_save_path['password']) && ! $redis->auth($this->_save_path['password'])) + { + log_message('error', 'Session: Unable to authenticate to Redis instance.'); + } + elseif (isset($this->_save_path['database']) && ! $redis->select($this->_save_path['database'])) + { + log_message('error', 'Session: Unable to select Redis database with index '.$this->_save_path['database']); + } + else + { + $this->_redis = $redis; + return TRUE; + } + + return FALSE; + } + + // ------------------------------------------------------------------------ + + public function read($session_id) + { + if (isset($this->_redis) && $this->_get_lock($session_id)) + { + $session_data = (string) $this->_redis->get($this->_key_prefix.$session_id); + $this->_fingerprint = md5($session_data); + return $session_data; + } + + return FALSE; + } + + public function write($session_id, $session_data) + { + if (isset($this->_redis, $this->_lock_key)) + { + $this->_redis->setTimeout($this->_lock_key, 10, time()); + if ($this->_fingerprint !== ($fingerprint = md5($session_data))) + { + if ($this->_redis->set($this->_key_prefix.$session_id, $session_data, $this->_expiration)) + { + $this->_fingerprint = $fingerprint; + return TRUE; + } + + return FALSE; + } + + return $this->_redis->setTimeout($this->_key_prefix.$session_id, $this->_expiration); + } + + return FALSE; + } + + // ------------------------------------------------------------------------ + + public function close() + { + if (isset($this->_redis)) + { + try { + if ($this->_redis->ping() === '+PONG') + { + isset($this->_lock_key) && $this->_redis->delete($this->_lock_key); + if ( ! $this->_redis->close()) + { + return FALSE; + } + } + } + catch (RedisException $e) + { + log_message('error', 'Session: Got RedisException on close(): '.$e->getMessage()); + } + + $this->_redis = NULL; + return TRUE; + } + + return FALSE; + } + + // ------------------------------------------------------------------------ + + public function destroy($session_id) + { + if (isset($this->_redis, $this->_lock_key)) + { + if ($this->_redis->delete($this->_key_prefix.$session_id) !== 1) + { + log_message('debug', 'Session: Redis::delete() expected to return 1, got '.var_export($result, TRUE).' instead.'); + } + + return ($this->_cookie_destroy() && $this->close()); + } + + return $this->close(); + } + + // ------------------------------------------------------------------------ + + public function gc($maxlifetime) + { + // TODO: keys()/getKeys() is said to be performance-intensive, + // although it supports patterns (*, [charlist] at the very least). + // scan() seems to be recommended, but requires redis 2.8 + // Not sure if we need any of these though, as we set keys with expire times + return TRUE; + } + + // ------------------------------------------------------------------------ + + protected function _get_lock($session_id) + { + if (isset($this->_lock_key)) + { + return $this->_redis->setTimeout($this->_lock_key, 5); + } + + $lock_key = $this->_key_prefix.$session_id.':lock'; + if (($ttl = $this->_redis->ttl($lock_key)) < 1) + { + if ( ! $this->_redis->setex($lock_key, 5, time())) + { + log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id); + return FALSE; + } + + $this->_lock_key = $lock_key; + + if ($ttl === -1) + { + log_message('debug', 'Session: Lock for '.$this->_key_prefix.$session_id.' had no TTL, overriding.'); + } + + $this->_lock = TRUE; + return TRUE; + } + + // Another process has the lock, we'll try to wait for it to free itself ... + $attempt = 0; + while ($attempt++ < 5) + { + usleep(($ttl * 1000000) - 20000); + if (($ttl = $this->_redis->ttl($lock_key)) > 0) + { + continue; + } + + if ( ! $this->_redis->setex($lock_key, 5, time())) + { + log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id); + return FALSE; + } + + $this->_lock_key = $lock_key; + break; + } + + if ($attempt === 5) + { + log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 5 attempts, aborting.'); + return FALSE; + } + + $this->_lock = TRUE; + return TRUE; + } + + // ------------------------------------------------------------------------ + + protected function _release_lock() + { + if (isset($this->_redis, $this->_lock_key) && $this->_lock) + { + if ( ! $this->_redis->delete($this->_lock_key)) + { + log_message('error', 'Session: Error while trying to free lock for '.$this->_key_prefix.$session_id); + return FALSE; + } + + $this->_lock_key = NULL; + $this->_lock = FALSE; + } + + return TRUE; + } + +} + +/* End of file Session_redis_driver.php */ +/* Location: ./system/libraries/Session/drivers/Session_redis_driver.php */ \ No newline at end of file -- cgit v1.2.3-24-g4f1b