redis-client 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Gemfile +1 -2
  4. data/Gemfile.lock +2 -3
  5. data/README.md +71 -8
  6. data/Rakefile +43 -23
  7. data/lib/redis_client/command_builder.rb +91 -0
  8. data/lib/redis_client/config.rb +19 -50
  9. data/lib/redis_client/connection_mixin.rb +40 -0
  10. data/lib/redis_client/decorator.rb +84 -0
  11. data/lib/redis_client/pooled.rb +38 -30
  12. data/lib/redis_client/ruby_connection/buffered_io.rb +153 -0
  13. data/lib/redis_client/{resp3.rb → ruby_connection/resp3.rb} +0 -26
  14. data/lib/redis_client/{connection.rb → ruby_connection.rb} +26 -31
  15. data/lib/redis_client/version.rb +1 -1
  16. data/lib/redis_client.rb +183 -36
  17. data/redis-client.gemspec +2 -4
  18. metadata +12 -59
  19. data/.rubocop.yml +0 -190
  20. data/ext/redis_client/hiredis/export.clang +0 -2
  21. data/ext/redis_client/hiredis/export.gcc +0 -7
  22. data/ext/redis_client/hiredis/extconf.rb +0 -61
  23. data/ext/redis_client/hiredis/hiredis_connection.c +0 -708
  24. data/ext/redis_client/hiredis/vendor/.gitignore +0 -9
  25. data/ext/redis_client/hiredis/vendor/.travis.yml +0 -131
  26. data/ext/redis_client/hiredis/vendor/CHANGELOG.md +0 -364
  27. data/ext/redis_client/hiredis/vendor/CMakeLists.txt +0 -165
  28. data/ext/redis_client/hiredis/vendor/COPYING +0 -29
  29. data/ext/redis_client/hiredis/vendor/Makefile +0 -308
  30. data/ext/redis_client/hiredis/vendor/README.md +0 -664
  31. data/ext/redis_client/hiredis/vendor/adapters/ae.h +0 -130
  32. data/ext/redis_client/hiredis/vendor/adapters/glib.h +0 -156
  33. data/ext/redis_client/hiredis/vendor/adapters/ivykis.h +0 -84
  34. data/ext/redis_client/hiredis/vendor/adapters/libev.h +0 -179
  35. data/ext/redis_client/hiredis/vendor/adapters/libevent.h +0 -175
  36. data/ext/redis_client/hiredis/vendor/adapters/libuv.h +0 -117
  37. data/ext/redis_client/hiredis/vendor/adapters/macosx.h +0 -115
  38. data/ext/redis_client/hiredis/vendor/adapters/qt.h +0 -135
  39. data/ext/redis_client/hiredis/vendor/alloc.c +0 -86
  40. data/ext/redis_client/hiredis/vendor/alloc.h +0 -91
  41. data/ext/redis_client/hiredis/vendor/appveyor.yml +0 -24
  42. data/ext/redis_client/hiredis/vendor/async.c +0 -887
  43. data/ext/redis_client/hiredis/vendor/async.h +0 -147
  44. data/ext/redis_client/hiredis/vendor/async_private.h +0 -75
  45. data/ext/redis_client/hiredis/vendor/dict.c +0 -352
  46. data/ext/redis_client/hiredis/vendor/dict.h +0 -126
  47. data/ext/redis_client/hiredis/vendor/fmacros.h +0 -12
  48. data/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in +0 -13
  49. data/ext/redis_client/hiredis/vendor/hiredis.c +0 -1174
  50. data/ext/redis_client/hiredis/vendor/hiredis.h +0 -336
  51. data/ext/redis_client/hiredis/vendor/hiredis.pc.in +0 -12
  52. data/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in +0 -13
  53. data/ext/redis_client/hiredis/vendor/hiredis_ssl.h +0 -157
  54. data/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in +0 -12
  55. data/ext/redis_client/hiredis/vendor/net.c +0 -612
  56. data/ext/redis_client/hiredis/vendor/net.h +0 -56
  57. data/ext/redis_client/hiredis/vendor/read.c +0 -739
  58. data/ext/redis_client/hiredis/vendor/read.h +0 -129
  59. data/ext/redis_client/hiredis/vendor/sds.c +0 -1289
  60. data/ext/redis_client/hiredis/vendor/sds.h +0 -278
  61. data/ext/redis_client/hiredis/vendor/sdsalloc.h +0 -44
  62. data/ext/redis_client/hiredis/vendor/sockcompat.c +0 -248
  63. data/ext/redis_client/hiredis/vendor/sockcompat.h +0 -92
  64. data/ext/redis_client/hiredis/vendor/ssl.c +0 -544
  65. data/ext/redis_client/hiredis/vendor/test.c +0 -1401
  66. data/ext/redis_client/hiredis/vendor/test.sh +0 -78
  67. data/ext/redis_client/hiredis/vendor/win32.h +0 -56
  68. data/lib/redis_client/buffered_io.rb +0 -151
  69. data/lib/redis_client/hiredis_connection.rb +0 -80
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f09f4bc8f3f2cf92a6beb3b02ef601209bc36073eee68e4d57c93f802b10db2f
4
- data.tar.gz: c3539ba4db69fac8a98649e27125409feb62d925b55a2c03bc75e87d26e60a89
3
+ metadata.gz: 770636c5814252674d680c45f75af4ea291b698c18382b49d93bec4e3d7ab45b
4
+ data.tar.gz: bdef4f0f80574a9aaf29613ec23d31d7a0c3d916ca3945ac412b36b55eecd73a
5
5
  SHA512:
