From b0ea37ccfddbb2c2e794bae6b0c6b787627b612a Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Mon, 5 Aug 2013 15:18:43 -0700 Subject: [PATCH] Implement Redis session module Translates session events into Redis commands Differential Revision: D872896 --- hphp/NEWS | 1 + hphp/runtime/ext/ext_session.cpp | 9 ++ hphp/system/php.txt | 1 + hphp/system/php/redis/RedisSessionModule.php | 162 +++++++++++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 hphp/system/php/redis/RedisSessionModule.php diff --git a/hphp/NEWS b/hphp/NEWS index 909ea841d03..446a5939284 100644 --- a/hphp/NEWS +++ b/hphp/NEWS @@ -6,6 +6,7 @@ - Implement php_strip_whitespace() - Support for timeouts in cli mode - Closure allocation optimizations (~2x faster to allocate a closure) + - Implement Redis session handler "Tamale" 22-Jul-2013 - Optimize vector-shaped Arrays (arrays with keys in range 0..size-1) diff --git a/hphp/runtime/ext/ext_session.cpp b/hphp/runtime/ext/ext_session.cpp index 1030d37d9d2..8d53c970817 100644 --- a/hphp/runtime/ext/ext_session.cpp +++ b/hphp/runtime/ext/ext_session.cpp @@ -532,6 +532,15 @@ bool SystemlibSessionModule::gc(int maxlifetime, int *nrdels) { } ////////////////////////////////////////////////////////////////////////////// +// SystemlibSessionModule implementations + +static class RedisSessionModule : public SystemlibSessionModule { + public: + RedisSessionModule() : + SystemlibSessionModule("redis", "RedisSessionModule") { } +} s_redis_session_module; + +////////////////////////////////////////////////////////////////////////////// // FileSessionModule class FileSessionData { diff --git a/hphp/system/php.txt b/hphp/system/php.txt index c5de98ce7e6..ec4771d2a66 100644 --- a/hphp/system/php.txt +++ b/hphp/system/php.txt @@ -68,6 +68,7 @@ hphp/system/php/lang/ErrorException.php hphp/system/php/misc/php_strip_whitespace.php hphp/system/php/pdo/PDOException.php hphp/system/php/redis/Redis.php +hphp/system/php/redis/RedisSessionModule.php hphp/system/php/reflection/reflection.php hphp/system/php/sessions/session_register_shutdown.php hphp/system/php/soap/SoapFault.php diff --git a/hphp/system/php/redis/RedisSessionModule.php b/hphp/system/php/redis/RedisSessionModule.php new file mode 100644 index 00000000000..5f2e34b2a9d --- /dev/null +++ b/hphp/system/php/redis/RedisSessionModule.php @@ -0,0 +1,162 @@ +weight = 0; + $this->paths = []; + + $paths = preg_split('/[\s,]+/', $save_path, -1, PREG_SPLIT_NO_EMPTY); + foreach ($paths as $path) { + if (!strncmp($path, "unix:", 5)) { + $path = 'file:' . substr($path, 5); + } + $url = parse_url($path); + $args = [ + 'weight' => 1, + 'timeout' => 86400, + 'persistent' => 0, + 'prefix' => 'PHPREDIS_SESSION:', + 'auth' => '', + 'database' => 0 + ]; + if (isset($url['query'])) { + parse_str($url['query'], $query); + foreach ($args as $key => &$val) { + if (!isset($query[$key])) continue; + if (is_string($val)) { + $val = $query[$key]; + } else { + $val = (int)$query[$key]; + } + } + } + + if ($args['weight'] === 0) { + // Disabled target, skip + continue; + } + if ($args['weight'] < 1) { + error_log("Invalid weight specified for session.save_path(redis), ". + "assuming 1", E_USER_WARNING); + $args['weight'] = 1; + } + + if ($url['scheme'] == 'file') { + $args['host'] = "unix://{$url['path']}"; + $args['port'] = null; + } else { + $args['host'] = $url['host']; + $args['port'] = !empty($url['port']) + ? $url['port'] : Redis::DEFAULT_PORT; + } + + $args['connection'] = null; + + $this->weight += $args['weight']; + $this->paths[] = $args; + } + + return true; + } + + protected function &selectWeight($key) { + if (count($this->paths) === 1) { + return $this->paths[0]; + } + $pos = abs(unpack("I", $key)); + $pos %= $this->weight; + foreach ($this->paths as $path) { + $pos -= $path['weight']; + if ($pos <= 0) { + return $path; + } + } + + throw new RedisException("Ran out of weights selecting redis host ". + "for session: $key"); + } + + protected function connect($key) { + $r =& $this->selectWeight($key); + if (!empty($r['connection'])) { + return $r['connection']; + } + + $redis = new Redis; + $func = ($r['persistent']) ? 'pconnect' : 'connect'; + if (!$redis->{$func}($r['host'], $r['port'], $r['timeout'])) { + return false; + } + if (($r['auth'] !== '') && + !$redis->auth($r['auth'])) { + return false; + } + if (($r['database'] !== 0) && + !$redis->select($r['database'])) { + return false; + } + if (!$redis->setOption(Redis::OPT_PREFIX, $r['prefix'])) { + return false; + } + + $r['connection'] = $redis; + return $redis; + } + + public function close() { + foreach ($this->paths as &$path) { + $path['connection'] = null; + } + + return true; + } + + public function read($key) { + $redis = $this->connect($key); + return (string)$redis->get($key); + } + + public function write($key, $value) { + $redis = $this->connect($key); + return $redis->set($key, $value, ini_get('session.gc_maxlifetime')); + } + + public function destroy($key) { + $redis = $this->connect($key); + return $redis->del($key); + } + + public function gc($maxlifetime) { + // Handled by $redis->set(..., ini_get('session.gc_maxlifetime')) + return 0; + } +} -- 2.11.4.GIT