Skip to content

Commit babc2e5

Browse files
Adding support for Redis Cluster & Username & TlsOptions (#62)
* Adding support for Redis Cluster & Username & TlsOptions * Updated for change in colinmollenhour/credis#191 * Updating logging connection to support cluster
1 parent 468bc02 commit babc2e5

File tree

4 files changed

+243
-36
lines changed

4 files changed

+243
-36
lines changed

src/Cm/RedisSession/Handler.php

Lines changed: 99 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,12 @@
5050
* - Detects inactive waiting processes to prevent false-positives in concurrency throttling.
5151
*/
5252

53+
use Cm\RedisSession\Handler\ClusterConfigInterface;
5354
use Cm\RedisSession\Handler\ConfigInterface;
5455
use Cm\RedisSession\Handler\ConfigSentinelPasswordInterface;
5556
use Cm\RedisSession\Handler\LoggerInterface;
57+
use Cm\RedisSession\Handler\TlsOptionsConfigInterface;
58+
use Cm\RedisSession\Handler\UsernameConfigInterface;
5659

5760
class Handler implements \SessionHandlerInterface
5861
{
@@ -162,10 +165,20 @@ class Handler implements \SessionHandlerInterface
162165
const DEFAULT_LIFETIME = 60;
163166

164167
/**
165-
* @var \Credis_Client
168+
* @var \Credis_Client|\Credis_Cluster
166169
*/
167170
protected $_redis;
168171

172+
/**
173+
* @var bool
174+
*/
175+
protected readonly bool $_usePipeline;
176+
177+
/**
178+
* @var bool
179+
*/
180+
protected readonly bool $_useCluster;
181+
169182
/**
170183
* @var int
171184
*/
@@ -278,10 +291,12 @@ public function __construct(ConfigInterface $config, LoggerInterface $logger, $r
278291
$host = $this->config->getHost() ?: self::DEFAULT_HOST;
279292
$port = $this->config->getPort() ?: self::DEFAULT_PORT;
280293
$pass = $this->config->getPassword() ?: null;
294+
$username = $this->config instanceof UsernameConfigInterface ? $this->config->getUsername() : null;
281295
$timeout = $this->config->getTimeout() ?: self::DEFAULT_TIMEOUT;
282296
$retries = $this->config->getRetries() ?: self::DEFAULT_RETRIES;
283297
$persistent = $this->config->getPersistentIdentifier() ?: '';
284298
$this->_dbNum = $this->config->getDatabase() ?: self::DEFAULT_DATABASE;
299+
$tlsOptions = $this->config instanceof TlsOptionsConfigInterface ? $this->config->getTlsOptions() : null;
285300

286301
// General config
287302
$this->_readOnly = $readOnly;
@@ -307,6 +322,8 @@ public function __construct(ConfigInterface $config, LoggerInterface $logger, $r
307322

308323
// Connect and authenticate
309324
if ($sentinelServers && $sentinelMaster) {
325+
$this->_usePipeline = true;
326+
$this->_useCluster = false;
310327
$servers = preg_split('/\s*,\s*/', trim($sentinelServers), -1, PREG_SPLIT_NO_EMPTY);
311328
$sentinel = NULL;
312329
$exception = NULL;
@@ -322,35 +339,35 @@ public function __construct(ConfigInterface $config, LoggerInterface $logger, $r
322339
} catch (\CredisException $e) {
323340
// Prevent throwing exception if Sentinel has no password set (error messages are different between redis 5 and redis 6)
324341
if ($e->getCode() !== 0 || (
325-
strpos($e->getMessage(), 'ERR Client sent AUTH, but no password is set') === false &&
342+
strpos($e->getMessage(), 'ERR Client sent AUTH, but no password is set') === false &&
326343
strpos($e->getMessage(), 'ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct?') === false)
327344
) {
328345
throw $e;
329346
}
330347
}
331348
}
332-
349+
333350
$sentinel = new \Credis_Sentinel($sentinelClient);
334351
$sentinel
335352
->setClientTimeout($timeout)
336353
->setClientPersistent($persistent);
337354
$redisMaster = $sentinel->getMasterClient($sentinelMaster);
338-
if ($pass) $redisMaster->auth($pass);
355+
if ($pass) $redisMaster->auth($pass, $username);
339356

340357
// Verify connected server is actually master as per Sentinel client spec
341358
if ($sentinelVerifyMaster) {
342359
$roleData = $redisMaster->role();
343360
if ( ! $roleData || $roleData[0] != 'master') {
344361
usleep(100000); // Sleep 100ms and try again
345362
$redisMaster = $sentinel->getMasterClient($sentinelMaster);
346-
if ($pass) $redisMaster->auth($pass);
363+
if ($pass) $redisMaster->auth($pass, $username);
347364
$roleData = $redisMaster->role();
348365
if ( ! $roleData || $roleData[0] != 'master') {
349366
throw new \Exception('Unable to determine master redis server.');
350367
}
351368
}
352369
}
353-
if ($this->_dbNum || $persistent) $redisMaster->select(0);
370+
if (($this->_dbNum || $persistent) && !$this->_useCluster) $redisMaster->select(0);
354371

355372
$this->_redis = $redisMaster;
356373
break 2;
@@ -366,24 +383,61 @@ public function __construct(ConfigInterface $config, LoggerInterface $logger, $r
366383
}
367384
}
368385
else {
369-
$this->_redis = new \Credis_Client($host, $port, $timeout, $persistent, 0, $pass);
386+
if (($config instanceof ClusterConfigInterface) && ($config->isCluster())) {
387+
$this->_redis = new \Credis_Cluster(
388+
$config->getClusterName(),
389+
$config->getClusterSeeds(),
390+
$timeout,
391+
0,
392+
$config->getClusterUsePersistentConnection(),
393+
$pass,
394+
$username,
395+
$tlsOptions,
396+
);
397+
$this->_usePipeline = false;
398+
$this->_useCluster = true;
399+
} else {
400+
$this->_redis = new \Credis_Client(
401+
$host,
402+
$port,
403+
$timeout,
404+
$persistent,
405+
0,
406+
$pass,
407+
$username,
408+
$tlsOptions
409+
);
410+
$this->_usePipeline = true;
411+
$this->_useCluster = false;
412+
}
370413
$this->_redis->setMaxConnectRetries($retries);
371414
if ($this->hasConnection() == false) {
372415
throw new ConnectionFailedException('Unable to connect to Redis');
373416
}
374417
}
375-
376418
// Destructor order cannot be predicted
377419
$this->_redis->setCloseOnDestruct(false);
378-
$this->_log(
379-
sprintf(
380-
"%s initialized for connection to %s:%s after %.5f seconds",
381-
get_class($this),
382-
$this->_redis->getHost(),
383-
$this->_redis->getPort(),
384-
(microtime(true) - $timeStart)
385-
)
386-
);
420+
if ($this->_useCluster) {
421+
$this->_log(
422+
sprintf(
423+
"%s initialized for connection to %s after %.5f seconds",
424+
get_class($this),
425+
(!empty($this->_redis->getClusterSeeds())) ?
426+
var_export($this->_redis->getClusterSeeds(), true) : $this->_redis->getClusterName(),
427+
(microtime(true) - $timeStart)
428+
)
429+
);
430+
} else {
431+
$this->_log(
432+
sprintf(
433+
"%s initialized for connection to %s:%s after %.5f seconds",
434+
get_class($this),
435+
$this->_redis->getHost(),
436+
$this->_redis->getPort(),
437+
(microtime(true) - $timeStart)
438+
)
439+
);
440+
}
387441
}
388442

389443
/**
@@ -459,7 +513,7 @@ public function read($sessionId)
459513
$timeStart = microtime(true);
460514
$this->_log(sprintf("Attempting to take lock on ID %s", $sessionId));
461515

462-
$this->_redis->select($this->_dbNum);
516+
if (!$this->_useCluster) $this->_redis->select($this->_dbNum);
463517
while ($this->_useLocking && !$this->_readOnly)
464518
{
465519
// Increment lock value for this session and retrieve the new value
@@ -639,18 +693,19 @@ public function read($sessionId)
639693
);
640694
}
641695
}
642-
643-
// Set session data and expiration
644-
$this->_redis->pipeline();
696+
if ($this->_usePipeline) {
697+
// Set session data and expiration
698+
$this->_redis->pipeline();
699+
}
645700
if ( ! empty($setData)) {
646701
$this->_redis->hMSet($sessionId, $setData);
647702
}
648703
$this->_redis->expire($sessionId, 3600*6); // Expiration will be set to correct value when session is written
649-
$this->_redis->exec();
650-
704+
if ($this->_usePipeline) {
705+
$this->_redis->exec();
706+
}
651707
// Reset flag in case of multiple session read/write operations
652708
$this->_sessionWritten = false;
653-
654709
return $sessionData ? (string) $this->_decodeData($sessionData) : '';
655710
}
656711

@@ -673,7 +728,7 @@ public function write($sessionId, $sessionData)
673728

674729
// Do not overwrite the session if it is locked by another pid
675730
try {
676-
if($this->_dbNum) $this->_redis->select($this->_dbNum); // Prevent conflicts with other connections?
731+
if ($this->_dbNum && !$this->_useCluster) $this->_redis->select($this->_dbNum); // Prevent conflicts with other connections?
677732
678733
if ( ! $this->_useLocking
679734
|| ( ! ($pid = $this->_redis->hGet('sess_'.$sessionId, 'pid')) || $pid == $this->_getPid())
@@ -711,10 +766,14 @@ public function write($sessionId, $sessionData)
711766
public function destroy($sessionId)
712767
{
713768
$this->_log(sprintf("Destroying ID %s", $sessionId));
714-
$this->_redis->pipeline();
715-
if($this->_dbNum) $this->_redis->select($this->_dbNum);
769+
if ($this->_usePipeline) {
770+
$this->_redis->pipeline();
771+
}
772+
if ($this->_dbNum && !$this->_useCluster) $this->_redis->select($this->_dbNum);
716773
$this->_redis->unlink(self::SESSION_PREFIX.$sessionId);
717-
$this->_redis->exec();
774+
if ($this->_usePipeline) {
775+
$this->_redis->exec();
776+
}
718777
return true;
719778
}
720779

@@ -832,7 +891,7 @@ protected function _encodeData($data)
832891
case 'lz4': $data = lz4_compress($data); $prefix = ':l4:'; break;
833892
case 'gzip': $data = gzcompress($data, 1); break;
834893
}
835-
if($data) {
894+
if ($data) {
836895
$data = $prefix.$data;
837896
$this->_log(
838897
sprintf(
@@ -880,15 +939,19 @@ protected function _decodeData($data)
880939
protected function _writeRawSession($id, $data, $lifetime)
881940
{
882941
$sessionId = 'sess_' . $id;
883-
$this->_redis->pipeline()
884-
->select($this->_dbNum)
885-
->hMSet($sessionId, array(
942+
if ($this->_usePipeline) {
943+
$this->_redis->pipeline();
944+
}
945+
if (!$this->_useCluster) $this->_redis->select($this->_dbNum);
946+
$this->_redis->hMSet($sessionId, array(
886947
'data' => $this->_encodeData($data),
887948
'lock' => 0, // 0 so that next lock attempt will get 1
888-
))
889-
->hIncrBy($sessionId, 'writes', 1)
890-
->expire($sessionId, min((int)$lifetime, (int)$this->_maxLifetime))
891-
->exec();
949+
));
950+
$this->_redis->hIncrBy($sessionId, 'writes', 1);
951+
$this->_redis->expire($sessionId, min((int)$lifetime, (int)$this->_maxLifetime));
952+
if ($this->_usePipeline) {
953+
$this->_redis->exec();
954+
}
892955
}
893956

894957
/**
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
/*
3+
==New BSD License==
4+
5+
Copyright (c) 2013, Colin Mollenhour
6+
All rights reserved.
7+
8+
Redistribution and use in source and binary forms, with or without
9+
modification, are permitted provided that the following conditions are met:
10+
11+
* Redistributions of source code must retain the above copyright
12+
notice, this list of conditions and the following disclaimer.
13+
* Redistributions in binary form must reproduce the above copyright
14+
notice, this list of conditions and the following disclaimer in the
15+
documentation and/or other materials provided with the distribution.
16+
* The name of Colin Mollenhour may not be used to endorse or promote products
17+
derived from this software without specific prior written permission.
18+
* Redistributions in any form must not change the Cm_RedisSession namespace.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
24+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
namespace Cm\RedisSession\Handler;
32+
33+
interface ClusterConfigInterface extends ConfigInterface
34+
{
35+
/**
36+
* Is this a cluster?
37+
*
38+
* @return bool
39+
*/
40+
public function isCluster() : bool;
41+
42+
/**
43+
* Optional name for cluster as read in redis.ini
44+
*
45+
* @return bool
46+
*/
47+
public function getClusterName() : ?string;
48+
49+
/**
50+
* Seeds for cluster
51+
*
52+
* @return bool
53+
*/
54+
public function getClusterSeeds() : ?array;
55+
56+
/**
57+
* Should we use persistent connection?
58+
*
59+
* @return bool
60+
*/
61+
public function getClusterUsePersistentConnection() : bool;
62+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
/*
3+
==New BSD License==
4+
5+
Copyright (c) 2013, Colin Mollenhour
6+
All rights reserved.
7+
8+
Redistribution and use in source and binary forms, with or without
9+
modification, are permitted provided that the following conditions are met:
10+
11+
* Redistributions of source code must retain the above copyright
12+
notice, this list of conditions and the following disclaimer.
13+
* Redistributions in binary form must reproduce the above copyright
14+
notice, this list of conditions and the following disclaimer in the
15+
documentation and/or other materials provided with the distribution.
16+
* The name of Colin Mollenhour may not be used to endorse or promote products
17+
derived from this software without specific prior written permission.
18+
* Redistributions in any form must not change the Cm_RedisSession namespace.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
24+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
namespace Cm\RedisSession\Handler;
32+
33+
interface TlsOptionsConfigInterface extends ConfigInterface
34+
{
35+
/**
36+
* Get optional TLS options
37+
*
38+
* @return array|null
39+
*/
40+
public function getTlsOptions() : ?array;
41+
}

0 commit comments

Comments
 (0)