6
- metadata.gz: '08bbfce855729654d6f74830a18a6aaea755f4790c58ccf92cc53ac321a6a3ca4d5935a885498c89d48d781b5affc16e757524e7d1f2355da57b5048ca968528'
7
- data.tar.gz: aac332fe44444ac5afdf5b23ba35e23dc3129a50c5c9c51628f24d151620c26108984574decd5ab25435f39ee305cf4933ab0413aaad7076cf525503c9ec50d3
6
+ metadata.gz: 3367281b907b6e38e73ba1e9a74f63404e40f422b319c551645fec1efa694e87c09cd96c67d308c362407c84be59eb62435b9951740cd515727a554050ae3137
7
+ data.tar.gz: 9dc6f5e6638405c8841b267d0c9cf882d0721cbd04655984af71bddb66cc7e2f4698e7199e752582064e4db3ca8e3f45a77b56241df309ae0c2e231ebff1cf2b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Unreleased
2
2
 
3
+ - Fix handling of connection URLs with empty passwords (`redis://:pass@example.com`).
4
+ - Handle URLs with IPv6 hosts.
5
+ - Add `RedisClient::Config#server_url` as a quick way to identify which server the client is pointing to.
6
+ - Add `CommandError#command` to expose the command that caused the error.
7
+ - Raise a more explicit error when connecting to older redises without RESP3 support (5.0 and older).
8
+ - Properly reject empty commands early.
9
+
10
+ # 0.4.0
11
+
12
+ - The `hiredis` driver have been moved to the `hiredis-client` gem.
13
+
14
+ # 0.3.0
15
+
16
+ - `hiredis` is now the default driver when available.
17
+ - Add `RedisClient.default_driver=`.
18
+ - `#call` now takes an optional block to cast the return value.
19
+ - Treat `#call` keyword arguments as Redis flags.
20
+ - Fix `RedisClient#multi` returning some errors as values instead of raising them.
21
+
3
22
  # 0.2.1
4
23
 
5
24
  - Use a more robust way to detect the current compiler.
data/Gemfile CHANGED
@@ -3,9 +3,8 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  # Specify your gem's dependencies in redis-client.gemspec
6
- gemspec
6
+ gemspec name: "redis-client"
7
7
 
8
- gem "connection_pool"
9
8
  gem "minitest"
10
9
  gem "rake", "~> 13.0"
11
10
  gem "rake-compiler"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redis-client (0.2.1)
4
+ redis-client (0.5.0)
5
5
  connection_pool
6
6
 
7
7
  GEM
@@ -51,7 +51,6 @@ PLATFORMS
51
51
  DEPENDENCIES
52
52
  benchmark-ips
53
53
  byebug
54
- connection_pool
55
54
  hiredis
56
55
  minitest
57
56
  rake (~> 13.0)
@@ -64,4 +63,4 @@ DEPENDENCIES
64
63
  toxiproxy
65
64
 
