Commit 508a65c3 authored by Jeremy's avatar Jeremy

Merge in effort to port memcache-sessions to D7. Still needs work.

Patch thanks to chx.
parent d5a061c8
......@@ -31,12 +31,14 @@ $_memcache_statistics = array('get' => array(), 'set' => array(), 'hit' => array
* @return bool
*/
function dmemcache_set($key, $value, $exp = 0, $bin = 'cache', $mc = NULL) {
global $_memcache_statistics;
$_memcache_statistics['set'][] = $key;
$_memcache_statistics['bins'][] = $bin;
if ($mc || ($mc = dmemcache_object($bin))) {
$full_key = dmemcache_key($key, $bin);
if (!$mc->set($full_key, $value, MEMCACHE_COMPRESSED, $exp)) {
error_log("setting: ". $full_key . gmdate('c') . "\n", 3, '/tmp/log');
if (!memcache_set($mc, $full_key, $value, MEMCACHE_COMPRESSED, $exp)) {
return FALSE;
}
else {
......@@ -60,18 +62,18 @@ function dmemcache_get($key, $bin = 'cache', $mc = NULL) {
$_memcache_statistics['bins'][] = $bin;
if ($mc || ($mc = dmemcache_object($bin))) {
$full_key = dmemcache_key($key, $bin);
$result = $mc->get($full_key);
$result = memcache_get($mc, $full_key);
if ($result) {
// We check $result->expire to see if the object has expired. If so, we
// try and grab a lock. If we get the lock, we return FALSE instead of
// the cached object which should cause it to be rebuilt. If we do not
// get the lock, we return the cached object. The goal here is to avoid
// cache stampedes.
// cache stampedes.
// By default the cache stampede semaphore is held for 15 seconds. This
// can be adjusted by setting the memcache_stampede_semaphore variable.
// TODO: Can we log when a sempahore expires versus being intentionally
// freed to track when this is happening?
if (isset($result->expire) && $result->expire !== CACHE_PERMANENT && $result->expire <= time() && $mc->add($full_key .'_semaphore', '', FALSE, variable_get('memcache_stampede_semaphore', 15))) {
if (isset($result->expire) && $result->expire !== CACHE_PERMANENT && $result->expire <= time() && memcache_add($mc, $full_key .'_semaphore', '', FALSE, variable_get('memcache_stampede_semaphore', 15))) {
$result = FALSE;
}
else {
......@@ -93,7 +95,7 @@ function dmemcache_get($key, $bin = 'cache', $mc = NULL) {
function dmemcache_delete($key, $bin = 'cache', $mc = NULL) {
if ($mc || ($mc = dmemcache_object($bin))) {
$full_key = dmemcache_key($key, $bin);
return $mc->delete($full_key);
return memcache_delete($mc, $full_key);
}
return FALSE;
}
......@@ -110,7 +112,7 @@ function dmemcache_delete($key, $bin = 'cache', $mc = NULL) {
*/
function dmemcache_flush($bin = 'cache', $mc = NULL) {
if ($mc || ($mc = dmemcache_object($bin))) {
return $mc->flush();
return memcache_flush($mc);
}
}
......@@ -126,13 +128,13 @@ function dmemcache_stats($bin = 'cache', $type = '') {
if ($mc = dmemcache_object($bin)) {
// The PHP Memcache extension 3.x version throws an error if the stats
// type is NULL or not in {reset, malloc, slabs, cachedump, items, sizes}.
// If $type is 'default', then no parameter should be passed to the
// If $type is 'default', then no parameter should be passed to the
// Memcache memcache_get_extended_stats() function.
if ($type == 'default' || $type == '') {
return $mc->getExtendedStats();
return memcache_getExtendedStats($mc);
}
else {
return $mc->getExtendedStats($type);
return memcache_getExtendedStats($mc, $type);
}
}
}
......@@ -156,7 +158,7 @@ function dmemcache_object($bin = NULL, $flush = FALSE) {
if ($flush) {
foreach ($memcacheCache as $cluster) {
$cluster->close();
memcache_close($cluster);
}
$memcacheCache = array();
}
......@@ -195,7 +197,7 @@ function dmemcache_object($bin = NULL, $flush = FALSE) {
// This is a server that belongs to this cluster.
if (!$init) {
// First server gets the connect.
if (@$memcache->connect($host, $port)) {
if ($memcache = @memcache_connect($host, $port)) {
// Only set init to true if a connection was made.
$init = TRUE;
$memcache_online = TRUE;
......@@ -206,7 +208,7 @@ function dmemcache_object($bin = NULL, $flush = FALSE) {
}
else {
// Subsequent servers gett addServer.
@$memcache->addServer($host, $port);
memcache_addServer($memcache, $host, $port);
}
}
}
......
This diff is collapsed.
......@@ -222,7 +222,7 @@ class MemCacheDrupal implements DrupalCacheInterface {
}
}
}
function isEmpty() {
// We do not know so err on the safe side?
return FALSE;
......
......@@ -2,3 +2,7 @@
name = Memcache
description = High performance integration with memcache.
package = Caching
core = 7.x
files[] = memcache.inc
files[] = memcache.module
files[] = memcache.test
......@@ -318,3 +318,225 @@ class MemCacheClearCase extends MemcacheTestCase {
t('All cache entries removed when the array exceeded the cache clear threshold.'));
}
}
/**
* @file
* Provides SimpleTests for core session handling functionality.
*/
class MemcacheSessionTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Session tests',
'description' => 'Memcache session handling tests.',
'group' => 'Memcache'
);
}
function setUp() {
parent::setUp('session_test');
}
/**
* Tests for drupal_save_session() and drupal_session_regenerate().
*/
function testSessionSaveRegenerate() {
$this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE (inside of testing framework) when initially called with no arguments.'), t('Session'));
$this->assertFalse(drupal_save_session(FALSE), t('drupal_save_session() correctly returns FALSE when called with FALSE.'), t('Session'));
$this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE when saving has been disabled.'), t('Session'));
$this->assertTrue(drupal_save_session(TRUE), t('drupal_save_session() correctly returns TRUE when called with TRUE.'), t('Session'));
$this->assertTrue(drupal_save_session(), t('drupal_save_session() correctly returns TRUE when saving has been enabled.'), t('Session'));
// Test session hardening code from SA-2008-044.
$user = $this->drupalCreateUser(array('access content'));
// Enable sessions.
$this->sessionReset($user->uid);
// Make sure the session cookie is set as HttpOnly.
$this->drupalLogin($user);
$this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), t('Session cookie is set as HttpOnly.'));
$this->drupalLogout();
// Verify that the session is regenerated if a module calls exit
// in hook_user_login().
user_save($user, array('name' => 'session_test_user'));
$user->name = 'session_test_user';
$this->drupalGet('session-test/id');
$matches = array();
preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches);
$this->assertTrue(!empty($matches[1]) , t('Found session ID before logging in.'));
$original_session = $matches[1];
// We cannot use $this->drupalLogin($user); because we exit in
// session_test_user_login() which breaks a normal assertion.
$edit = array(
'name' => $user->name,
'pass' => $user->pass_raw
);
$this->drupalPost('user', $edit, t('Log in'));
$this->drupalGet('user');
$pass = $this->assertText($user->name, t('Found name: %name', array('%name' => $user->name)), t('User login'));
$this->_logged_in = $pass;
$this->drupalGet('session-test/id');
$matches = array();
preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches);
$this->assertTrue(!empty($matches[1]) , t('Found session ID after logging in.'));
$this->assertTrue($matches[1] != $original_session, t('Session ID changed after login.'));
}
/**
* Test data persistence via the session_test module callbacks. Also tests
* drupal_session_count() since session data is already generated here.
*/
function testDataPersistence() {
$user = $this->drupalCreateUser(array('access content'));
// Enable sessions.
$this->sessionReset($user->uid);
$this->drupalLogin($user);
$value_1 = $this->randomName();
$this->drupalGet('session-test/set/' . $value_1);
$this->assertText($value_1, t('The session value was stored.'), t('Session'));
$this->drupalGet('session-test/get');
$this->assertText($value_1, t('Session correctly returned the stored data for an authenticated user.'), t('Session'));
// Attempt to write over val_1. If drupal_save_session(FALSE) is working.
// properly, val_1 will still be set.
$value_2 = $this->randomName();
$this->drupalGet('session-test/no-set/' . $value_2);
$this->assertText($value_2, t('The session value was correctly passed to session-test/no-set.'), t('Session'));
$this->drupalGet('session-test/get');
$this->assertText($value_1, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session'));
// Switch browser cookie to anonymous user, then back to user 1.
$this->sessionReset();
$this->sessionReset($user->uid);
$this->assertText($value_1, t('Session data persists through browser close.'), t('Session'));
// Logout the user and make sure the stored value no longer persists.
$this->drupalLogout();
$this->sessionReset();
$this->drupalGet('session-test/get');
$this->assertNoText($value_1, t("After logout, previous user's session data is not available."), t('Session'));
// Now try to store some data as an anonymous user.
$value_3 = $this->randomName();
$this->drupalGet('session-test/set/' . $value_3);
$this->assertText($value_3, t('Session data stored for anonymous user.'), t('Session'));
$this->drupalGet('session-test/get');
$this->assertText($value_3, t('Session correctly returned the stored data for an anonymous user.'), t('Session'));
// Try to store data when drupal_save_session(FALSE).
$value_4 = $this->randomName();
$this->drupalGet('session-test/no-set/' . $value_4);
$this->assertText($value_4, t('The session value was correctly passed to session-test/no-set.'), t('Session'));
$this->drupalGet('session-test/get');
$this->assertText($value_3, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session'));
// Login, the data should persist.
$this->drupalLogin($user);
$this->sessionReset($user->uid);
$this->drupalGet('session-test/get');
$this->assertNoText($value_1, t('Session has persisted for an authenticated user after logging out and then back in.'), t('Session'));
// Change session and create another user.
$user2 = $this->drupalCreateUser(array('access content'));
$this->sessionReset($user2->uid);
$this->drupalLogin($user2);
}
/**
* Test that empty anonymous sessions are destroyed.
*/
function testEmptyAnonymousSession() {
// Verify that no session is automatically created for anonymous user.
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(TRUE);
// The same behavior is expected when caching is enabled.
variable_set('cache', CACHE_NORMAL);
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(TRUE);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Page was not cached.'));
// Start a new session by setting a message.
$this->drupalGet('session-test/set-message');
$this->assertSessionCookie(TRUE);
$this->assertTrue($this->drupalGetHeader('Set-Cookie'), t('New session was started.'));
// Display the message, during the same request the session is destroyed
// and the session cookie is unset.
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(FALSE);
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Caching was bypassed.'));
$this->assertText(t('This is a dummy message.'), t('Message was displayed.'));
$this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.'));
// Verify that session was destroyed.
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(TRUE);
$this->assertNoText(t('This is a dummy message.'), t('Message was not cached.'));
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.'));
$this->assertFalse($this->drupalGetHeader('Set-Cookie'), t('New session was not started.'));
// Verify that no session is created if drupal_save_session(FALSE) is called.
$this->drupalGet('session-test/set-message-but-dont-save');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(TRUE);
// Verify that no message is displayed.
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(TRUE);
$this->assertNoText(t('This is a dummy message.'), t('The message was not saved.'));
}
/**
* Reset the cookie file so that it refers to the specified user.
*
* @param $uid User id to set as the active session.
*/
function sessionReset($uid = 0) {
// Close the internal browser.
$this->curlClose();
$this->loggedInUser = FALSE;
// Change cookie file for user.
$this->cookieFile = file_directory_path('temporary') . '/cookie.' . $uid . '.txt';
$this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile;
$this->additionalCurlOptions[CURLOPT_COOKIESESSION] = TRUE;
$this->drupalGet('session-test/get');
$this->assertResponse(200, t('Session test module is correctly enabled.'), t('Session'));
}
/**
* Assert whether the SimpleTest browser sent a session cookie.
*/
function assertSessionCookie($sent) {
if ($sent) {
$this->assertNotNull($this->session_id, t('Session cookie was sent.'));
}
else {
$this->assertNull($this->session_id, t('Session cookie was not sent.'));
}
}
/**
* Assert whether $_SESSION is empty at the beginning of the request.
*/
function assertSessionEmpty($empty) {
if ($empty) {
$this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '1', t('Session was empty.'));
}
else {
$this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', t('Session was not empty.'));
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment