async-redis 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/redis/client.rb +7 -12
- data/lib/async/redis/context/pipeline.rb +13 -10
- data/lib/async/redis/endpoint.rb +268 -0
- data/lib/async/redis/protocol/authenticated.rb +44 -0
- data/lib/async/redis/protocol/resp2.rb +2 -2
- data/lib/async/redis/protocol/selected.rb +44 -0
- data/lib/async/redis/sentinels.rb +41 -32
- data/lib/async/redis/version.rb +2 -2
- data/license.md +1 -1
- data/readme.md +11 -100
- data.tar.gz.sig +0 -0
- metadata +35 -22
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97fe8ed81d0096bd994cbbd3511fc3acb7c0c4629cfafdac47e5c237137083f1
|
4
|
+
data.tar.gz: c8a5b6a33efb6de45dc7cc2154b694fbd9337a14c485f85f1693bfeabbcc0f79
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad448e4900402ef3e974a247c27a6a8fa5bf65d47fda67b46260830ecfb76a185c86a03ff0c38cd18ae1d628a7ce332d6952e26704d357f3858458fe593fb49d
|
7
|
+
data.tar.gz: 83535dc337e792808e6e3033864e393cb075a5d709bd5a8c507577d8b792f0600388f98cd28997c891102c36dc79062c6327004359f51c4677b50bab42eced8a
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/async/redis/client.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2018-
|
4
|
+
# Copyright, 2018-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2018, by Huba Nagy.
|
6
6
|
# Copyright, 2019, by Mikael Henriksson.
|
7
7
|
# Copyright, 2019, by David Ortiz.
|
@@ -10,28 +10,23 @@
|
|
10
10
|
require_relative 'context/pipeline'
|
11
11
|
require_relative 'context/transaction'
|
12
12
|
require_relative 'context/subscribe'
|
13
|
+
require_relative 'endpoint'
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
require 'async/io'
|
17
|
-
require 'async/io/stream'
|
15
|
+
require 'io/endpoint/host_endpoint'
|
18
16
|
require 'async/pool/controller'
|
19
|
-
|
20
17
|
require 'protocol/redis/methods'
|
21
18
|
|
19
|
+
require 'io/stream'
|
20
|
+
|
22
21
|
module Async
|
23
22
|
module Redis
|
24
23
|
# Legacy.
|
25
24
|
ServerError = ::Protocol::Redis::ServerError
|
26
25
|
|
27
|
-
def self.local_endpoint(port: 6379)
|
28
|
-
Async::IO::Endpoint.tcp('localhost', port)
|
29
|
-
end
|
30
|
-
|
31
26
|
class Client
|
32
27
|
include ::Protocol::Redis::Methods
|
33
28
|
|
34
|
-
def initialize(endpoint =
|
29
|
+
def initialize(endpoint = Endpoint.local, protocol: endpoint.protocol, **options)
|
35
30
|
@endpoint = endpoint
|
36
31
|
@protocol = protocol
|
37
32
|
|
@@ -121,7 +116,7 @@ module Async
|
|
121
116
|
# We will manage flushing ourselves:
|
122
117
|
peer.sync = true
|
123
118
|
|
124
|
-
stream = IO::Stream
|
119
|
+
stream = ::IO::Stream(peer)
|
125
120
|
|
126
121
|
@protocol.client(stream)
|
127
122
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2019, by David Ortiz.
|
5
|
-
# Copyright, 2019-
|
5
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
6
6
|
# Copyright, 2022, by Tim Willard.
|
7
7
|
|
8
8
|
require_relative 'generic'
|
@@ -22,8 +22,8 @@ module Async
|
|
22
22
|
end
|
23
23
|
|
24
24
|
# This method just accumulates the commands and their params.
|
25
|
-
def call(
|
26
|
-
@pipeline.call(
|
25
|
+
def call(...)
|
26
|
+
@pipeline.call(...)
|
27
27
|
|
28
28
|
@pipeline.flush(1)
|
29
29
|
|
@@ -46,6 +46,15 @@ module Async
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
def collect
|
50
|
+
if block_given?
|
51
|
+
flush
|
52
|
+
yield
|
53
|
+
end
|
54
|
+
|
55
|
+
@count.times.map{read_response}
|
56
|
+
end
|
57
|
+
|
49
58
|
def sync
|
50
59
|
@sync ||= Sync.new(self)
|
51
60
|
end
|
@@ -73,15 +82,9 @@ module Async
|
|
73
82
|
end
|
74
83
|
end
|
75
84
|
|
76
|
-
def collect
|
77
|
-
yield
|
78
|
-
|
79
|
-
@count.times.map{read_response}
|
80
|
-
end
|
81
|
-
|
82
85
|
def close
|
83
86
|
flush
|
84
|
-
|
87
|
+
ensure
|
85
88
|
super
|
86
89
|
end
|
87
90
|
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
|
+
|
6
|
+
require 'io/endpoint'
|
7
|
+
require 'io/endpoint/host_endpoint'
|
8
|
+
require 'io/endpoint/ssl_endpoint'
|
9
|
+
|
10
|
+
require_relative 'protocol/resp2'
|
11
|
+
require_relative 'protocol/authenticated'
|
12
|
+
require_relative 'protocol/selected'
|
13
|
+
|
14
|
+
module Async
|
15
|
+
module Redis
|
16
|
+
def self.local_endpoint(**options)
|
17
|
+
Endpoint.local(**options)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Represents a way to connect to a remote Redis server.
|
21
|
+
class Endpoint < ::IO::Endpoint::Generic
|
22
|
+
LOCALHOST = URI.parse("redis://localhost").freeze
|
23
|
+
|
24
|
+
def self.local(**options)
|
25
|
+
self.new(LOCALHOST, **options)
|
26
|
+
end
|
27
|
+
|
28
|
+
SCHEMES = {
|
29
|
+
'redis' => URI::Generic,
|
30
|
+
'rediss' => URI::Generic,
|
31
|
+
}
|
32
|
+
|
33
|
+
def self.parse(string, endpoint = nil, **options)
|
34
|
+
url = URI.parse(string).normalize
|
35
|
+
|
36
|
+
return self.new(url, endpoint, **options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Construct an endpoint with a specified scheme, hostname, optional path, and options.
|
40
|
+
#
|
41
|
+
# @parameter scheme [String] The scheme to use, e.g. "redis" or "rediss".
|
42
|
+
# @parameter hostname [String] The hostname to connect to (or bind to).
|
43
|
+
# @parameter *options [Hash] Additional options, passed to {#initialize}.
|
44
|
+
def self.for(scheme, hostname, credentials: nil, port: nil, database: nil, **options)
|
45
|
+
uri_klass = SCHEMES.fetch(scheme.downcase) do
|
46
|
+
raise ArgumentError, "Unsupported scheme: #{scheme.inspect}"
|
47
|
+
end
|
48
|
+
|
49
|
+
if database
|
50
|
+
path = "/#{database}"
|
51
|
+
end
|
52
|
+
|
53
|
+
self.new(
|
54
|
+
uri_klass.new(scheme, credentials&.join(":"), hostname, port, nil, path, nil, nil, nil).normalize,
|
55
|
+
**options
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Coerce the given object into an endpoint.
|
60
|
+
# @parameter url [String | Endpoint] The URL or endpoint to convert.
|
61
|
+
def self.[](object)
|
62
|
+
if object.is_a?(self)
|
63
|
+
return object
|
64
|
+
else
|
65
|
+
self.parse(object.to_s)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Create a new endpoint.
|
70
|
+
#
|
71
|
+
# @parameter url [URI] The URL to connect to.
|
72
|
+
# @parameter endpoint [Endpoint] The underlying endpoint to use.
|
73
|
+
# @parameter scheme [String] The scheme to use, e.g. "redis" or "rediss".
|
74
|
+
# @parameter hostname [String] The hostname to connect to (or bind to), overrides the URL hostname (used for SNI).
|
75
|
+
# @parameter port [Integer] The port to bind to, overrides the URL port.
|
76
|
+
def initialize(url, endpoint = nil, **options)
|
77
|
+
super(**options)
|
78
|
+
|
79
|
+
raise ArgumentError, "URL must be absolute (include scheme, host): #{url}" unless url.absolute?
|
80
|
+
|
81
|
+
@url = url
|
82
|
+
|
83
|
+
if endpoint
|
84
|
+
@endpoint = self.build_endpoint(endpoint)
|
85
|
+
else
|
86
|
+
@endpoint = nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_url
|
91
|
+
url = @url.dup
|
92
|
+
|
93
|
+
unless default_port?
|
94
|
+
url.port = self.port
|
95
|
+
end
|
96
|
+
|
97
|
+
return url
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_s
|
101
|
+
"\#<#{self.class} #{self.to_url} #{@options}>"
|
102
|
+
end
|
103
|
+
|
104
|
+
def inspect
|
105
|
+
"\#<#{self.class} #{self.to_url} #{@options.inspect}>"
|
106
|
+
end
|
107
|
+
|
108
|
+
attr :url
|
109
|
+
|
110
|
+
def address
|
111
|
+
endpoint.address
|
112
|
+
end
|
113
|
+
|
114
|
+
def secure?
|
115
|
+
['rediss'].include?(self.scheme)
|
116
|
+
end
|
117
|
+
|
118
|
+
def protocol
|
119
|
+
protocol = @options.fetch(:protocol, Protocol::RESP2)
|
120
|
+
|
121
|
+
if credentials = self.credentials
|
122
|
+
protocol = Protocol::Authenticated.new(credentials, protocol)
|
123
|
+
end
|
124
|
+
|
125
|
+
if database = self.database
|
126
|
+
protocol = Protocol::Selected.new(database, protocol)
|
127
|
+
end
|
128
|
+
|
129
|
+
return protocol
|
130
|
+
end
|
131
|
+
|
132
|
+
def default_port
|
133
|
+
6379
|
134
|
+
end
|
135
|
+
|
136
|
+
def default_port?
|
137
|
+
port == default_port
|
138
|
+
end
|
139
|
+
|
140
|
+
def port
|
141
|
+
@options[:port] || @url.port || default_port
|
142
|
+
end
|
143
|
+
|
144
|
+
# The hostname is the server we are connecting to:
|
145
|
+
def hostname
|
146
|
+
@options[:hostname] || @url.hostname
|
147
|
+
end
|
148
|
+
|
149
|
+
def scheme
|
150
|
+
@options[:scheme] || @url.scheme
|
151
|
+
end
|
152
|
+
|
153
|
+
def database
|
154
|
+
@options[:database] || extract_database(@url.path)
|
155
|
+
end
|
156
|
+
|
157
|
+
private def extract_database(path)
|
158
|
+
if path =~ /\/(\d+)$/
|
159
|
+
return $1.to_i
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def credentials
|
164
|
+
@options[:credentials] || extract_userinfo(@url.userinfo)
|
165
|
+
end
|
166
|
+
|
167
|
+
private def extract_userinfo(userinfo)
|
168
|
+
if userinfo
|
169
|
+
credentials = userinfo.split(":").reject(&:empty?)
|
170
|
+
|
171
|
+
if credentials.any?
|
172
|
+
return credentials
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def localhost?
|
178
|
+
@url.hostname =~ /^(.*?\.)?localhost\.?$/
|
179
|
+
end
|
180
|
+
|
181
|
+
# We don't try to validate peer certificates when talking to localhost because they would always be self-signed.
|
182
|
+
def ssl_verify_mode
|
183
|
+
if self.localhost?
|
184
|
+
OpenSSL::SSL::VERIFY_NONE
|
185
|
+
else
|
186
|
+
OpenSSL::SSL::VERIFY_PEER
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def ssl_context
|
191
|
+
@options[:ssl_context] || OpenSSL::SSL::SSLContext.new.tap do |context|
|
192
|
+
context.set_params(
|
193
|
+
verify_mode: self.ssl_verify_mode
|
194
|
+
)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def build_endpoint(endpoint = nil)
|
199
|
+
endpoint ||= tcp_endpoint
|
200
|
+
|
201
|
+
if secure?
|
202
|
+
# Wrap it in SSL:
|
203
|
+
return ::IO::Endpoint::SSLEndpoint.new(endpoint,
|
204
|
+
ssl_context: self.ssl_context,
|
205
|
+
hostname: @url.hostname,
|
206
|
+
timeout: self.timeout,
|
207
|
+
)
|
208
|
+
end
|
209
|
+
|
210
|
+
return endpoint
|
211
|
+
end
|
212
|
+
|
213
|
+
def endpoint
|
214
|
+
@endpoint ||= build_endpoint
|
215
|
+
end
|
216
|
+
|
217
|
+
def endpoint=(endpoint)
|
218
|
+
@endpoint = build_endpoint(endpoint)
|
219
|
+
end
|
220
|
+
|
221
|
+
def bind(*arguments, &block)
|
222
|
+
endpoint.bind(*arguments, &block)
|
223
|
+
end
|
224
|
+
|
225
|
+
def connect(&block)
|
226
|
+
endpoint.connect(&block)
|
227
|
+
end
|
228
|
+
|
229
|
+
def each
|
230
|
+
return to_enum unless block_given?
|
231
|
+
|
232
|
+
self.tcp_endpoint.each do |endpoint|
|
233
|
+
yield self.class.new(@url, endpoint, **@options)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def key
|
238
|
+
[@url, @options]
|
239
|
+
end
|
240
|
+
|
241
|
+
def eql? other
|
242
|
+
self.key.eql? other.key
|
243
|
+
end
|
244
|
+
|
245
|
+
def hash
|
246
|
+
self.key.hash
|
247
|
+
end
|
248
|
+
|
249
|
+
protected
|
250
|
+
|
251
|
+
def tcp_options
|
252
|
+
options = @options.dup
|
253
|
+
|
254
|
+
options.delete(:scheme)
|
255
|
+
options.delete(:port)
|
256
|
+
options.delete(:hostname)
|
257
|
+
options.delete(:ssl_context)
|
258
|
+
options.delete(:protocol)
|
259
|
+
|
260
|
+
return options
|
261
|
+
end
|
262
|
+
|
263
|
+
def tcp_endpoint
|
264
|
+
::IO::Endpoint.tcp(self.hostname, port, **tcp_options)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
|
+
|
6
|
+
require 'protocol/redis'
|
7
|
+
|
8
|
+
module Async
|
9
|
+
module Redis
|
10
|
+
module Protocol
|
11
|
+
# Executes AUTH after the user has established a connection.
|
12
|
+
class Authenticated
|
13
|
+
# Authentication has failed for some reason.
|
14
|
+
class AuthenticationError < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
# Create a new authenticated protocol.
|
18
|
+
#
|
19
|
+
# @parameter credentials [Array] The credentials to use for authentication.
|
20
|
+
# @parameter protocol [Object] The delegated protocol for connecting.
|
21
|
+
def initialize(credentials, protocol = Async::Redis::Protocol::RESP2)
|
22
|
+
@credentials = credentials
|
23
|
+
@protocol = protocol
|
24
|
+
end
|
25
|
+
|
26
|
+
attr :credentials
|
27
|
+
|
28
|
+
# Create a new client and authenticate it.
|
29
|
+
def client(stream)
|
30
|
+
client = @protocol.client(stream)
|
31
|
+
|
32
|
+
client.write_request(["AUTH", *@credentials])
|
33
|
+
response = client.read_response
|
34
|
+
|
35
|
+
if response != "OK"
|
36
|
+
raise AuthenticationError, "Could not authenticate: #{response}"
|
37
|
+
end
|
38
|
+
|
39
|
+
return client
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2018-
|
4
|
+
# Copyright, 2018-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require 'protocol/redis'
|
7
7
|
|
@@ -15,7 +15,7 @@ module Async
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def viable?
|
18
|
-
@stream.
|
18
|
+
@stream.readable?
|
19
19
|
end
|
20
20
|
|
21
21
|
def reusable?
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
|
+
|
6
|
+
require 'protocol/redis'
|
7
|
+
|
8
|
+
module Async
|
9
|
+
module Redis
|
10
|
+
module Protocol
|
11
|
+
# Executes AUTH after the user has established a connection.
|
12
|
+
class Selected
|
13
|
+
# Authentication has failed for some reason.
|
14
|
+
class SelectionError < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
# Create a new authenticated protocol.
|
18
|
+
#
|
19
|
+
# @parameter index [Integer] The database index to select.
|
20
|
+
# @parameter protocol [Object] The delegated protocol for connecting.
|
21
|
+
def initialize(index, protocol = Async::Redis::Protocol::RESP2)
|
22
|
+
@index = index
|
23
|
+
@protocol = protocol
|
24
|
+
end
|
25
|
+
|
26
|
+
attr :index
|
27
|
+
|
28
|
+
# Create a new client and authenticate it.
|
29
|
+
def client(stream)
|
30
|
+
client = @protocol.client(stream)
|
31
|
+
|
32
|
+
client.write_request(["SELECT", @index])
|
33
|
+
response = client.read_response
|
34
|
+
|
35
|
+
if response != "OK"
|
36
|
+
raise SelectionError, "Could not select database: #{response}"
|
37
|
+
end
|
38
|
+
|
39
|
+
return client
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -2,91 +2,100 @@
|
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2020, by David Ortiz.
|
5
|
-
# Copyright, 2023, by Samuel Williams.
|
5
|
+
# Copyright, 2023-2024, by Samuel Williams.
|
6
|
+
|
7
|
+
require 'io/stream'
|
6
8
|
|
7
9
|
module Async
|
8
10
|
module Redis
|
9
11
|
class SentinelsClient < Client
|
10
12
|
def initialize(master_name, sentinels, role = :master, protocol = Protocol::RESP2, **options)
|
11
13
|
@master_name = master_name
|
14
|
+
|
12
15
|
@sentinel_endpoints = sentinels.map do |sentinel|
|
13
|
-
|
16
|
+
::IO::Endpoint.tcp(sentinel[:host], sentinel[:port])
|
14
17
|
end
|
18
|
+
|
15
19
|
@role = role
|
16
|
-
|
17
20
|
@protocol = protocol
|
18
21
|
@pool = connect(**options)
|
19
22
|
end
|
20
|
-
|
23
|
+
|
21
24
|
private
|
22
|
-
|
25
|
+
|
23
26
|
# Override the parent method. The only difference is that this one needs
|
24
27
|
# to resolve the master/slave address.
|
25
28
|
def connect(**options)
|
26
29
|
Async::Pool::Controller.wrap(**options) do
|
27
30
|
endpoint = resolve_address
|
28
31
|
peer = endpoint.connect
|
29
|
-
stream = IO::Stream
|
30
|
-
|
32
|
+
stream = ::IO::Stream(peer)
|
33
|
+
|
31
34
|
@protocol.client(stream)
|
32
35
|
end
|
33
36
|
end
|
34
|
-
|
37
|
+
|
35
38
|
def resolve_address
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
case @role
|
40
|
+
when :master
|
41
|
+
resolve_master
|
42
|
+
when :slave
|
43
|
+
resolve_slave
|
44
|
+
else
|
45
|
+
raise ArgumentError, "Unknown instance role #{@role}"
|
46
|
+
end => address
|
47
|
+
|
42
48
|
address or raise RuntimeError, "Unable to fetch #{@role} via Sentinel."
|
43
49
|
end
|
44
|
-
|
50
|
+
|
45
51
|
def resolve_master
|
46
52
|
@sentinel_endpoints.each do |sentinel_endpoint|
|
47
|
-
client = Client.new(sentinel_endpoint)
|
48
|
-
|
53
|
+
client = Client.new(sentinel_endpoint, protocol: @protocol)
|
54
|
+
|
49
55
|
begin
|
50
56
|
address = client.call('sentinel', 'get-master-addr-by-name', @master_name)
|
51
57
|
rescue Errno::ECONNREFUSED
|
52
58
|
next
|
53
59
|
end
|
54
|
-
|
55
|
-
return
|
60
|
+
|
61
|
+
return ::IO::Endpoint.tcp(address[0], address[1]) if address
|
56
62
|
end
|
57
|
-
|
63
|
+
|
58
64
|
nil
|
59
65
|
end
|
60
|
-
|
66
|
+
|
61
67
|
def resolve_slave
|
62
68
|
@sentinel_endpoints.each do |sentinel_endpoint|
|
63
|
-
client = Client.new(sentinel_endpoint)
|
64
|
-
|
69
|
+
client = Client.new(sentinel_endpoint, protocol: @protocol)
|
70
|
+
|
65
71
|
begin
|
66
72
|
reply = client.call('sentinel', 'slaves', @master_name)
|
67
73
|
rescue Errno::ECONNREFUSED
|
68
74
|
next
|
69
75
|
end
|
70
|
-
|
76
|
+
|
71
77
|
slaves = available_slaves(reply)
|
72
78
|
next if slaves.empty?
|
73
|
-
|
79
|
+
|
74
80
|
slave = select_slave(slaves)
|
75
|
-
return
|
81
|
+
return ::IO::Endpoint.tcp(slave['ip'], slave['port'])
|
76
82
|
end
|
77
|
-
|
83
|
+
|
78
84
|
nil
|
79
85
|
end
|
80
|
-
|
81
|
-
def available_slaves(
|
86
|
+
|
87
|
+
def available_slaves(reply)
|
82
88
|
# The reply is an array with the format: [field1, value1, field2,
|
83
89
|
# value2, etc.].
|
84
90
|
# When a slave is marked as down by the sentinel, the "flags" field
|
85
91
|
# (comma-separated array) contains the "s_down" value.
|
86
|
-
|
87
|
-
|
92
|
+
slaves = reply.map{|fields| fields.each_slice(2).to_h}
|
93
|
+
|
94
|
+
slaves.reject do |slave|
|
95
|
+
slave['flags'].split(',').include?('s_down')
|
96
|
+
end
|
88
97
|
end
|
89
|
-
|
98
|
+
|
90
99
|
def select_slave(available_slaves)
|
91
100
|
available_slaves.sample
|
92
101
|
end
|
data/lib/async/redis/version.rb
CHANGED
data/license.md
CHANGED
data/readme.md
CHANGED
@@ -1,111 +1,14 @@
|
|
1
1
|
# Async::Redis
|
2
2
|
|
3
|
-
An asynchronous client for Redis including TLS. Support for streaming requests and responses. Built on top of [async](https://github.com/socketry/async)
|
3
|
+
An asynchronous client for Redis including TLS. Support for streaming requests and responses. Built on top of [async](https://github.com/socketry/async).
|
4
4
|
|
5
5
|
[![Development Status](https://github.com/socketry/async-redis/workflows/Test/badge.svg)](https://github.com/socketry/async-redis/actions?workflow=Test)
|
6
6
|
|
7
|
-
## Installation
|
8
|
-
|
9
|
-
``` shell
|
10
|
-
$ bundle add async-redis
|
11
|
-
```
|
12
|
-
|
13
7
|
## Usage
|
14
8
|
|
15
|
-
|
16
|
-
|
17
|
-
``` ruby
|
18
|
-
require 'async/redis'
|
19
|
-
|
20
|
-
endpoint = Async::Redis.local_endpoint
|
21
|
-
client = Async::Redis::Client.new(endpoint)
|
22
|
-
|
23
|
-
Async do
|
24
|
-
pp client.info
|
25
|
-
ensure
|
26
|
-
client.close
|
27
|
-
end
|
28
|
-
```
|
29
|
-
|
30
|
-
### Connecting to Redis SSL Endpoint
|
31
|
-
|
32
|
-
This example demonstrates parsing an environment variable with a `redis://` or SSL `rediss://` scheme, and demonstrates how you can specify SSL parameters on the SSLContext object.
|
33
|
-
|
34
|
-
``` ruby
|
35
|
-
require 'async/redis'
|
36
|
-
|
37
|
-
def make_redis_endpoint(uri)
|
38
|
-
tcp_endpoint = Async::IO::Endpoint.tcp(uri.hostname, uri.port)
|
39
|
-
case uri.scheme
|
40
|
-
when 'redis'
|
41
|
-
tcp_endpoint
|
42
|
-
when 'rediss'
|
43
|
-
ssl_context = OpenSSL::SSL::SSLContext.new
|
44
|
-
ssl_context.set_params(
|
45
|
-
ca_file: "/path/to/ca.crt",
|
46
|
-
cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
|
47
|
-
key: OpenSSL::PKey::RSA.new(File.read("client.key")),
|
48
|
-
)
|
49
|
-
Async::IO::SSLEndpoint.new(tcp_endpoint, ssl_context: ssl_context)
|
50
|
-
else
|
51
|
-
raise ArgumentError
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
endpoint = make_redis_endpoint(URI(ENV['REDIS_URL']))
|
56
|
-
client = Async::Redis::Client.new(endpoint)
|
57
|
-
|
58
|
-
# ...
|
59
|
-
```
|
9
|
+
Please see the [project documentation](https://socketry.github.io/async-redis/) for more details.
|
60
10
|
|
61
|
-
|
62
|
-
|
63
|
-
``` ruby
|
64
|
-
require 'async'
|
65
|
-
require 'async/redis'
|
66
|
-
|
67
|
-
endpoint = Async::Redis.local_endpoint
|
68
|
-
client = Async::Redis::Client.new(endpoint)
|
69
|
-
|
70
|
-
Async do
|
71
|
-
client.set('X', 10)
|
72
|
-
pp client.get('X')
|
73
|
-
ensure
|
74
|
-
client.close
|
75
|
-
end
|
76
|
-
```
|
77
|
-
|
78
|
-
### Subscriptions
|
79
|
-
|
80
|
-
``` ruby
|
81
|
-
require 'async'
|
82
|
-
require 'async/redis'
|
83
|
-
|
84
|
-
endpoint = Async::Redis.local_endpoint
|
85
|
-
client = Async::Redis::Client.new(endpoint)
|
86
|
-
|
87
|
-
Async do |task|
|
88
|
-
condition = Async::Condition.new
|
89
|
-
|
90
|
-
publisher = task.async do
|
91
|
-
condition.wait
|
92
|
-
|
93
|
-
client.publish 'status.frontend', 'good'
|
94
|
-
end
|
95
|
-
|
96
|
-
subscriber = task.async do
|
97
|
-
client.subscribe 'status.frontend' do |context|
|
98
|
-
condition.signal # We are waiting for messages.
|
99
|
-
|
100
|
-
type, name, message = context.listen
|
101
|
-
|
102
|
-
pp type, name, message
|
103
|
-
end
|
104
|
-
end
|
105
|
-
ensure
|
106
|
-
client.close
|
107
|
-
end
|
108
|
-
```
|
11
|
+
- [Getting Started](https://socketry.github.io/async-redis/guides/getting-started/index) - This guide explains how to use the `async-redis` gem to connect to a Redis server and perform basic operations.
|
109
12
|
|
110
13
|
## Contributing
|
111
14
|
|
@@ -116,3 +19,11 @@ We welcome contributions to this project.
|
|
116
19
|
3. Commit your changes (`git commit -am 'Add some feature'`).
|
117
20
|
4. Push to the branch (`git push origin my-new-feature`).
|
118
21
|
5. Create new Pull Request.
|
22
|
+
|
23
|
+
### Developer Certificate of Origin
|
24
|
+
|
25
|
+
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
26
|
+
|
27
|
+
### Community Guidelines
|
28
|
+
|
29
|
+
This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -48,70 +48,78 @@ cert_chain:
|
|
48
48
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
49
49
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
50
50
|
-----END CERTIFICATE-----
|
51
|
-
date:
|
51
|
+
date: 2024-08-16 00:00:00.000000000 Z
|
52
52
|
dependencies:
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
54
|
name: async
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
|
-
- - "
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '1.8'
|
60
|
-
- - "<"
|
57
|
+
- - "~>"
|
61
58
|
- !ruby/object:Gem::Version
|
62
|
-
version: '
|
59
|
+
version: '2.10'
|
63
60
|
type: :runtime
|
64
61
|
prerelease: false
|
65
62
|
version_requirements: !ruby/object:Gem::Requirement
|
66
63
|
requirements:
|
67
|
-
- - "
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '2.10'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: async-pool
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
68
72
|
- !ruby/object:Gem::Version
|
69
|
-
version: '
|
70
|
-
|
73
|
+
version: '0.2'
|
74
|
+
type: :runtime
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
71
79
|
- !ruby/object:Gem::Version
|
72
|
-
version: '
|
80
|
+
version: '0.2'
|
73
81
|
- !ruby/object:Gem::Dependency
|
74
|
-
name:
|
82
|
+
name: io-endpoint
|
75
83
|
requirement: !ruby/object:Gem::Requirement
|
76
84
|
requirements:
|
77
85
|
- - "~>"
|
78
86
|
- !ruby/object:Gem::Version
|
79
|
-
version: '
|
87
|
+
version: '0.10'
|
80
88
|
type: :runtime
|
81
89
|
prerelease: false
|
82
90
|
version_requirements: !ruby/object:Gem::Requirement
|
83
91
|
requirements:
|
84
92
|
- - "~>"
|
85
93
|
- !ruby/object:Gem::Version
|
86
|
-
version: '
|
94
|
+
version: '0.10'
|
87
95
|
- !ruby/object:Gem::Dependency
|
88
|
-
name:
|
96
|
+
name: io-stream
|
89
97
|
requirement: !ruby/object:Gem::Requirement
|
90
98
|
requirements:
|
91
99
|
- - "~>"
|
92
100
|
- !ruby/object:Gem::Version
|
93
|
-
version: '0.
|
101
|
+
version: '0.4'
|
94
102
|
type: :runtime
|
95
103
|
prerelease: false
|
96
104
|
version_requirements: !ruby/object:Gem::Requirement
|
97
105
|
requirements:
|
98
106
|
- - "~>"
|
99
107
|
- !ruby/object:Gem::Version
|
100
|
-
version: '0.
|
108
|
+
version: '0.4'
|
101
109
|
- !ruby/object:Gem::Dependency
|
102
110
|
name: protocol-redis
|
103
111
|
requirement: !ruby/object:Gem::Requirement
|
104
112
|
requirements:
|
105
113
|
- - "~>"
|
106
114
|
- !ruby/object:Gem::Version
|
107
|
-
version: 0.
|
115
|
+
version: '0.9'
|
108
116
|
type: :runtime
|
109
117
|
prerelease: false
|
110
118
|
version_requirements: !ruby/object:Gem::Requirement
|
111
119
|
requirements:
|
112
120
|
- - "~>"
|
113
121
|
- !ruby/object:Gem::Version
|
114
|
-
version: 0.
|
122
|
+
version: '0.9'
|
115
123
|
description:
|
116
124
|
email:
|
117
125
|
executables: []
|
@@ -124,8 +132,11 @@ files:
|
|
124
132
|
- lib/async/redis/context/pipeline.rb
|
125
133
|
- lib/async/redis/context/subscribe.rb
|
126
134
|
- lib/async/redis/context/transaction.rb
|
135
|
+
- lib/async/redis/endpoint.rb
|
127
136
|
- lib/async/redis/key.rb
|
137
|
+
- lib/async/redis/protocol/authenticated.rb
|
128
138
|
- lib/async/redis/protocol/resp2.rb
|
139
|
+
- lib/async/redis/protocol/selected.rb
|
129
140
|
- lib/async/redis/sentinels.rb
|
130
141
|
- lib/async/redis/version.rb
|
131
142
|
- license.md
|
@@ -133,7 +144,9 @@ files:
|
|
133
144
|
homepage: https://github.com/socketry/async-redis
|
134
145
|
licenses:
|
135
146
|
- MIT
|
136
|
-
metadata:
|
147
|
+
metadata:
|
148
|
+
documentation_uri: https://socketry.github.io/async-redis/
|
149
|
+
source_code_uri: https://github.com/socketry/async-redis.git
|
137
150
|
post_install_message:
|
138
151
|
rdoc_options: []
|
139
152
|
require_paths:
|
@@ -142,14 +155,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
142
155
|
requirements:
|
143
156
|
- - ">="
|
144
157
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
158
|
+
version: '3.1'
|
146
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
160
|
requirements:
|
148
161
|
- - ">="
|
149
162
|
- !ruby/object:Gem::Version
|
150
163
|
version: '0'
|
151
164
|
requirements: []
|
152
|
-
rubygems_version: 3.
|
165
|
+
rubygems_version: 3.5.11
|
153
166
|
signing_key:
|
154
167
|
specification_version: 4
|
155
168
|
summary: A Redis client library.
|
metadata.gz.sig
CHANGED
Binary file
|