66
65
  BUNDLED WITH
67
- 2.3.8
66
+ 2.3.13
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `redis-client` is a simple, low-level, client for Redis 6+.
4
4
 
5
- Contrary to the `redis` gem, `redis-client` doesn't try to map all redis commands to Ruby constructs,
5
+ Contrary to the `redis` gem, `redis-client` doesn't try to map all Redis commands to Ruby constructs,
6
6
  it merely is a thin wrapper on top of the RESP3 protocol.
7
7
 
8
8
  ## Installation
@@ -63,7 +63,7 @@ redis.call("GET", "mykey")
63
63
  ### Configuration
64
64
 
65
65
  - `url`: A Redis connection URL, e.g. `redis://example.com:6379/5`, a `rediss://` scheme enable SSL, and the path is interpreted as a database number.
66
- Note tht all other configurtions take precedence, e.g. `RedisClient.config(url: "redis://localhost:3000" port: 6380)` will connect on port `6380`.
66
+ Note that all other configurations take precedence, e.g. `RedisClient.config(url: "redis://localhost:3000" port: 6380)` will connect on port `6380`.
67
67
  - `host`: The server hostname or IP address. Defaults to `"localhost"`.
68
68
  - `port`: The server port. Defaults to `6379`.
69
69
  - `path`: The path to a UNIX socket, if set `url`, `host` and `port` are ignored.
@@ -142,21 +142,70 @@ is equivalent to:
142
142
  redis.call("LPUSH", "list", "1", "2", "3", "4")
143
143
  ```
144
144
 
145
- Hashes are flatenned as well:
145
+ Hashes are flattened as well:
146
146
 
147
147
  ```ruby
148
- redis.call("HMSET", "hash", foo: 1, bar: 2)
149
- redis.call("SET", "key", "value", ex: 5)
148
+ redis.call("HMSET", "hash", { "foo" => "1", "bar" => "2" })
150
149
  ```
151
150
 
152
151
  is equivalent to:
153
152
 
154
153
  ```ruby
155
154
  redis.call("HMSET", "hash", "foo", "1", "bar", "2")
156
- redis.call("SET", "key", "value", "ex", "5")
157
155
  ```
158
156
 
159
- Any other type requires the caller to explictly cast the argument as a string.
157
+ Any other type requires the caller to explicitly cast the argument as a string.
158
+
159
+ Keywords arguments are treated as Redis command flags:
160
+
161
+ ```ruby
162
+ redis.call("SET", "mykey", "value", nx: true, ex: 60)
163
+ redis.call("SET", "mykey", "value", nx: false, ex: nil)
164
+ ```
165
+
166
+ is equivalent to:
167
+
168
+ ```ruby
169
+ redis.call("SET", "mykey", "value", "nx", "ex", "60")
170
+ redis.call("SET", "mykey", "value")
171
+ ```
172
+
173
+ If flags are built dynamically, you'll have to explicitly pass them as keyword arguments with `**`:
174
+
175
+ ```ruby
176
+ flags = {}
177
+ flags[:nx] = true if something?
178
+ redis.call("SET", "mykey", "value", **flags)
179
+ ```
180
+
181
+ **Important Note**: because of the keyword argument semantic change between Ruby 2 and Ruby 3,
182
+ unclosed hash literals with string keys may be interpreted differently:
183
+
184
+ ```ruby
185
+ redis.call("HMSET", "hash", "foo" => "bar")
186
+ ```
187
+
188
+ On Ruby 2 `"foo" => "bar"` will be passed as a positional argument, but on Ruby 3 it will be interpreted as keyword
189
+ arguments. To avoid such problem, make sure to enclose hash literals:
190
+
191
+ ```ruby
192
+ redis.call("HMSET", "hash", { "foo" => "bar" })
193
+ ```
194
+
195
+ ### Commands return values
196
+
197
+ Contrary to the `redis` gem, `redis-client` doesn't do any type casting on the return value of commands.
198
+
199
+ If you wish to cast the return value, you can pass a block to the `#call` family of methods:
200
+
201
+ ```ruby
202
+ redis.call("INCR", "counter") # => 1
203
+ redis.call("GET", "counter") # => "1"
204
+ redis.call("GET", "counter", &:to_i) # => 1
205
+
206
+ redis.call("EXISTS", "counter") # => 1
207
+ redis.call("EXISTS", "counter") { |c| c > 0 } # => true
208
+ ```
160
209
 
