dalli 4.3.10 → 5.0.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 +4 -4
- data/CHANGELOG.md +23 -161
- data/Gemfile +1 -1
- data/README.md +4 -55
- data/lib/dalli/client.rb +39 -98
- data/lib/dalli/compressor.rb +5 -53
- data/lib/dalli/instrumentation.rb +1 -1
- data/lib/dalli/key_manager.rb +2 -28
- data/lib/dalli/options.rb +2 -2
- data/lib/dalli/pipelined_getter.rb +32 -46
- data/lib/dalli/pipelined_setter.rb +8 -17
- data/lib/dalli/protocol/base.rb +31 -160
- data/lib/dalli/protocol/connection_manager.rb +20 -137
- data/lib/dalli/protocol/meta.rb +15 -54
- data/lib/dalli/protocol/{meta/request_formatter.rb → request_formatter.rb} +8 -21
- data/lib/dalli/protocol/response_buffer.rb +12 -27
- data/lib/dalli/protocol/{meta/response_processor.rb → response_processor.rb} +32 -64
- data/lib/dalli/protocol/server_config_parser.rb +1 -1
- data/lib/dalli/protocol/value_compressor.rb +7 -36
- data/lib/dalli/protocol/value_serializer.rb +1 -1
- data/lib/dalli/protocol.rb +9 -7
- data/lib/dalli/ring.rb +2 -2
- data/lib/dalli/servers_arg_normalizer.rb +1 -1
- data/lib/dalli/socket.rb +0 -29
- data/lib/dalli/version.rb +2 -2
- data/lib/dalli.rb +0 -3
- data/lib/rack/session/dalli.rb +2 -1
- metadata +7 -14
- data/lib/dalli/json_serializer.rb +0 -28
- data/lib/dalli/protocol/binary/request_formatter.rb +0 -116
- data/lib/dalli/protocol/binary/response_header.rb +0 -36
- data/lib/dalli/protocol/binary/response_processor.rb +0 -245
- data/lib/dalli/protocol/binary/sasl_authentication.rb +0 -60
- data/lib/dalli/protocol/binary.rb +0 -202
- data/lib/dalli/protocol_deprecations.rb +0 -45
- /data/lib/dalli/protocol/{meta/key_regularizer.rb → key_regularizer.rb} +0 -0
data/lib/dalli/socket.rb
CHANGED
|
@@ -181,41 +181,12 @@ module Dalli
|
|
|
181
181
|
[seconds, microseconds].pack(timeval_pack_format(sock))
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
-
# Closes the TCP socket if the handshake fails or times out, so a failed
|
|
185
|
-
# attempt doesn't hold a file descriptor until GC.
|
|
186
184
|
def self.wrapping_ssl_socket(tcp_socket, host, ssl_context)
|
|
187
|
-
warn_if_unverified(ssl_context)
|
|
188
|
-
connected = false
|
|
189
185
|
ssl_socket = Dalli::Socket::SSLSocket.new(tcp_socket, ssl_context)
|
|
190
186
|
ssl_socket.hostname = host
|
|
191
187
|
ssl_socket.sync_close = true
|
|
192
188
|
ssl_socket.connect
|
|
193
|
-
connected = true
|
|
194
189
|
ssl_socket
|
|
195
|
-
ensure
|
|
196
|
-
tcp_socket.close unless connected
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# Class variable to track whether the TLS verification warning has been logged
|
|
200
|
-
@@ssl_verification_warning_logged = false # rubocop:disable Style/ClassVars
|
|
201
|
-
|
|
202
|
-
# Verification comes entirely from the caller's ssl_context, and a bare
|
|
203
|
-
# SSLContext.new verifies nothing, so warn (once per process) when the
|
|
204
|
-
# certificate or the hostname won't be checked.
|
|
205
|
-
def self.warn_if_unverified(ssl_context)
|
|
206
|
-
return if @@ssl_verification_warning_logged
|
|
207
|
-
|
|
208
|
-
problem = if !ssl_context.verify_mode.to_i.anybits?(::OpenSSL::SSL::VERIFY_PEER)
|
|
209
|
-
'verify_mode is VERIFY_NONE, so the server certificate is not checked'
|
|
210
|
-
elsif !ssl_context.verify_hostname
|
|
211
|
-
'verify_hostname is false, so the certificate is not checked against the hostname'
|
|
212
|
-
end
|
|
213
|
-
return unless problem
|
|
214
|
-
|
|
215
|
-
Dalli.logger.warn "SECURITY WARNING: Dalli's ssl_context #{problem}. " \
|
|
216
|
-
'Use ssl_context.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER, ' \
|
|
217
|
-
'verify_hostname: true) to verify the server.'
|
|
218
|
-
@@ssl_verification_warning_logged = true # rubocop:disable Style/ClassVars
|
|
219
190
|
end
|
|
220
191
|
end
|
|
221
192
|
|
data/lib/dalli/version.rb
CHANGED
data/lib/dalli.rb
CHANGED
|
@@ -62,8 +62,6 @@ require_relative 'dalli/version'
|
|
|
62
62
|
require_relative 'dalli/instrumentation'
|
|
63
63
|
|
|
64
64
|
require_relative 'dalli/compressor'
|
|
65
|
-
require_relative 'dalli/json_serializer'
|
|
66
|
-
require_relative 'dalli/protocol_deprecations'
|
|
67
65
|
require_relative 'dalli/client'
|
|
68
66
|
require_relative 'dalli/key_manager'
|
|
69
67
|
require_relative 'dalli/pipelined_getter'
|
|
@@ -72,7 +70,6 @@ require_relative 'dalli/pipelined_deleter'
|
|
|
72
70
|
require_relative 'dalli/ring'
|
|
73
71
|
require_relative 'dalli/protocol'
|
|
74
72
|
require_relative 'dalli/protocol/base'
|
|
75
|
-
require_relative 'dalli/protocol/binary'
|
|
76
73
|
require_relative 'dalli/protocol/connection_manager'
|
|
77
74
|
require_relative 'dalli/protocol/meta'
|
|
78
75
|
require_relative 'dalli/protocol/response_buffer'
|
data/lib/rack/session/dalli.rb
CHANGED
|
@@ -16,10 +16,11 @@ module Rack
|
|
|
16
16
|
attr_reader :data
|
|
17
17
|
|
|
18
18
|
# Don't freeze this until we fix the specs/implementation
|
|
19
|
-
# rubocop:disable
|
|
19
|
+
# rubocop:disable Style/MutableConstant
|
|
20
20
|
DEFAULT_DALLI_OPTIONS = {
|
|
21
21
|
namespace: 'rack:session'
|
|
22
22
|
}
|
|
23
|
+
# rubocop:enable Style/MutableConstant
|
|
23
24
|
|
|
24
25
|
# Brings in a new Rack::Session::Dalli middleware with the given
|
|
25
26
|
# `:memcache_server`. The server is either a hostname, or a
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dalli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 5.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter M. Goldstein
|
|
@@ -41,7 +41,6 @@ files:
|
|
|
41
41
|
- lib/dalli/client.rb
|
|
42
42
|
- lib/dalli/compressor.rb
|
|
43
43
|
- lib/dalli/instrumentation.rb
|
|
44
|
-
- lib/dalli/json_serializer.rb
|
|
45
44
|
- lib/dalli/key_manager.rb
|
|
46
45
|
- lib/dalli/options.rb
|
|
47
46
|
- lib/dalli/pid_cache.rb
|
|
@@ -50,24 +49,18 @@ files:
|
|
|
50
49
|
- lib/dalli/pipelined_setter.rb
|
|
51
50
|
- lib/dalli/protocol.rb
|
|
52
51
|
- lib/dalli/protocol/base.rb
|
|
53
|
-
- lib/dalli/protocol/binary.rb
|
|
54
|
-
- lib/dalli/protocol/binary/request_formatter.rb
|
|
55
|
-
- lib/dalli/protocol/binary/response_header.rb
|
|
56
|
-
- lib/dalli/protocol/binary/response_processor.rb
|
|
57
|
-
- lib/dalli/protocol/binary/sasl_authentication.rb
|
|
58
52
|
- lib/dalli/protocol/connection_manager.rb
|
|
53
|
+
- lib/dalli/protocol/key_regularizer.rb
|
|
59
54
|
- lib/dalli/protocol/meta.rb
|
|
60
|
-
- lib/dalli/protocol/
|
|
61
|
-
- lib/dalli/protocol/meta/request_formatter.rb
|
|
62
|
-
- lib/dalli/protocol/meta/response_processor.rb
|
|
55
|
+
- lib/dalli/protocol/request_formatter.rb
|
|
63
56
|
- lib/dalli/protocol/response_buffer.rb
|
|
57
|
+
- lib/dalli/protocol/response_processor.rb
|
|
64
58
|
- lib/dalli/protocol/server_config_parser.rb
|
|
65
59
|
- lib/dalli/protocol/string_marshaller.rb
|
|
66
60
|
- lib/dalli/protocol/ttl_sanitizer.rb
|
|
67
61
|
- lib/dalli/protocol/value_compressor.rb
|
|
68
62
|
- lib/dalli/protocol/value_marshaller.rb
|
|
69
63
|
- lib/dalli/protocol/value_serializer.rb
|
|
70
|
-
- lib/dalli/protocol_deprecations.rb
|
|
71
64
|
- lib/dalli/ring.rb
|
|
72
65
|
- lib/dalli/servers_arg_normalizer.rb
|
|
73
66
|
- lib/dalli/socket.rb
|
|
@@ -78,7 +71,7 @@ licenses:
|
|
|
78
71
|
- MIT
|
|
79
72
|
metadata:
|
|
80
73
|
bug_tracker_uri: https://github.com/petergoldstein/dalli/issues
|
|
81
|
-
changelog_uri: https://github.com/petergoldstein/dalli/blob/
|
|
74
|
+
changelog_uri: https://github.com/petergoldstein/dalli/blob/main/CHANGELOG.md
|
|
82
75
|
rubygems_mfa_required: 'true'
|
|
83
76
|
rdoc_options: []
|
|
84
77
|
require_paths:
|
|
@@ -87,14 +80,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
87
80
|
requirements:
|
|
88
81
|
- - ">="
|
|
89
82
|
- !ruby/object:Gem::Version
|
|
90
|
-
version: '3.
|
|
83
|
+
version: '3.3'
|
|
91
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
85
|
requirements:
|
|
93
86
|
- - ">="
|
|
94
87
|
- !ruby/object:Gem::Version
|
|
95
88
|
version: '0'
|
|
96
89
|
requirements: []
|
|
97
|
-
rubygems_version: 4.0.
|
|
90
|
+
rubygems_version: 4.0.6
|
|
98
91
|
specification_version: 4
|
|
99
92
|
summary: High performance memcached client for Ruby
|
|
100
93
|
test_files: []
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'json'
|
|
4
|
-
|
|
5
|
-
module Dalli
|
|
6
|
-
##
|
|
7
|
-
# A serializer that stores values as JSON and never creates Ruby objects
|
|
8
|
-
# from what it reads.
|
|
9
|
-
#
|
|
10
|
-
# Passing the JSON module itself as the serializer (serializer: JSON) reads
|
|
11
|
-
# values back with JSON.load, which on json gem versions before 3.0 honors
|
|
12
|
-
# "json_class" and instantiates other classes when the json gem's additions
|
|
13
|
-
# are loaded. This serializer reads with JSON.parse, so a value written to
|
|
14
|
-
# memcached by someone else can only come back as plain hashes, arrays,
|
|
15
|
-
# strings, numbers, booleans and nil.
|
|
16
|
-
#
|
|
17
|
-
# Dalli::Client.new(servers, serializer: Dalli::JSONSerializer)
|
|
18
|
-
##
|
|
19
|
-
module JSONSerializer
|
|
20
|
-
def self.dump(value)
|
|
21
|
-
JSON.generate(value)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def self.load(data)
|
|
25
|
-
JSON.parse(data)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Dalli
|
|
4
|
-
module Protocol
|
|
5
|
-
class Binary
|
|
6
|
-
##
|
|
7
|
-
# Class that encapsulates logic for formatting binary protocol requests
|
|
8
|
-
# to memcached.
|
|
9
|
-
##
|
|
10
|
-
class RequestFormatter
|
|
11
|
-
REQUEST = 0x80
|
|
12
|
-
|
|
13
|
-
OPCODES = {
|
|
14
|
-
get: 0x00,
|
|
15
|
-
set: 0x01,
|
|
16
|
-
add: 0x02,
|
|
17
|
-
replace: 0x03,
|
|
18
|
-
delete: 0x04,
|
|
19
|
-
incr: 0x05,
|
|
20
|
-
decr: 0x06,
|
|
21
|
-
flush: 0x08,
|
|
22
|
-
noop: 0x0A,
|
|
23
|
-
version: 0x0B,
|
|
24
|
-
getkq: 0x0D,
|
|
25
|
-
append: 0x0E,
|
|
26
|
-
prepend: 0x0F,
|
|
27
|
-
stat: 0x10,
|
|
28
|
-
setq: 0x11,
|
|
29
|
-
addq: 0x12,
|
|
30
|
-
replaceq: 0x13,
|
|
31
|
-
deleteq: 0x14,
|
|
32
|
-
incrq: 0x15,
|
|
33
|
-
decrq: 0x16,
|
|
34
|
-
flushq: 0x18,
|
|
35
|
-
appendq: 0x19,
|
|
36
|
-
prependq: 0x1A,
|
|
37
|
-
touch: 0x1C,
|
|
38
|
-
gat: 0x1D,
|
|
39
|
-
auth_negotiation: 0x20,
|
|
40
|
-
auth_request: 0x21,
|
|
41
|
-
auth_continue: 0x22
|
|
42
|
-
}.freeze
|
|
43
|
-
|
|
44
|
-
REQ_HEADER_FORMAT = 'CCnCCnNNQ'
|
|
45
|
-
|
|
46
|
-
KEY_ONLY = 'a*'
|
|
47
|
-
TTL_AND_KEY = 'Na*'
|
|
48
|
-
KEY_AND_VALUE = 'a*a*'
|
|
49
|
-
INCR_DECR = 'NNNNNa*'
|
|
50
|
-
TTL_ONLY = 'N'
|
|
51
|
-
NO_BODY = ''
|
|
52
|
-
|
|
53
|
-
BODY_FORMATS = {
|
|
54
|
-
get: KEY_ONLY,
|
|
55
|
-
getkq: KEY_ONLY,
|
|
56
|
-
delete: KEY_ONLY,
|
|
57
|
-
deleteq: KEY_ONLY,
|
|
58
|
-
stat: KEY_ONLY,
|
|
59
|
-
|
|
60
|
-
append: KEY_AND_VALUE,
|
|
61
|
-
prepend: KEY_AND_VALUE,
|
|
62
|
-
appendq: KEY_AND_VALUE,
|
|
63
|
-
prependq: KEY_AND_VALUE,
|
|
64
|
-
auth_request: KEY_AND_VALUE,
|
|
65
|
-
auth_continue: KEY_AND_VALUE,
|
|
66
|
-
|
|
67
|
-
set: 'NNa*a*',
|
|
68
|
-
setq: 'NNa*a*',
|
|
69
|
-
add: 'NNa*a*',
|
|
70
|
-
addq: 'NNa*a*',
|
|
71
|
-
replace: 'NNa*a*',
|
|
72
|
-
replaceq: 'NNa*a*',
|
|
73
|
-
|
|
74
|
-
incr: INCR_DECR,
|
|
75
|
-
decr: INCR_DECR,
|
|
76
|
-
incrq: INCR_DECR,
|
|
77
|
-
decrq: INCR_DECR,
|
|
78
|
-
|
|
79
|
-
flush: TTL_ONLY,
|
|
80
|
-
flushq: TTL_ONLY,
|
|
81
|
-
|
|
82
|
-
noop: NO_BODY,
|
|
83
|
-
auth_negotiation: NO_BODY,
|
|
84
|
-
version: NO_BODY,
|
|
85
|
-
|
|
86
|
-
touch: TTL_AND_KEY,
|
|
87
|
-
gat: TTL_AND_KEY
|
|
88
|
-
}.freeze
|
|
89
|
-
FORMAT = BODY_FORMATS.transform_values { |v| REQ_HEADER_FORMAT + v }
|
|
90
|
-
|
|
91
|
-
# rubocop:disable-next Metrics/ParameterLists
|
|
92
|
-
def self.standard_request(opkey:, key: nil, value: nil, opaque: 0, cas: 0, bitflags: nil, ttl: nil)
|
|
93
|
-
extra_len = (bitflags.nil? ? 0 : 4) + (ttl.nil? ? 0 : 4)
|
|
94
|
-
key_len = key.nil? ? 0 : key.bytesize
|
|
95
|
-
value_len = value.nil? ? 0 : value.bytesize
|
|
96
|
-
header = [REQUEST, OPCODES[opkey], key_len, extra_len, 0, 0, extra_len + key_len + value_len, opaque, cas]
|
|
97
|
-
body = [bitflags, ttl, key, value].compact
|
|
98
|
-
(header + body).pack(FORMAT[opkey])
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def self.decr_incr_request(opkey:, key: nil, count: nil, initial: nil, expiry: nil)
|
|
102
|
-
extra_len = 20
|
|
103
|
-
(h, l) = as_8byte_uint(count)
|
|
104
|
-
(dh, dl) = as_8byte_uint(initial)
|
|
105
|
-
header = [REQUEST, OPCODES[opkey], key.bytesize, extra_len, 0, 0, key.bytesize + extra_len, 0, 0]
|
|
106
|
-
body = [h, l, dh, dl, expiry, key]
|
|
107
|
-
(header + body).pack(FORMAT[opkey])
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def self.as_8byte_uint(val)
|
|
111
|
-
[val >> 32, val & 0xFFFFFFFF]
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Dalli
|
|
4
|
-
module Protocol
|
|
5
|
-
class Binary
|
|
6
|
-
##
|
|
7
|
-
# Class that encapsulates data parsed from a memcached response header.
|
|
8
|
-
##
|
|
9
|
-
class ResponseHeader
|
|
10
|
-
SIZE = 24
|
|
11
|
-
FMT = '@2nCCnNNQ'
|
|
12
|
-
|
|
13
|
-
attr_reader :key_len, :extra_len, :data_type, :status, :body_len, :opaque, :cas
|
|
14
|
-
|
|
15
|
-
def initialize(buf)
|
|
16
|
-
raise ArgumentError, "Response buffer must be at least #{SIZE} bytes" unless buf.bytesize >= SIZE
|
|
17
|
-
|
|
18
|
-
@key_len, @extra_len, @data_type, @status, @body_len, @opaque, @cas = buf.unpack(FMT)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def ok?
|
|
22
|
-
status.zero?
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def not_found?
|
|
26
|
-
status == 1
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
NOT_STORED_STATUSES = [2, 5].freeze
|
|
30
|
-
def not_stored?
|
|
31
|
-
NOT_STORED_STATUSES.include?(status)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Dalli
|
|
4
|
-
module Protocol
|
|
5
|
-
class Binary
|
|
6
|
-
##
|
|
7
|
-
# Class that encapsulates logic for processing binary protocol responses
|
|
8
|
-
# from memcached. Includes logic for pulling data from an IO source
|
|
9
|
-
# and parsing into local values. Handles errors on unexpected values.
|
|
10
|
-
##
|
|
11
|
-
class ResponseProcessor
|
|
12
|
-
# Response codes taken from:
|
|
13
|
-
# https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped#response-status
|
|
14
|
-
RESPONSE_CODES = {
|
|
15
|
-
0 => 'No error',
|
|
16
|
-
1 => 'Key not found',
|
|
17
|
-
2 => 'Key exists',
|
|
18
|
-
3 => 'Value too large',
|
|
19
|
-
4 => 'Invalid arguments',
|
|
20
|
-
5 => 'Item not stored',
|
|
21
|
-
6 => 'Incr/decr on a non-numeric value',
|
|
22
|
-
7 => 'The vbucket belongs to another server',
|
|
23
|
-
8 => 'Authentication error',
|
|
24
|
-
9 => 'Authentication continue',
|
|
25
|
-
0x20 => 'Authentication required',
|
|
26
|
-
0x81 => 'Unknown command',
|
|
27
|
-
0x82 => 'Out of memory',
|
|
28
|
-
0x83 => 'Not supported',
|
|
29
|
-
0x84 => 'Internal error',
|
|
30
|
-
0x85 => 'Busy',
|
|
31
|
-
0x86 => 'Temporary failure'
|
|
32
|
-
}.freeze
|
|
33
|
-
|
|
34
|
-
def initialize(io_source, value_marshaller)
|
|
35
|
-
@io_source = io_source
|
|
36
|
-
@value_marshaller = value_marshaller
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def read(num_bytes)
|
|
40
|
-
@io_source.read(num_bytes)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def read_response
|
|
44
|
-
resp_header = ResponseHeader.new(read_header)
|
|
45
|
-
body = read(resp_header.body_len) if resp_header.body_len.positive?
|
|
46
|
-
[resp_header, body]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def unpack_response_body(resp_header, body, parse_as_stored_value)
|
|
50
|
-
extra_len = resp_header.extra_len
|
|
51
|
-
key_len = resp_header.key_len
|
|
52
|
-
bitflags = extra_len.positive? ? body.unpack1('N') : 0x0
|
|
53
|
-
key = body.byteslice(extra_len, key_len).force_encoding(Encoding::UTF_8) if key_len.positive?
|
|
54
|
-
value = body.byteslice((extra_len + key_len)..-1)
|
|
55
|
-
value = @value_marshaller.retrieve(value, bitflags) if parse_as_stored_value
|
|
56
|
-
[key, value]
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def read_header
|
|
60
|
-
read(ResponseHeader::SIZE) || raise(Dalli::NetworkError, 'No response')
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def raise_on_not_ok!(resp_header)
|
|
64
|
-
return if resp_header.ok?
|
|
65
|
-
|
|
66
|
-
raise Dalli::DalliError, "Response error #{resp_header.status}: #{RESPONSE_CODES[resp_header.status]}"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# raw: true returns the value as stored, without deserializing or
|
|
70
|
-
# decompressing it, whatever flags it was stored with
|
|
71
|
-
def get(cache_nils: false, raw: false)
|
|
72
|
-
resp_header, body = read_response
|
|
73
|
-
|
|
74
|
-
return false if resp_header.not_stored? # Not stored, normal status for add operation
|
|
75
|
-
return cache_nils ? ::Dalli::NOT_FOUND : nil if resp_header.not_found?
|
|
76
|
-
|
|
77
|
-
raise_on_not_ok!(resp_header)
|
|
78
|
-
return true unless body
|
|
79
|
-
|
|
80
|
-
unpack_response_body(resp_header, body, !raw).last
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
##
|
|
84
|
-
# Response for a storage operation. Returns the cas on success. False
|
|
85
|
-
# if the value wasn't stored. And raises an error on all other error
|
|
86
|
-
# codes from memcached.
|
|
87
|
-
##
|
|
88
|
-
def storage_response
|
|
89
|
-
resp_header, = read_response
|
|
90
|
-
return nil if resp_header.not_found?
|
|
91
|
-
return false if resp_header.not_stored? # Not stored, normal status for add operation
|
|
92
|
-
|
|
93
|
-
raise_on_not_ok!(resp_header)
|
|
94
|
-
resp_header.cas
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def delete
|
|
98
|
-
resp_header, = read_response
|
|
99
|
-
return false if resp_header.not_found? || resp_header.not_stored?
|
|
100
|
-
|
|
101
|
-
raise_on_not_ok!(resp_header)
|
|
102
|
-
true
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# raw: true returns the value as stored; see #get
|
|
106
|
-
def data_cas_response(raw: false)
|
|
107
|
-
resp_header, body = read_response
|
|
108
|
-
return [nil, resp_header.cas] if resp_header.not_found?
|
|
109
|
-
return [nil, false] if resp_header.not_stored?
|
|
110
|
-
|
|
111
|
-
raise_on_not_ok!(resp_header)
|
|
112
|
-
return [nil, resp_header.cas] unless body
|
|
113
|
-
|
|
114
|
-
[unpack_response_body(resp_header, body, !raw).last, resp_header.cas]
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Returns the new value for the key, if found and updated
|
|
118
|
-
def decr_incr
|
|
119
|
-
body = generic_response
|
|
120
|
-
body ? body.unpack1('Q>') : body
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def stats
|
|
124
|
-
hash = {}
|
|
125
|
-
loop do
|
|
126
|
-
resp_header, body = read_response
|
|
127
|
-
# This is the response to the terminating noop / end of stat
|
|
128
|
-
return hash if resp_header.ok? && resp_header.key_len.zero?
|
|
129
|
-
|
|
130
|
-
# Ignore any responses with non-zero status codes,
|
|
131
|
-
# such as errors from set operations. That allows
|
|
132
|
-
# this code to be used at the end of a multi
|
|
133
|
-
# block to clear any error responses from inside the multi.
|
|
134
|
-
next unless resp_header.ok?
|
|
135
|
-
|
|
136
|
-
key, value = unpack_response_body(resp_header, body, true)
|
|
137
|
-
hash[key] = value
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def flush
|
|
142
|
-
no_body_response
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def reset
|
|
146
|
-
generic_response
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def version
|
|
150
|
-
generic_response
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def consume_all_responses_until_noop
|
|
154
|
-
loop do
|
|
155
|
-
resp_header, = read_response
|
|
156
|
-
# This is the response to the terminating noop / end of stat
|
|
157
|
-
return true if resp_header.ok? && resp_header.key_len.zero?
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def generic_response
|
|
162
|
-
resp_header, body = read_response
|
|
163
|
-
|
|
164
|
-
return false if resp_header.not_stored? # Not stored, normal status for add operation
|
|
165
|
-
return nil if resp_header.not_found?
|
|
166
|
-
|
|
167
|
-
raise_on_not_ok!(resp_header)
|
|
168
|
-
return true unless body
|
|
169
|
-
|
|
170
|
-
unpack_response_body(resp_header, body, false).last
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def no_body_response
|
|
174
|
-
resp_header, = read_response
|
|
175
|
-
return false if resp_header.not_stored? # Not stored, possible status for append/prepend/delete
|
|
176
|
-
|
|
177
|
-
raise_on_not_ok!(resp_header)
|
|
178
|
-
true
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def validate_auth_format(extra_len, count)
|
|
182
|
-
return if extra_len.zero?
|
|
183
|
-
|
|
184
|
-
raise Dalli::NetworkError, "Unexpected message format: #{extra_len} #{count}"
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def auth_response(buf = read_header)
|
|
188
|
-
resp_header = ResponseHeader.new(buf)
|
|
189
|
-
body_len = resp_header.body_len
|
|
190
|
-
validate_auth_format(resp_header.extra_len, body_len)
|
|
191
|
-
content = read(body_len) if body_len.positive?
|
|
192
|
-
[resp_header.status, content]
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
##
|
|
196
|
-
# This method returns an array of values used in a pipelined
|
|
197
|
-
# getk process. The first value is the number of bytes by
|
|
198
|
-
# which to advance the pointer in the buffer. If the
|
|
199
|
-
# complete response is found in the buffer, this will
|
|
200
|
-
# be the response size. Otherwise it is zero.
|
|
201
|
-
#
|
|
202
|
-
# The remaining three values in the array are the ResponseHeader,
|
|
203
|
-
# key, and value.
|
|
204
|
-
##
|
|
205
|
-
# memcached can't store an item larger than 1 GiB (its -I maximum). A
|
|
206
|
-
# pipelined reply claiming an impossible size would otherwise have the
|
|
207
|
-
# buffer wait for (and accumulate) that many bytes.
|
|
208
|
-
MAX_BODY_BYTES = 1024 * 1024 * 1024
|
|
209
|
-
|
|
210
|
-
def check_body_size!(size)
|
|
211
|
-
return if size <= MAX_BODY_BYTES
|
|
212
|
-
|
|
213
|
-
raise Dalli::DalliError, "Reply body size #{size} is out of range"
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def getk_response_from_buffer(buf, offset = 0)
|
|
217
|
-
# There's no header in the buffer, so don't advance
|
|
218
|
-
return [0, nil, nil, nil, nil] unless buf && buf.bytesize >= offset + ResponseHeader::SIZE
|
|
219
|
-
|
|
220
|
-
resp_header = ResponseHeader.new(buf.byteslice(offset, ResponseHeader::SIZE))
|
|
221
|
-
body_len = resp_header.body_len
|
|
222
|
-
|
|
223
|
-
# We have a complete response that has no body.
|
|
224
|
-
# This is either the response to the terminating
|
|
225
|
-
# noop or, if the status is not zero, an intermediate
|
|
226
|
-
# error response that needs to be discarded.
|
|
227
|
-
return [ResponseHeader::SIZE, resp_header.ok?, resp_header.cas, nil, nil] if body_len.zero?
|
|
228
|
-
|
|
229
|
-
check_body_size!(body_len)
|
|
230
|
-
|
|
231
|
-
resp_size = ResponseHeader::SIZE + body_len
|
|
232
|
-
# The header is in the buffer, but the body is not. As we don't have
|
|
233
|
-
# a complete response, don't advance the buffer
|
|
234
|
-
return [0, nil, nil, nil, nil] unless buf.bytesize >= offset + resp_size
|
|
235
|
-
|
|
236
|
-
# The full response is in our buffer, so parse it and return
|
|
237
|
-
# the values
|
|
238
|
-
body = buf.byteslice(offset + ResponseHeader::SIZE, body_len)
|
|
239
|
-
key, value = unpack_response_body(resp_header, body, true)
|
|
240
|
-
[resp_size, resp_header.ok?, resp_header.cas, key, value]
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
end
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Dalli
|
|
4
|
-
module Protocol
|
|
5
|
-
class Binary
|
|
6
|
-
##
|
|
7
|
-
# Code to support SASL authentication
|
|
8
|
-
##
|
|
9
|
-
module SaslAuthentication
|
|
10
|
-
def perform_auth_negotiation
|
|
11
|
-
write(RequestFormatter.standard_request(opkey: :auth_negotiation))
|
|
12
|
-
|
|
13
|
-
status, content = response_processor.auth_response
|
|
14
|
-
return [status, []] if content.nil?
|
|
15
|
-
|
|
16
|
-
# Substitute spaces for the \x00 returned by
|
|
17
|
-
# memcached as a separator for easier
|
|
18
|
-
content&.tr!("\u0000", ' ')
|
|
19
|
-
mechanisms = content&.split
|
|
20
|
-
[status, mechanisms]
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
PLAIN_AUTH = 'PLAIN'
|
|
24
|
-
|
|
25
|
-
def supported_mechanisms!(mechanisms)
|
|
26
|
-
unless mechanisms.include?(PLAIN_AUTH)
|
|
27
|
-
raise NotImplementedError,
|
|
28
|
-
'Dalli only supports the PLAIN authentication mechanism'
|
|
29
|
-
end
|
|
30
|
-
[PLAIN_AUTH]
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def authenticate_with_plain
|
|
34
|
-
write(RequestFormatter.standard_request(opkey: :auth_request,
|
|
35
|
-
key: PLAIN_AUTH,
|
|
36
|
-
value: "\x0#{username}\x0#{password}"))
|
|
37
|
-
@response_processor.auth_response
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def authenticate_connection
|
|
41
|
-
Dalli.logger.info { "Dalli/SASL authenticating as #{username}" }
|
|
42
|
-
|
|
43
|
-
status, mechanisms = perform_auth_negotiation
|
|
44
|
-
return Dalli.logger.debug('Authentication not required/supported by server') if status == 0x81
|
|
45
|
-
|
|
46
|
-
supported_mechanisms!(mechanisms)
|
|
47
|
-
status, content = authenticate_with_plain
|
|
48
|
-
|
|
49
|
-
return Dalli.logger.info("Dalli/SASL: #{content}") if status.zero?
|
|
50
|
-
|
|
51
|
-
raise Dalli::DalliError, "Error authenticating: 0x#{status.to_s(16)}" unless status == 0x21
|
|
52
|
-
|
|
53
|
-
raise NotImplementedError, 'No two-step authentication mechanisms supported'
|
|
54
|
-
# (step, msg) = sasl.receive('challenge', content)
|
|
55
|
-
# raise Dalli::NetworkError, "Authentication failed" if sasl.failed? || step != 'response'
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|