Skip to content

Commit

Permalink
Add Memcache session handler
Browse files Browse the repository at this point in the history
Summary: Fixes #3781
Fixes #4008
Closes #4011

Reviewed By: @fredemmott

Differential Revision: D1626210

Signature: t1:1626210:1417816002:746d11901c87dbb326d851f66952fd372a228f84
  • Loading branch information
MaideCa authored and hhvm-bot committed Dec 5, 2014
1 parent e2ffb44 commit 8911447
Show file tree
Hide file tree
Showing 20 changed files with 546 additions and 1 deletion.
4 changes: 4 additions & 0 deletions hphp/runtime/ext/memcache/ext_memcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ static bool HHVM_METHOD(Memcache, addserver, const String& host,

///////////////////////////////////////////////////////////////////////////////
const StaticString s_MEMCACHE_COMPRESSED("MEMCACHE_COMPRESSED");
const StaticString s_MEMCACHE_HAVE_SESSION("MEMCACHE_HAVE_SESSION");

class MemcacheExtension : public Extension {
public:
Expand Down Expand Up @@ -711,6 +712,9 @@ class MemcacheExtension : public Extension {
Native::registerConstant<KindOfInt64>(
s_MEMCACHE_COMPRESSED.get(), k_MEMCACHE_COMPRESSED
);
Native::registerConstant<KindOfBoolean>(
s_MEMCACHE_HAVE_SESSION.get(), true
);
HHVM_ME(Memcache, connect);
HHVM_ME(Memcache, add);
HHVM_ME(Memcache, set);
Expand Down
159 changes: 159 additions & 0 deletions hphp/runtime/ext/memcache/ext_memcache.php
Original file line number Diff line number Diff line change
Expand Up @@ -726,3 +726,162 @@ function memcache_add_server(Memcache $memcache,
$retry_interval, $status, $failure_callback,
$timeoutms);
}

class MemcacheSessionModule implements SessionHandlerInterface {

const UNIX_PREFIX = 'unix://';
const FILE_PREFIX = 'file://';

const SCHEME_FILE = 'file';
const SCHEME_TCP = 'tcp';

const ZERO_PORT = ':0';

private $memcache;

public function close() {
$this->memcache->close();
$this->memcache = null;
return true;
}

public function destroy($sessionId) {
// Memcache will return false if the session key doesn't exist
// so we return true no matter the result (matches redis module)
$this->memcache->delete($sessionId);
return true;
}

public function gc($maxLifetime) {
return true;
}

public function open($savePath, $name) {
$serverList = self::parseSavePath($savePath);
if (!$serverList) {
return false;
}

$this->memcache = new Memcache;
foreach ($serverList as $serverInfo) {
$this->memcache->addServer($serverInfo['host'],
$serverInfo['port'],
$serverInfo['persistent'],
$serverInfo['weight'],
$serverInfo['timeout'],
$serverInfo['retry_interval']);
}

return true;
}

public function read($sessionId) {
$data = $this->memcache->get($sessionId);
if (!$data) {
// Return an empty string instead of false for new sessions as
// false values cause sessions to fail to init
return '';
}
return $data;
}

public function write($sessionId, $data) {
return $this->memcache->set($sessionId,
$data,
MEMCACHE_COMPRESSED,
ini_get('session.gc_maxlifetime'));
}

private static function parseSavePath($savePath) {
$savePath = trim($savePath);
if (empty($savePath)) {
trigger_error("Failed to parse session.save_path (empty save_path)",
E_WARNING);
return false;
}

$serverList = explode(',', $savePath);

$return = array();
foreach ($serverList as $url) {
$url = trim($url);

// white-space only / empty keys are skipped
if (empty($url)) {
continue;
}

// Swap unix:// to file:// for parse_url usage like pecl does, if found
if (strncasecmp($url,
self::UNIX_PREFIX,
strlen(self::UNIX_PREFIX)) === 0) {
$url = self::FILE_PREFIX . substr($url, strlen(self::UNIX_PREFIX));
}

$parsedUrlData = parse_url($url);
if (!$parsedUrlData) {
trigger_error("Failed to parse session.save_path " .
"(unable to parse url, url was '" . $url . "')",
E_WARNING);
return false;
}

// Init optional values to default values
$serverInfo = array(
'persistent' => true,
'weight' => 1,
'timeout' => 1,
'retry_interval' => 15
);

switch ($parsedUrlData['scheme']) {
case self::SCHEME_FILE:
// Remove a zero port, if found
if (substr($parsedUrlData['path'], -2) === self::ZERO_PORT) {
$parsedUrlData['path'] = substr($parsedUrlData['path'], 0, -2);
}
// Return url back to a unix:// prefix instead of file://
$serverInfo['host'] = self::UNIX_PREFIX . $parsedUrlData['path'];
$serverInfo['port'] = 0;
break;
case self::SCHEME_TCP:
// We don't give TCP connections a tcp:// prefix
$serverInfo['host'] = $parsedUrlData['host'];
if (array_key_exists('port', $parsedUrlData)) {
$serverInfo['port'] = $parsedUrlData['port'];
} else {
$serverInfo['port'] = (int)ini_get('memcache.default_port');
}
break;
default:
trigger_error("Failed to parse session.save_path " .
"(unknown protocol, url was '" . $url . "')",
E_WARNING);
return false;
}

if (array_key_exists('query', $parsedUrlData)) {
$optionList = array();
parse_str($parsedUrlData['query'], $optionList);
if (count($optionList) > 0) {
foreach ($optionList as $key => $value) {
switch ($key) {
case 'persistent':
$serverInfo[$key] = (bool)$value;
break;
case 'weight':
case 'timeout':
case 'retry_interval':
$serverInfo[$key] = (int)$value;
break;
}
}
}
}

$return[] = $serverInfo;
}

return $return;
}
}
21 changes: 20 additions & 1 deletion hphp/runtime/ext/memcached/ext_memcached.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,13 @@ const StaticString
s_value("value"),
s_cas("cas");

// INI settings
struct MEMCACHEDGlobals final {
std::string sess_prefix;
};
static __thread MEMCACHEDGlobals* s_memcached_globals;
#define MEMCACHEDG(name) s_memcached_globals->name

namespace {
class MemcachedResultWrapper {
public:
Expand Down Expand Up @@ -1275,6 +1282,19 @@ const StaticString s_SERIALIZER_PHP("SERIALIZER_PHP");
class MemcachedExtension : public Extension {
public:
MemcachedExtension() : Extension("memcached", "2.2.0b1") {}
void threadInit() override {
if (s_memcached_globals) {
return;
}
s_memcached_globals = new MEMCACHEDGlobals;
IniSetting::Bind(this, IniSetting::PHP_INI_ALL,
"memcached.sess_prefix", &MEMCACHEDG(sess_prefix));
}

void threadShutdown() override {
delete s_memcached_globals;
s_memcached_globals = nullptr;
}

virtual void moduleInit() {
HHVM_ME(Memcached, __construct);
Expand Down Expand Up @@ -1537,7 +1557,6 @@ class MemcachedExtension : public Extension {
);



loadSystemlib();
}
} s_memcached_extension;
Expand Down
127 changes: 127 additions & 0 deletions hphp/runtime/ext/memcached/ext_memcached.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/
<<__NativeData("MemcachedData")>>
class Memcached {
// Signifies we have provide a session handler
const HAVE_SESSION = true;
/**
* Create a Memcached instance
*
Expand Down Expand Up @@ -666,3 +668,128 @@ public function setOptions(array<int, mixed> $options): bool {

class MemcachedException {
}


class MemcachedSessionModule implements SessionHandlerInterface {

const CONFIG_PERSISTENT = 'PERSISTENT=';

private $memcached;
private $persistentKey;

public function close() {
$this->memcached = null;
$this->persistentKey = null;
return true;
}

public function destroy($sessionId) {
$this->memcached->delete($sessionId);
return true;
}

public function gc($maxLifetime) {
return true;
}

public function open($savePath, $name) {
$serverList = self::parseSavePath($savePath);
if (!$serverList) {
return false;
}

$keyPrefix = trim((string)ini_get('memcached.sess_prefix'));
// Validate non-empty values (empty values are accepted)
if (strlen($keyPrefix) == 0 ||
strlen($keyPrefix) > 218 ||
!ctype_graph($keyPrefix)) {
trigger_error("Bad memcached key prefix in memcached.sess_prefix",
E_WARNING);
return false;
}

$memcached = new Memcached($this->persistentKey);
foreach ($serverList as $serverInfo) {
$memcached->addServer($serverInfo['host'], $serverInfo['port']);
}

if (!$memcached->setOption(Memcached::OPT_PREFIX_KEY,
$keyPrefix)) {
// setOption already throws a warning for bad values
return false;
}

$this->memcached = $memcached;

return true;
}

public function read($sessionId) {
$data = $this->memcached->get($sessionId);
if (!$data) {
// Return an empty string instead of false for new sessions as
// false values cause sessions to fail to init
return '';
}
return $data;
}

public function write($sessionId, $data) {
return $this->memcached->set($sessionId,
$data,
ini_get('session.gc_maxlifetime'));
}

private static function parseSavePath($savePath) {
$savePath = trim($savePath);
if (empty($savePath)) {
trigger_error("Failed to initialize memcached session storage",
E_WARNING);
return false;
}

// Handle persistent key at front of save_path
if (strncasecmp($savePath,
self::CONFIG_PERSISTENT,
strlen(self::CONFIG_PERSISTENT)) === 0) {
$savePath = substr($savePath, strlen(self::CONFIG_PERSISTENT) - 1);
if (empty($savePath)) {
trigger_error("Invalid persistent id for session storage",
E_WARNING);
return false;
}

$explode = explode(' ', $savePath, 2);
if (count($explode) !== 2) {
trigger_error("Invalid persistent id for session storage",
E_WARNING);
return false;
}

$this->persistentKey = $explode[0];
$savePath = $explode[1];
}

$serverList = explode(',', $savePath);

$return = array();
foreach ($serverList as $url) {
$url = trim($url);

// Skip empty servers
if (empty($url)) {
continue;
}
$explode = explode(':', $url, 2);

$serverInfo = array('host' => $explode[0]);

// When port is missing (e.g. unix socket) use port of 0
$serverInfo['port'] = (isset($explode[1])) ? (int)$explode[1] : 0;

$return[] = $serverInfo;
}

return $return;
}
}
12 changes: 12 additions & 0 deletions hphp/runtime/ext/session/ext_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,18 @@ static class RedisSessionModule : public SystemlibSessionModule {
SystemlibSessionModule("redis", "RedisSessionModule") { }
} s_redis_session_module;

static class MemcacheSessionModule : public SystemlibSessionModule {
public:
MemcacheSessionModule() :
SystemlibSessionModule("memcache", "MemcacheSessionModule") { }
} s_memcache_session_module;

static class MemcachedSessionModule : public SystemlibSessionModule {
public:
MemcachedSessionModule() :
SystemlibSessionModule("memcached", "MemcachedSessionModule") { }
} s_memcached_session_module;

//////////////////////////////////////////////////////////////////////////////
// FileSessionModule

Expand Down

0 comments on commit 8911447

Please sign in to comment.