RedisFactory.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Alejandro Varela <epma01@gmail.com>
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Robin Appelman <robin@icewind.nl>
  10. * @author Robin McCorkell <robin@mccorkell.me.uk>
  11. *
  12. * @license AGPL-3.0
  13. *
  14. * This code is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU Affero General Public License, version 3,
  16. * as published by the Free Software Foundation.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU Affero General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU Affero General Public License, version 3,
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>
  25. *
  26. */
  27. namespace OC;
  28. use OCP\Diagnostics\IEventLogger;
  29. class RedisFactory {
  30. public const REDIS_MINIMAL_VERSION = '3.1.3';
  31. public const REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION = '5.3.0';
  32. /** @var \Redis|\RedisCluster */
  33. private $instance;
  34. private SystemConfig $config;
  35. private IEventLogger $eventLogger;
  36. /**
  37. * RedisFactory constructor.
  38. *
  39. * @param SystemConfig $config
  40. */
  41. public function __construct(SystemConfig $config, IEventLogger $eventLogger) {
  42. $this->config = $config;
  43. $this->eventLogger = $eventLogger;
  44. }
  45. private function create() {
  46. $isCluster = in_array('redis.cluster', $this->config->getKeys(), true);
  47. $config = $isCluster
  48. ? $this->config->getValue('redis.cluster', [])
  49. : $this->config->getValue('redis', []);
  50. if ($isCluster && !class_exists('RedisCluster')) {
  51. throw new \Exception('Redis Cluster support is not available');
  52. }
  53. $timeout = $config['timeout'] ?? 0.0;
  54. $readTimeout = $config['read_timeout'] ?? 0.0;
  55. $auth = null;
  56. if (isset($config['password']) && (string)$config['password'] !== '') {
  57. if (isset($config['user']) && (string)$config['user'] !== '') {
  58. $auth = [$config['user'], $config['password']];
  59. } else {
  60. $auth = $config['password'];
  61. }
  62. }
  63. // # TLS support
  64. // # https://github.com/phpredis/phpredis/issues/1600
  65. $connectionParameters = $this->getSslContext($config);
  66. // cluster config
  67. if ($isCluster) {
  68. if (!isset($config['seeds'])) {
  69. throw new \Exception('Redis cluster config is missing the "seeds" attribute');
  70. }
  71. // Support for older phpredis versions not supporting connectionParameters
  72. if ($connectionParameters !== null) {
  73. $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, true, $auth, $connectionParameters);
  74. } else {
  75. $this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, true, $auth);
  76. }
  77. if (isset($config['failover_mode'])) {
  78. $this->instance->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, $config['failover_mode']);
  79. }
  80. } else {
  81. $this->instance = new \Redis();
  82. $host = $config['host'] ?? '127.0.0.1';
  83. $port = $config['port'] ?? ($host[0] !== '/' ? 6379 : null);
  84. $this->eventLogger->start('connect:redis', 'Connect to redis and send AUTH, SELECT');
  85. // Support for older phpredis versions not supporting connectionParameters
  86. if ($connectionParameters !== null) {
  87. // Non-clustered redis requires connection parameters to be wrapped inside `stream`
  88. $connectionParameters = [
  89. 'stream' => $this->getSslContext($config)
  90. ];
  91. /**
  92. * even though the stubs and documentation don't want you to know this,
  93. * pconnect does have the same $connectionParameters argument connect has
  94. *
  95. * https://github.com/phpredis/phpredis/blob/0264de1824b03fb2d0ad515b4d4ec019cd2dae70/redis.c#L710-L730
  96. *
  97. * @psalm-suppress TooManyArguments
  98. */
  99. $this->instance->pconnect($host, $port, $timeout, null, 0, $readTimeout, $connectionParameters);
  100. } else {
  101. $this->instance->pconnect($host, $port, $timeout, null, 0, $readTimeout);
  102. }
  103. // Auth if configured
  104. if ($auth !== null) {
  105. $this->instance->auth($auth);
  106. }
  107. if (isset($config['dbindex'])) {
  108. $this->instance->select($config['dbindex']);
  109. }
  110. $this->eventLogger->end('connect:redis');
  111. }
  112. }
  113. /**
  114. * Get the ssl context config
  115. *
  116. * @param array $config the current config
  117. * @return array|null
  118. * @throws \UnexpectedValueException
  119. */
  120. private function getSslContext($config) {
  121. if (isset($config['ssl_context'])) {
  122. if (!$this->isConnectionParametersSupported()) {
  123. throw new \UnexpectedValueException(\sprintf(
  124. 'php-redis extension must be version %s or higher to support ssl context',
  125. self::REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION
  126. ));
  127. }
  128. return $config['ssl_context'];
  129. }
  130. return null;
  131. }
  132. public function getInstance() {
  133. if (!$this->isAvailable()) {
  134. throw new \Exception('Redis support is not available');
  135. }
  136. if (!$this->instance instanceof \Redis) {
  137. $this->create();
  138. }
  139. return $this->instance;
  140. }
  141. public function isAvailable(): bool {
  142. return \extension_loaded('redis') &&
  143. \version_compare(\phpversion('redis'), self::REDIS_MINIMAL_VERSION, '>=');
  144. }
  145. /**
  146. * Php redis does support configurable extra parameters since version 5.3.0, see: https://github.com/phpredis/phpredis#connect-open.
  147. * We need to check if the current version supports extra connection parameters, otherwise the connect method will throw an exception
  148. *
  149. * @return boolean
  150. */
  151. private function isConnectionParametersSupported(): bool {
  152. return \extension_loaded('redis') &&
  153. \version_compare(\phpversion('redis'), self::REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION, '>=');
  154. }
  155. }