redis-client 0.1.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.
- checksums.yaml +7 -0
- data/.rubocop.yml +190 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +67 -0
- data/LICENSE.md +21 -0
- data/README.md +347 -0
- data/Rakefile +86 -0
- data/ext/redis_client/hiredis/extconf.rb +54 -0
- data/ext/redis_client/hiredis/hiredis_connection.c +696 -0
- data/ext/redis_client/hiredis/vendor/.gitignore +9 -0
- data/ext/redis_client/hiredis/vendor/.travis.yml +131 -0
- data/ext/redis_client/hiredis/vendor/CHANGELOG.md +364 -0
- data/ext/redis_client/hiredis/vendor/CMakeLists.txt +165 -0
- data/ext/redis_client/hiredis/vendor/COPYING +29 -0
- data/ext/redis_client/hiredis/vendor/Makefile +308 -0
- data/ext/redis_client/hiredis/vendor/README.md +664 -0
- data/ext/redis_client/hiredis/vendor/adapters/ae.h +130 -0
- data/ext/redis_client/hiredis/vendor/adapters/glib.h +156 -0
- data/ext/redis_client/hiredis/vendor/adapters/ivykis.h +84 -0
- data/ext/redis_client/hiredis/vendor/adapters/libev.h +179 -0
- data/ext/redis_client/hiredis/vendor/adapters/libevent.h +175 -0
- data/ext/redis_client/hiredis/vendor/adapters/libuv.h +117 -0
- data/ext/redis_client/hiredis/vendor/adapters/macosx.h +115 -0
- data/ext/redis_client/hiredis/vendor/adapters/qt.h +135 -0
- data/ext/redis_client/hiredis/vendor/alloc.c +86 -0
- data/ext/redis_client/hiredis/vendor/alloc.h +91 -0
- data/ext/redis_client/hiredis/vendor/appveyor.yml +24 -0
- data/ext/redis_client/hiredis/vendor/async.c +887 -0
- data/ext/redis_client/hiredis/vendor/async.h +147 -0
- data/ext/redis_client/hiredis/vendor/async_private.h +75 -0
- data/ext/redis_client/hiredis/vendor/dict.c +352 -0
- data/ext/redis_client/hiredis/vendor/dict.h +126 -0
- data/ext/redis_client/hiredis/vendor/fmacros.h +12 -0
- data/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in +13 -0
- data/ext/redis_client/hiredis/vendor/hiredis.c +1174 -0
- data/ext/redis_client/hiredis/vendor/hiredis.h +336 -0
- data/ext/redis_client/hiredis/vendor/hiredis.pc.in +12 -0
- data/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in +13 -0
- data/ext/redis_client/hiredis/vendor/hiredis_ssl.h +157 -0
- data/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in +12 -0
- data/ext/redis_client/hiredis/vendor/net.c +612 -0
- data/ext/redis_client/hiredis/vendor/net.h +56 -0
- data/ext/redis_client/hiredis/vendor/read.c +739 -0
- data/ext/redis_client/hiredis/vendor/read.h +129 -0
- data/ext/redis_client/hiredis/vendor/sds.c +1289 -0
- data/ext/redis_client/hiredis/vendor/sds.h +278 -0
- data/ext/redis_client/hiredis/vendor/sdsalloc.h +44 -0
- data/ext/redis_client/hiredis/vendor/sockcompat.c +248 -0
- data/ext/redis_client/hiredis/vendor/sockcompat.h +92 -0
- data/ext/redis_client/hiredis/vendor/ssl.c +544 -0
- data/ext/redis_client/hiredis/vendor/test.c +1401 -0
- data/ext/redis_client/hiredis/vendor/test.sh +78 -0
- data/ext/redis_client/hiredis/vendor/win32.h +56 -0
- data/lib/redis-client.rb +3 -0
- data/lib/redis_client/buffered_io.rb +149 -0
- data/lib/redis_client/config.rb +174 -0
- data/lib/redis_client/connection.rb +86 -0
- data/lib/redis_client/hiredis_connection.rb +78 -0
- data/lib/redis_client/pooled.rb +86 -0
- data/lib/redis_client/resp3.rb +225 -0
- data/lib/redis_client/sentinel_config.rb +134 -0
- data/lib/redis_client/version.rb +5 -0
- data/lib/redis_client.rb +438 -0
- data/redis-client.gemspec +34 -0
- metadata +125 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/bin/sh -ue
|
2
|
+
|
3
|
+
REDIS_SERVER=${REDIS_SERVER:-redis-server}
|
4
|
+
REDIS_PORT=${REDIS_PORT:-56379}
|
5
|
+
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
|
6
|
+
TEST_SSL=${TEST_SSL:-0}
|
7
|
+
SKIPS_AS_FAILS=${SKIPS_AS_FAILS-:0}
|
8
|
+
SSL_TEST_ARGS=
|
9
|
+
SKIPS_ARG=
|
10
|
+
|
11
|
+
tmpdir=$(mktemp -d)
|
12
|
+
PID_FILE=${tmpdir}/hiredis-test-redis.pid
|
13
|
+
SOCK_FILE=${tmpdir}/hiredis-test-redis.sock
|
14
|
+
|
15
|
+
if [ "$TEST_SSL" = "1" ]; then
|
16
|
+
SSL_CA_CERT=${tmpdir}/ca.crt
|
17
|
+
SSL_CA_KEY=${tmpdir}/ca.key
|
18
|
+
SSL_CERT=${tmpdir}/redis.crt
|
19
|
+
SSL_KEY=${tmpdir}/redis.key
|
20
|
+
|
21
|
+
openssl genrsa -out ${tmpdir}/ca.key 4096
|
22
|
+
openssl req \
|
23
|
+
-x509 -new -nodes -sha256 \
|
24
|
+
-key ${SSL_CA_KEY} \
|
25
|
+
-days 3650 \
|
26
|
+
-subj '/CN=Hiredis Test CA' \
|
27
|
+
-out ${SSL_CA_CERT}
|
28
|
+
openssl genrsa -out ${SSL_KEY} 2048
|
29
|
+
openssl req \
|
30
|
+
-new -sha256 \
|
31
|
+
-key ${SSL_KEY} \
|
32
|
+
-subj '/CN=Hiredis Test Cert' | \
|
33
|
+
openssl x509 \
|
34
|
+
-req -sha256 \
|
35
|
+
-CA ${SSL_CA_CERT} \
|
36
|
+
-CAkey ${SSL_CA_KEY} \
|
37
|
+
-CAserial ${tmpdir}/ca.txt \
|
38
|
+
-CAcreateserial \
|
39
|
+
-days 365 \
|
40
|
+
-out ${SSL_CERT}
|
41
|
+
|
42
|
+
SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}"
|
43
|
+
fi
|
44
|
+
|
45
|
+
cleanup() {
|
46
|
+
set +e
|
47
|
+
kill $(cat ${PID_FILE})
|
48
|
+
rm -rf ${tmpdir}
|
49
|
+
}
|
50
|
+
trap cleanup INT TERM EXIT
|
51
|
+
|
52
|
+
cat > ${tmpdir}/redis.conf <<EOF
|
53
|
+
daemonize yes
|
54
|
+
pidfile ${PID_FILE}
|
55
|
+
port ${REDIS_PORT}
|
56
|
+
bind 127.0.0.1
|
57
|
+
unixsocket ${SOCK_FILE}
|
58
|
+
EOF
|
59
|
+
|
60
|
+
if [ "$TEST_SSL" = "1" ]; then
|
61
|
+
cat >> ${tmpdir}/redis.conf <<EOF
|
62
|
+
tls-port ${REDIS_SSL_PORT}
|
63
|
+
tls-ca-cert-file ${SSL_CA_CERT}
|
64
|
+
tls-cert-file ${SSL_CERT}
|
65
|
+
tls-key-file ${SSL_KEY}
|
66
|
+
EOF
|
67
|
+
fi
|
68
|
+
|
69
|
+
cat ${tmpdir}/redis.conf
|
70
|
+
${REDIS_SERVER} ${tmpdir}/redis.conf
|
71
|
+
|
72
|
+
# Wait until we detect the unix socket
|
73
|
+
while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done
|
74
|
+
|
75
|
+
# Treat skips as failures if directed
|
76
|
+
[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="--skips-as-fails"
|
77
|
+
|
78
|
+
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#ifndef _WIN32_HELPER_INCLUDE
|
2
|
+
#define _WIN32_HELPER_INCLUDE
|
3
|
+
#ifdef _MSC_VER
|
4
|
+
|
5
|
+
#include <winsock2.h> /* for struct timeval */
|
6
|
+
|
7
|
+
#ifndef inline
|
8
|
+
#define inline __inline
|
9
|
+
#endif
|
10
|
+
|
11
|
+
#ifndef strcasecmp
|
12
|
+
#define strcasecmp stricmp
|
13
|
+
#endif
|
14
|
+
|
15
|
+
#ifndef strncasecmp
|
16
|
+
#define strncasecmp strnicmp
|
17
|
+
#endif
|
18
|
+
|
19
|
+
#ifndef va_copy
|
20
|
+
#define va_copy(d,s) ((d) = (s))
|
21
|
+
#endif
|
22
|
+
|
23
|
+
#ifndef snprintf
|
24
|
+
#define snprintf c99_snprintf
|
25
|
+
|
26
|
+
__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
|
27
|
+
{
|
28
|
+
int count = -1;
|
29
|
+
|
30
|
+
if (size != 0)
|
31
|
+
count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
|
32
|
+
if (count == -1)
|
33
|
+
count = _vscprintf(format, ap);
|
34
|
+
|
35
|
+
return count;
|
36
|
+
}
|
37
|
+
|
38
|
+
__inline int c99_snprintf(char* str, size_t size, const char* format, ...)
|
39
|
+
{
|
40
|
+
int count;
|
41
|
+
va_list ap;
|
42
|
+
|
43
|
+
va_start(ap, format);
|
44
|
+
count = c99_vsnprintf(str, size, format, ap);
|
45
|
+
va_end(ap);
|
46
|
+
|
47
|
+
return count;
|
48
|
+
}
|
49
|
+
#endif
|
50
|
+
#endif /* _MSC_VER */
|
51
|
+
|
52
|
+
#ifdef _WIN32
|
53
|
+
#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
|
54
|
+
#endif /* _WIN32 */
|
55
|
+
|
56
|
+
#endif /* _WIN32_HELPER_INCLUDE */
|
data/lib/redis-client.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "io/wait" unless IO.method_defined?(:wait_readable) && IO.method_defined?(:wait_writable)
|
4
|
+
|
5
|
+
class RedisClient
|
6
|
+
class BufferedIO
|
7
|
+
EOL = "\r\n".b.freeze
|
8
|
+
EOL_SIZE = EOL.bytesize
|
9
|
+
|
10
|
+
def initialize(io, read_timeout:, write_timeout:, chunk_size: 4096)
|
11
|
+
@io = io
|
12
|
+
@buffer = "".b
|
13
|
+
@offset = 0
|
14
|
+
@chunk_size = chunk_size
|
15
|
+
@read_timeout = read_timeout
|
16
|
+
@write_timeout = write_timeout
|
17
|
+
@blocking_reads = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def close
|
21
|
+
@io.to_io.close
|
22
|
+
end
|
23
|
+
|
24
|
+
def closed?
|
25
|
+
@io.to_io.closed?
|
26
|
+
end
|
27
|
+
|
28
|
+
def eof?
|
29
|
+
@offset >= @buffer.bytesize && @io.eof?
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_timeout(new_timeout)
|
33
|
+
new_timeout = false if new_timeout == 0
|
34
|
+
|
35
|
+
previous_read_timeout = @read_timeout
|
36
|
+
previous_blocking_reads = @blocking_reads
|
37
|
+
|
38
|
+
if new_timeout
|
39
|
+
@read_timeout = new_timeout
|
40
|
+
else
|
41
|
+
@blocking_reads = true
|
42
|
+
end
|
43
|
+
|
44
|
+
begin
|
45
|
+
yield
|
46
|
+
ensure
|
47
|
+
@read_timeout = previous_read_timeout
|
48
|
+
@blocking_reads = previous_blocking_reads
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def skip(offset)
|
53
|
+
ensure_remaining(offset)
|
54
|
+
@offset += offset
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def write(string)
|
59
|
+
total = remaining = string.bytesize
|
60
|
+
loop do
|
61
|
+
case bytes_written = @io.write_nonblock(string, exception: false)
|
62
|
+
when Integer
|
63
|
+
remaining -= bytes_written
|
64
|
+
if remaining > 0
|
65
|
+
string = string.byteslice(bytes_written..-1)
|
66
|
+
else
|
67
|
+
return total
|
68
|
+
end
|
69
|
+
when :wait_readable
|
70
|
+
@io.to_io.wait_readable(@read_timeout) or raise ReadTimeoutError
|
71
|
+
when :wait_writable
|
72
|
+
@io.to_io.wait_writable(@write_timeout) or raise WriteTimeoutError
|
73
|
+
when nil
|
74
|
+
raise Errno::ECONNRESET
|
75
|
+
else
|
76
|
+
raise "Unexpected `write_nonblock` return: #{bytes.inspect}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def getbyte
|
82
|
+
ensure_remaining(1)
|
83
|
+
byte = @buffer.getbyte(@offset)
|
84
|
+
@offset += 1
|
85
|
+
byte
|
86
|
+
end
|
87
|
+
|
88
|
+
def gets_chomp
|
89
|
+
fill_buffer(false) if @offset >= @buffer.bytesize
|
90
|
+
until eol_index = @buffer.index(EOL, @offset)
|
91
|
+
fill_buffer(false)
|
92
|
+
end
|
93
|
+
|
94
|
+
line = @buffer.byteslice(@offset, eol_index - @offset)
|
95
|
+
@offset = eol_index + EOL_SIZE
|
96
|
+
line
|
97
|
+
end
|
98
|
+
|
99
|
+
def read_chomp(bytes)
|
100
|
+
ensure_remaining(bytes + EOL_SIZE)
|
101
|
+
str = @buffer.byteslice(@offset, bytes)
|
102
|
+
@offset += bytes + EOL_SIZE
|
103
|
+
str
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def ensure_remaining(bytes)
|
109
|
+
needed = bytes - (@buffer.bytesize - @offset)
|
110
|
+
if needed > 0
|
111
|
+
fill_buffer(true, needed)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def fill_buffer(strict, size = @chunk_size)
|
116
|
+
remaining = size
|
117
|
+
empty_buffer = @offset >= @buffer.bytesize
|
118
|
+
|
119
|
+
loop do
|
120
|
+
bytes = if empty_buffer
|
121
|
+
@io.read_nonblock([remaining, @chunk_size].max, @buffer, exception: false)
|
122
|
+
else
|
123
|
+
@io.read_nonblock([remaining, @chunk_size].max, exception: false)
|
124
|
+
end
|
125
|
+
case bytes
|
126
|
+
when String
|
127
|
+
if empty_buffer
|
128
|
+
@offset = 0
|
129
|
+
empty_buffer = false
|
130
|
+
else
|
131
|
+
@buffer << bytes
|
132
|
+
end
|
133
|
+
remaining -= bytes.bytesize
|
134
|
+
return if !strict || remaining <= 0
|
135
|
+
when :wait_readable
|
136
|
+
unless @io.to_io.wait_readable(@read_timeout)
|
137
|
+
raise ReadTimeoutError unless @blocking_reads
|
138
|
+
end
|
139
|
+
when :wait_writable
|
140
|
+
@io.to_io.wait_writable(@write_timeout) or raise WriteTimeoutError
|
141
|
+
when nil
|
142
|
+
raise Errno::ECONNRESET
|
143
|
+
else
|
144
|
+
raise "Unexpected `read_nonblock` return: #{bytes.inspect}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
class RedisClient
|
6
|
+
class Config
|
7
|
+
DEFAULT_TIMEOUT = 1.0
|
8
|
+
DEFAULT_HOST = "localhost"
|
9
|
+
DEFAULT_PORT = 6379
|
10
|
+
DEFAULT_USERNAME = "default"
|
11
|
+
DEFAULT_DB = 0
|
12
|
+
|
13
|
+
module Common
|
14
|
+
attr_reader :db, :username, :password, :id, :ssl, :ssl_params,
|
15
|
+
:connect_timeout, :read_timeout, :write_timeout, :driver, :connection_prelude
|
16
|
+
|
17
|
+
alias_method :ssl?, :ssl
|
18
|
+
|
19
|
+
def initialize(
|
20
|
+
username: nil,
|
21
|
+
password: nil,
|
22
|
+
db: nil,
|
23
|
+
id: nil,
|
24
|
+
timeout: DEFAULT_TIMEOUT,
|
25
|
+
read_timeout: timeout,
|
26
|
+
write_timeout: timeout,
|
27
|
+
connect_timeout: timeout,
|
28
|
+
ssl: nil,
|
29
|
+
ssl_params: nil,
|
30
|
+
driver: :ruby,
|
31
|
+
reconnect_attempts: false
|
32
|
+
)
|
33
|
+
@username = username || DEFAULT_USERNAME
|
34
|
+
@password = password
|
35
|
+
@db = db || DEFAULT_DB
|
36
|
+
@id = id
|
37
|
+
@ssl = ssl || false
|
38
|
+
|
39
|
+
@ssl_params = ssl_params
|
40
|
+
@connect_timeout = connect_timeout
|
41
|
+
@read_timeout = read_timeout
|
42
|
+
@write_timeout = write_timeout
|
43
|
+
|
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
|
55
|
+
|
56
|
+
reconnect_attempts = Array.new(reconnect_attempts, 0).freeze if reconnect_attempts.is_a?(Integer)
|
57
|
+
@reconnect_attempts = reconnect_attempts
|
58
|
+
|
59
|
+
@connection_prelude = build_connection_prelude
|
60
|
+
end
|
61
|
+
|
62
|
+
def sentinel?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
def new_pool(**kwargs)
|
67
|
+
kwargs[:timeout] ||= DEFAULT_TIMEOUT
|
68
|
+
Pooled.new(self, **kwargs)
|
69
|
+
end
|
70
|
+
|
71
|
+
def new_client(**kwargs)
|
72
|
+
RedisClient.new(self, **kwargs)
|
73
|
+
end
|
74
|
+
|
75
|
+
def retry_connecting?(attempt, _error)
|
76
|
+
if @reconnect_attempts
|
77
|
+
if (pause = @reconnect_attempts[attempt])
|
78
|
+
if pause > 0
|
79
|
+
sleep(pause)
|
80
|
+
end
|
81
|
+
return true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
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
|
+
)
|
95
|
+
end
|
96
|
+
|
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
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def build_connection_prelude
|
127
|
+
prelude = []
|
128
|
+
prelude << if @password
|
129
|
+
["HELLO", "3", "AUTH", @username, @password]
|
130
|
+
else
|
131
|
+
["HELLO", "3"]
|
132
|
+
end
|
133
|
+
|
134
|
+
if @db && @db != 0
|
135
|
+
prelude << ["SELECT", @db.to_s]
|
136
|
+
end
|
137
|
+
prelude.freeze
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
include Common
|
142
|
+
|
143
|
+
attr_reader :host, :port, :path
|
144
|
+
|
145
|
+
def initialize(
|
146
|
+
url: nil,
|
147
|
+
host: nil,
|
148
|
+
port: nil,
|
149
|
+
path: nil,
|
150
|
+
**kwargs
|
151
|
+
)
|
152
|
+
uri = url && URI.parse(url)
|
153
|
+
if uri
|
154
|
+
kwargs[:ssl] = uri.scheme == "rediss" unless kwargs.key?(:ssl)
|
155
|
+
|
156
|
+
kwargs[:username] ||= uri.user && uri.password
|
157
|
+
|
158
|
+
kwargs[:password] ||= if uri.user && !uri.password
|
159
|
+
URI.decode_www_form_component(uri.user)
|
160
|
+
elsif uri&.user && uri&.password
|
161
|
+
URI.decode_www_form_component(uri.password)
|
162
|
+
end
|
163
|
+
|
164
|
+
kwargs[:db] ||= Integer(uri.path.delete_prefix("/")) if uri.path && !uri.path.empty?
|
165
|
+
end
|
166
|
+
|
167
|
+
super(**kwargs)
|
168
|
+
|
169
|
+
@host = host || uri&.host || DEFAULT_HOST
|
170
|
+
@port = port || uri&.port || DEFAULT_PORT
|
171
|
+
@path = path
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
require "openssl"
|
5
|
+
require "redis_client/buffered_io"
|
6
|
+
|
7
|
+
class RedisClient
|
8
|
+
class Connection
|
9
|
+
def initialize(config, connect_timeout:, read_timeout:, write_timeout:)
|
10
|
+
socket = if config.path
|
11
|
+
UNIXSocket.new(config.path)
|
12
|
+
else
|
13
|
+
sock = Socket.tcp(config.host, config.port, connect_timeout: connect_timeout)
|
14
|
+
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
15
|
+
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
16
|
+
sock
|
17
|
+
end
|
18
|
+
|
19
|
+
if config.ssl
|
20
|
+
socket = OpenSSL::SSL::SSLSocket.new(socket, config.openssl_context)
|
21
|
+
socket.hostname = config.host
|
22
|
+
loop do
|
23
|
+
case status = socket.connect_nonblock(exception: false)
|
24
|
+
when :wait_readable
|
25
|
+
socket.to_io.wait_readable(connect_timeout) or raise ReadTimeoutError
|
26
|
+
when :wait_writable
|
27
|
+
socket.to_io.wait_writable(connect_timeout) or raise WriteTimeoutError
|
28
|
+
when socket
|
29
|
+
break
|
30
|
+
else
|
31
|
+
raise "Unexpected `connect_nonblock` return: #{status.inspect}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@io = BufferedIO.new(
|
37
|
+
socket,
|
38
|
+
read_timeout: read_timeout,
|
39
|
+
write_timeout: write_timeout,
|
40
|
+
)
|
41
|
+
rescue Errno::ETIMEDOUT => error
|
42
|
+
raise ConnectTimeoutError, error.message
|
43
|
+
rescue SystemCallError, OpenSSL::SSL::SSLError => error
|
44
|
+
raise ConnectionError, error.message
|
45
|
+
end
|
46
|
+
|
47
|
+
def connected?
|
48
|
+
!@io.closed?
|
49
|
+
end
|
50
|
+
|
51
|
+
def close
|
52
|
+
@io.close
|
53
|
+
end
|
54
|
+
|
55
|
+
def write(command)
|
56
|
+
buffer = RESP3.dump(command)
|
57
|
+
begin
|
58
|
+
@io.write(buffer)
|
59
|
+
rescue SystemCallError, IOError => error
|
60
|
+
raise ConnectionError, error.message
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def write_multi(commands)
|
65
|
+
buffer = nil
|
66
|
+
commands.each do |command|
|
67
|
+
buffer = RESP3.dump(command, buffer)
|
68
|
+
end
|
69
|
+
begin
|
70
|
+
@io.write(buffer)
|
71
|
+
rescue SystemCallError, IOError => error
|
72
|
+
raise ConnectionError, error.message
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def read(timeout = nil)
|
77
|
+
if timeout.nil?
|
78
|
+
RESP3.load(@io)
|
79
|
+
else
|
80
|
+
@io.with_timeout(timeout) { RESP3.load(@io) }
|
81
|
+
end
|
82
|
+
rescue SystemCallError, IOError, OpenSSL::SSL::SSLError => error
|
83
|
+
raise ConnectionError, error.message
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redis_client/hiredis_connection.so"
|
4
|
+
|
5
|
+
class RedisClient
|
6
|
+
class HiredisConnection
|
7
|
+
class SSLContext
|
8
|
+
def initialize(ca_file: nil, ca_path: nil, cert: nil, key: nil, hostname: nil)
|
9
|
+
if (error = init(ca_file, ca_path, cert, key, hostname))
|
10
|
+
raise error
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(config, connect_timeout:, read_timeout:, write_timeout:)
|
16
|
+
self.connect_timeout = connect_timeout
|
17
|
+
self.read_timeout = read_timeout
|
18
|
+
self.write_timeout = write_timeout
|
19
|
+
|
20
|
+
if config.path
|
21
|
+
connect_unix(config.path)
|
22
|
+
else
|
23
|
+
connect_tcp(config.host, config.port)
|
24
|
+
end
|
25
|
+
|
26
|
+
if config.ssl
|
27
|
+
init_ssl(config.hiredis_ssl_context)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def connect_timeout=(timeout)
|
32
|
+
self.connect_timeout_us = timeout ? (timeout * 1_000_000).to_i : 0
|
33
|
+
@connect_timeout = timeout
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_timeout=(timeout)
|
37
|
+
self.read_timeout_us = timeout ? (timeout * 1_000_000).to_i : 0
|
38
|
+
@read_timeout = timeout
|
39
|
+
end
|
40
|
+
|
41
|
+
def write_timeout=(timeout)
|
42
|
+
self.write_timeout_us = timeout ? (timeout * 1_000_000).to_i : 0
|
43
|
+
@write_timeout = timeout
|
44
|
+
end
|
45
|
+
|
46
|
+
def read(timeout = nil)
|
47
|
+
if timeout.nil?
|
48
|
+
_read
|
49
|
+
else
|
50
|
+
previous_timeout = @read_timeout
|
51
|
+
self.read_timeout = timeout
|
52
|
+
begin
|
53
|
+
_read
|
54
|
+
ensure
|
55
|
+
self.read_timeout = previous_timeout
|
56
|
+
end
|
57
|
+
end
|
58
|
+
rescue SystemCallError, IOError => error
|
59
|
+
raise ConnectionError, error.message
|
60
|
+
end
|
61
|
+
|
62
|
+
def write(command)
|
63
|
+
_write(command)
|
64
|
+
flush
|
65
|
+
rescue SystemCallError, IOError => error
|
66
|
+
raise ConnectionError, error.message
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_multi(commands)
|
70
|
+
commands.each do |command|
|
71
|
+
_write(command)
|
72
|
+
end
|
73
|
+
flush
|
74
|
+
rescue SystemCallError, IOError => error
|
75
|
+
raise ConnectionError, error.message
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "connection_pool"
|
4
|
+
|
5
|
+
class RedisClient
|
6
|
+
class Pooled
|
7
|
+
EMPTY_HASH = {}.freeze
|
8
|
+
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
def initialize(config, **kwargs)
|
12
|
+
@config = config
|
13
|
+
@pool_kwargs = kwargs
|
14
|
+
@pool = new_pool
|
15
|
+
@mutex = Mutex.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def with(options = EMPTY_HASH, &block)
|
19
|
+
pool.with(options, &block)
|
20
|
+
rescue ConnectionPool::TimeoutError => error
|
21
|
+
raise CheckoutTimeoutError, "Couldn't checkout a connection in time: #{error.message}"
|
22
|
+
end
|
23
|
+
alias_method :then, :with
|
24
|
+
|
25
|
+
def close
|
26
|
+
if @pool
|
27
|
+
@mutex.synchronize do
|
28
|
+
pool = @pool
|
29
|
+
@pool = nil
|
30
|
+
pool&.shutdown(&:close)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def size
|
37
|
+
pool.size
|
38
|
+
end
|
39
|
+
|
40
|
+
%w(pipelined).each do |method|
|
41
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
42
|
+
def #{method}(&block)
|
43
|
+
with { |r| r.#{method}(&block) }
|
44
|
+
end
|
45
|
+
RUBY
|
46
|
+
end
|
47
|
+
|
48
|
+
%w(multi).each do |method|
|
49
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
50
|
+
def #{method}(**kwargs, &block)
|
51
|
+
with { |r| r.#{method}(**kwargs, &block) }
|
52
|
+
end
|
53
|
+
RUBY
|
54
|
+
end
|
55
|
+
|
56
|
+
%w(call call_once blocking_call pubsub).each do |method|
|
57
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
58
|
+
def #{method}(*args)
|
59
|
+
with { |r| r.#{method}(*args) }
|
60
|
+
end
|
61
|
+
RUBY
|
62
|
+
end
|
63
|
+
|
64
|
+
%w(scan sscan hscan zscan).each do |method|
|
65
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
66
|
+
def #{method}(*args, &block)
|
67
|
+
unless block_given?
|
68
|
+
return to_enum(__callee__, *args)
|
69
|
+
end
|
70
|
+
|
71
|
+
with { |r| r.#{method}(*args, &block) }
|
72
|
+
end
|
73
|
+
RUBY
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def pool
|
79
|
+
@pool ||= @mutex.synchronize { new_pool }
|
80
|
+
end
|
81
|
+
|
82
|
+
def new_pool
|
83
|
+
ConnectionPool.new(**@pool_kwargs) { @config.new_client }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|