161
210
  ### Blocking commands
162
211
 
@@ -248,7 +297,7 @@ end
248
297
 
249
298
  If the transaction wasn't successful, `#multi` will return `nil`.
250
299
 
251
- Note that transactions using optimistic locking aren't automatically retried uppon connection errors.
300
+ Note that transactions using optimistic locking aren't automatically retried upon connection errors.
252
301
 
253
302
  ### Publish / Subscribe
254
303
 
@@ -338,6 +387,20 @@ redis.call("GET", "counter") # Will be retried up to 3 times.
338
387
  redis.call_once("INCR", "counter") # Won't be retried.
339
388
  ```
340
389
 
390
+ ### Drivers
391
+
392
+ `redis-client` ships with a pure Ruby socket implementation.
393
+
394
+ For increased performance, you can enable the `hiredis` binding by adding `hiredis-client` to your Gemfile:
395
+
396
+ ```ruby
397
+ gem "hiredis-client"
398
+ ```
399
+
400
+ The hiredis binding is only available on Linux, macOS and other POSIX platforms. You can install the gem on other platforms, but it won't have any effect.
401
+
402
+ The default driver can be set through `RedisClient.default_driver=`:
403
+
341
404
  ## Notable differences with the `redis` gem
342
405
 
343
406
  ### Thread Safety
data/Rakefile CHANGED
@@ -1,33 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
3
  require "rake/extensiontask"
5
4
  require "rake/testtask"
6
5
  require 'rubocop/rake_task'
7
6
 
8
7
  RuboCop::RakeTask.new
9
8
 
9
+ require "rake/clean"
10
+ CLOBBER.include "pkg"
11
+ require "bundler/gem_helper"
12
+ Bundler::GemHelper.install_tasks(name: "redis-client")
13
+ Bundler::GemHelper.install_tasks(dir: "hiredis-client", name: "hiredis-client")
14
+
10
15
  gemspec = Gem::Specification.load("redis-client.gemspec")
11
16
  Rake::ExtensionTask.new do |ext|
12
17
  ext.name = "hiredis_connection"
13
- ext.ext_dir = "ext/redis_client/hiredis"
14
- ext.lib_dir = "lib/redis_client"
18
+ ext.ext_dir = "hiredis-client/ext/redis_client/hiredis"
19
+ ext.lib_dir = "hiredis-client/lib/redis_client"
15
20
  ext.gem_spec = gemspec
16
21
  CLEAN.add("#{ext.ext_dir}/vendor/*.{a,o}")
17
22
  end
18
23
 
19
- Rake::TestTask.new(:test) do |t|
20
- t.libs << "test"
21
- t.libs << "lib"
22
- t.test_files = FileList["test/**/*_test.rb"].exclude("test/sentinel/*_test.rb")
23
- end
24
-
25
24
  namespace :test do
25
+ Rake::TestTask.new(:ruby) do |t|
26
+ t.libs << "test"
27
+ t.libs << "lib"
28
+ t.test_files = FileList["test/**/*_test.rb"].exclude("test/sentinel/*_test.rb")
29
+ end
30
+
26
31
  Rake::TestTask.new(:sentinel) do |t|
32
+ t.libs << "test/sentinel"
27
33
  t.libs << "test"
28
34
  t.libs << "lib"
29
35
  t.test_files = FileList["test/sentinel/*_test.rb"]
30
36
  end
37
+
38
+ Rake::TestTask.new(:hiredis) do |t|
39
+ t.libs << "test/hiredis"
40
+ t.libs << "test"
41
+ t.libs << "lib"
42
+ t.test_files = FileList["test/**/*_test.rb"].exclude("test/sentinel/*_test.rb")
43
+ end
44
+ end
45
+
46
+ hiredis_supported = RUBY_ENGINE == "ruby" && !RUBY_PLATFORM.match?(/mswin/)
47
+ if hiredis_supported
48
+ task test: %i[test:ruby test:hiredis test:sentinel]
49
+ else
50
+ task test: %i[test:ruby test:sentinel]
31
51
  end
32
52
 
33
53
  namespace :hiredis do
@@ -36,10 +56,14 @@ namespace :hiredis do
36
56
  archive_path = "tmp/hiredis-#{version}.tar.gz"
37
57
  url = "https://github.com/redis/hiredis/archive/refs/tags/v#{version}.tar.gz"
38
58
  system("curl", "-L", url, out: archive_path) or raise "Downloading of #{url} failed"
39
- system("rm", "-rf", "ext/redis_client/hiredis/vendor/")
40
- system("mkdir", "-p", "ext/redis_client/hiredis/vendor/")
41
- system("tar", "xvzf", archive_path, "-C", "ext/redis_client/hiredis/vendor", "--strip-components", "1")
42
- system("rm", "-rf", "ext/redis_client/hiredis/vendor/examples")
59
+ system("rm", "-rf", "hiredis-client/ext/redis_client/hiredis/vendor/")
60
+ system("mkdir", "-p", "hiredis-client/ext/redis_client/hiredis/vendor/")
61
+ system(
62
+ "tar", "xvzf", archive_path,
63
+ "-C", "hiredis-client/ext/redis_client/hiredis/vendor",
64
+ "--strip-components", "1",
65
+ )
66
+ system("rm", "-rf", "hiredis-client/ext/redis_client/hiredis/vendor/examples")
43
67
  end
44
68
  end
45
69
 
@@ -57,7 +81,7 @@ namespace :benchmark do
57
81
  env = {}
58
82
  args = []
59
83
  args << "--yjit" if mode == :yjit
60
- env["DRIVER"] = "hiredis" if mode == :hiredis
84
+ env["DRIVER"] = mode == :hiredis ? "hiredis" : "ruby"
61
85
  system(env, RbConfig.ruby, *args, "benchmark/#{suite}.rb", out: output)
62
86
  end
63
87
 
@@ -81,14 +105,10 @@ namespace :benchmark do
81
105
  end
82
106
  end
83
107
 
84
- if RUBY_PLATFORM == "java"
85
- task default: %i[test test:sentinel rubocop]
86
- else
87
- task default: %i[compile test test:sentinel rubocop]
88
- end
89
-
90
- if ENV["DRIVER"] == "hiredis"
91
- task ci: %i[compile test test:sentinel]
108
+ if hiredis_supported
109
+ task default: %i[compile test rubocop]
110
+ task ci: %i[compile test]
92
111
  else
93
- task ci: %i[test test:sentinel]
112
+ task default: %i[test rubocop]
113
+ task ci: %i[test]
94
114
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedisClient
4
+ module CommandBuilder
5
+ extend self
6
+
7
+ if Symbol.method_defined?(:name)
8
+ def generate!(args, kwargs)
9
+ command = args.flat_map do |element|
10
+ case element
11
+ when Hash
12
+ element.flatten
13
+ when Set
14
+ element.to_a
15
+ else
16
+ element
17
+ end
18
+ end
19
+
20
+ kwargs.each do |key, value|
21
+ if value
22
+ if value == true
23
+ command << key.name
24
+ else
25
+ command << key.name << value
26
+ end
27
+ end
28
+ end
29
+
30
+ command.map! do |element|
31
+ case element
32
+ when String
33
+ element
34
+ when Symbol
35
+ element.name
36
+ when Integer, Float
37
+ element.to_s
38
+ else
39
+ raise TypeError, "Unsupported command argument type: #{element.class}"
40
+ end
41
+ end
42
+
43
+ if command.empty?
44
+ raise ArgumentError, "can't issue an empty redis command"
45
+ end
46
+
47
+ command
48
+ end
49
+ else
50
+ def generate!(args, kwargs)
51
+ command = args.flat_map do |element|
52
+ case element
53
+ when Hash
54
+ element.flatten
55
+ when Set
56
+ element.to_a
57
+ else
58
+ element
59
+ end
60
+ end
61
+
62
+ kwargs.each do |key, value|
63
+ if value
64
+ if value == true
65
+ command << key.to_s
66
+ else
67
+ command << key.to_s << value
68
+ end
69
+ end
70
+ end
71
+
72
+ command.map! do |element|
73
+ case element
74
+ when String
75
+ element
76
+ when Integer, Float, Symbol
77
+ element.to_s
78
+ else
79
+ raise TypeError, "Unsupported command argument type: #{element.class}"
80
+ end
81
+ end
82
+
83
+ if command.empty?
84
+ raise ArgumentError, "can't issue an empty redis command"
85
+ end
86
+
87
+ command
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "openssl"
3
4
  require "uri"
4
5
 
5
6
  class RedisClient
@@ -11,7 +12,7 @@ class RedisClient
11
12
  DEFAULT_DB = 0
12
13
 
13
14
  module Common
14
- attr_reader :db, :username, :password, :id, :ssl, :ssl_params,
15
+ attr_reader :db, :username, :password, :id, :ssl, :ssl_params, :command_builder,
15
16
  :connect_timeout, :read_timeout, :write_timeout, :driver, :connection_prelude
16
17
 
17
18
  alias_method :ssl?, :ssl
@@ -27,7 +28,8 @@ class RedisClient
27
28
  connect_timeout: timeout,
28
29
  ssl: nil,
29
30
  ssl_params: nil,
30
- driver: :ruby,
31
+ driver: nil,
32
+ command_builder: CommandBuilder,
31
33
  reconnect_attempts: false
32
34
  )
33
35
  @username = username || DEFAULT_USERNAME
@@ -41,17 +43,9 @@ class RedisClient
41
43
  @read_timeout = read_timeout
42
44
  @write_timeout = write_timeout
43
45
 
44
- @driver = case driver
45
- when :ruby
46
- Connection
47
- when :hiredis
48
- unless defined?(RedisClient::HiredisConnection)
49
- require "redis_client/hiredis_connection"
50
- end
51
- HiredisConnection
52
- else
53
- raise ArgumentError, "Unknown driver #{driver.inspect}, expected one of: `:ruby`, `:hiredis`"
54
- end
46
+ @driver = driver ? RedisClient.driver(driver) : RedisClient.default_driver
47
+
48
+ @command_builder = command_builder
55
49
 
56
50
  reconnect_attempts = Array.new(reconnect_attempts, 0).freeze if reconnect_attempts.is_a?(Integer)
57
51
  @reconnect_attempts = reconnect_attempts
@@ -84,40 +78,15 @@ class RedisClient
84
78
  false
85
79
  end
86
80
 
87
- def hiredis_ssl_context
88
- @hiredis_ssl_context ||= HiredisConnection::SSLContext.new(
89
- ca_file: @ssl_params[:ca_file],
90
- ca_path: @ssl_params[:ca_path],
91
- cert: @ssl_params[:cert],
92
- key: @ssl_params[:key],
93
- hostname: @ssl_params[:hostname],
94
- )
81
+ def ssl_context
82
+ @ssl_context ||= @driver.ssl_context(@ssl_params)
95
83
  end
96
84
 
97
- def openssl_context
98
- @openssl_context ||= begin
99
- params = @ssl_params.dup || {}
100
-
101
- cert = params[:cert]
102
- if cert.is_a?(String)
103
- cert = File.read(cert) if File.exist?(cert)
104
- params[:cert] = OpenSSL::X509::Certificate.new(cert)
105
- end
106
-
107
- key = params[:key]
108
- if key.is_a?(String)
109
- key = File.read(key) if File.exist?(key)
110
- params[:key] = OpenSSL::PKey.read(key)
111
- end
112
-
113
- context = OpenSSL::SSL::SSLContext.new
114
- context.set_params(params)
115
- if context.verify_mode != OpenSSL::SSL::VERIFY_NONE
116
- if context.respond_to?(:verify_hostname) # Missing on JRuby
117
- context.verify_hostname
118
- end
119
- end
120
- context
85
+ def server_url
86
+ if path
87
+ "#{path}/#{db}"
88
+ else
89
+ "redis#{'s' if ssl?}://#{host}:#{port}/#{db}"
121
90
  end
122
91
  end
123
92
 
@@ -149,15 +118,15 @@ class RedisClient
149
118
  path: nil,
150
119
  **kwargs
151
120
  )
152
- uri = url && URI.parse(url)
153
- if uri
121
+ if url
122
+ uri = URI.parse(url)
154
123
  kwargs[:ssl] = uri.scheme == "rediss" unless kwargs.key?(:ssl)
155
124
 
156
- kwargs[:username] ||= uri.user && uri.password
125
+ kwargs[:username] ||= uri.user if uri.password && !uri.user.empty?
157
126
 
158
127
  kwargs[:password] ||= if uri.user && !uri.password
159
128
  URI.decode_www_form_component(uri.user)
160
- elsif uri&.user && uri&.password
129
+ elsif uri.user && uri.password
161
130
  URI.decode_www_form_component(uri.password)
162
131
  end
163
132
 
@@ -166,7 +135,7 @@ class RedisClient
166
135
 
167
136
  super(**kwargs)
168
137
 
169
- @host = host || uri&.host || DEFAULT_HOST
138
+ @host = host || uri&.host&.sub(/\A\[(.*)\]\z/, '\1') || DEFAULT_HOST
170
139
  @port = port || uri&.port || DEFAULT_PORT
171
140
  @path = path
172
141
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedisClient
4
+ module ConnectionMixin
5
+ def call(command, timeout)
6
+ write(command)
7
+ result = read(timeout)
8
+ if result.is_a?(CommandError)
9
+ result._set_command(command)
10
+ raise result
11
+ else
12
+ result
13
+ end
14
+ end
15
+
16
+ def call_pipelined(commands, timeouts)
17
+ exception = nil
18
+
19
+ size = commands.size
20
+ results = Array.new(commands.size)
21
+ write_multi(commands)
22
+
23
+ size.times do |index|
24
+ timeout = timeouts && timeouts[index]
25
+ result = read(timeout)
26
+ if result.is_a?(CommandError)
27
+ result._set_command(commands[index])
28
+ exception ||= result
29
+ end
30
+ results[index] = result
31
+ end
32
+
33
+ if exception
34
+ raise exception
35
+ else
36
+ results
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedisClient
4
+ module Decorator
5
+ class << self
6
+ def create(commands_mixin)
7
+ client_decorator = Class.new(Client)
8
+ client_decorator.include(commands_mixin)
9
+
10
+ pipeline_decorator = Class.new(Pipeline)
11
+ pipeline_decorator.include(commands_mixin)
12
+ client_decorator.const_set(:Pipeline, pipeline_decorator)
13
+
14
+ client_decorator
15
+ end
16
+ end
17
+
18
+ module CommandsMixin
19
+ def initialize(client)
20
+ @client = client
21
+ end
22
+
23
+ %i(call call_once blocking_call).each do |method|
24
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
25
+ def #{method}(*args, &block)
26
+ @client.#{method}(*args, &block)
27
+ end
28
+ ruby2_keywords :#{method} if respond_to?(:ruby2_keywords, true)
29
+ RUBY
30
+ end
31
+ end
32
+
33
+ class Pipeline
34
+ include CommandsMixin
35
+ end
36
+
37
+ class Client
38
+ include CommandsMixin
39
+
40
+ def initialize(_client)
41
+ super
42
+ @_pipeline_class = self.class::Pipeline
43
+ end
44
+
45
+ def with(*args)
46
+ @client.with(*args) { |c| yield self.class.new(c) }
47
+ end
48
+ ruby2_keywords :with if respond_to?(:ruby2_keywords, true)
49
+
50
+ def pipelined
51
+ @client.pipelined { |p| yield @_pipeline_class.new(p) }
52
+ end
53
+
54
+ def multi(**kwargs)
55
+ @client.multi(**kwargs) { |p| yield @_pipeline_class.new(p) }
56
+ end
57
+
58
+ %i(close scan hscan sscan zscan).each do |method|
59
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
60
+ def #{method}(*args, &block)
61
+ @client.#{method}(*args, &block)
62
+ end
63
+ ruby2_keywords :#{method} if respond_to?(:ruby2_keywords, true)
64
+ RUBY
65
+ end
66
+
67
+ %i(id config size connect_timeout read_timeout write_timeout).each do |reader|
68
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
69
+ def #{reader}
70
+ @client.#{reader}
71
+ end
72
+ RUBY
73
+ end
74
+
75
+ %i(timeout connect_timeout read_timeout write_timeout).each do |writer|
76
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
77
+ def #{writer}=(value)
78
+ @client.#{writer} = value
79
+ end
80
+ RUBY
81
+ end
82
+ end
83
+ end
84
+ end