dalli 5.2.2 → 5.2.4
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 +68 -0
- data/README.md +40 -0
- data/lib/dalli/client.rb +79 -16
- data/lib/dalli/options.rb +1 -1
- data/lib/dalli/pipelined_getter.rb +41 -25
- data/lib/dalli/pipelined_setter.rb +9 -0
- data/lib/dalli/protocol/base.rb +110 -17
- data/lib/dalli/protocol/connection_manager.rb +20 -3
- data/lib/dalli/protocol/meta.rb +71 -13
- data/lib/dalli/protocol/server_config_parser.rb +10 -5
- data/lib/dalli/protocol/string_marshaller.rb +3 -5
- data/lib/dalli/protocol/value_compressor.rb +6 -4
- data/lib/dalli/protocol/value_marshaller.rb +21 -5
- data/lib/dalli/protocol.rb +7 -9
- data/lib/dalli/socket.rb +40 -3
- data/lib/dalli/version.rb +1 -1
- data/lib/dalli.rb +4 -0
- data/lib/rack/session/dalli.rb +37 -10
- metadata +2 -2
data/lib/dalli/socket.rb
CHANGED
|
@@ -8,6 +8,12 @@ module Dalli
|
|
|
8
8
|
# Various socket implementations used by Dalli.
|
|
9
9
|
##
|
|
10
10
|
module Socket
|
|
11
|
+
# Timeout for establishing a connection: connect_timeout if set,
|
|
12
|
+
# otherwise socket_timeout.
|
|
13
|
+
def self.connect_timeout_for(options)
|
|
14
|
+
options[:connect_timeout] || options[:socket_timeout]
|
|
15
|
+
end
|
|
16
|
+
|
|
11
17
|
##
|
|
12
18
|
# Common methods for all socket implementations.
|
|
13
19
|
##
|
|
@@ -162,11 +168,13 @@ module Dalli
|
|
|
162
168
|
end
|
|
163
169
|
|
|
164
170
|
def self.create_socket_with_timeout(host, port, options)
|
|
171
|
+
timeout = Dalli::Socket.connect_timeout_for(options)
|
|
165
172
|
if supports_connect_timeout?
|
|
166
|
-
|
|
173
|
+
# resolv_timeout bounds the DNS lookup, which connect_timeout doesn't cover
|
|
174
|
+
sock = new(host, port, connect_timeout: timeout, resolv_timeout: timeout)
|
|
167
175
|
yield(sock)
|
|
168
176
|
else
|
|
169
|
-
Timeout.timeout(
|
|
177
|
+
Timeout.timeout(timeout) do
|
|
170
178
|
sock = new(host, port)
|
|
171
179
|
yield(sock)
|
|
172
180
|
end
|
|
@@ -228,12 +236,41 @@ module Dalli
|
|
|
228
236
|
[seconds, microseconds].pack(timeval_pack_format(sock))
|
|
229
237
|
end
|
|
230
238
|
|
|
239
|
+
# Closes the TCP socket if the handshake fails or times out, so a failed
|
|
240
|
+
# attempt doesn't hold a file descriptor until GC.
|
|
231
241
|
def self.wrapping_ssl_socket(tcp_socket, host, ssl_context)
|
|
242
|
+
warn_if_unverified(ssl_context)
|
|
243
|
+
connected = false
|
|
232
244
|
ssl_socket = Dalli::Socket::SSLSocket.new(tcp_socket, ssl_context)
|
|
233
245
|
ssl_socket.hostname = host
|
|
234
246
|
ssl_socket.sync_close = true
|
|
235
247
|
ssl_socket.connect
|
|
248
|
+
connected = true
|
|
236
249
|
ssl_socket
|
|
250
|
+
ensure
|
|
251
|
+
tcp_socket.close unless connected
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Class variable to track whether the TLS verification warning has been logged
|
|
255
|
+
@@ssl_verification_warning_logged = false # rubocop:disable Style/ClassVars
|
|
256
|
+
|
|
257
|
+
# Verification comes entirely from the caller's ssl_context, and a bare
|
|
258
|
+
# SSLContext.new verifies nothing, so warn (once per process) when the
|
|
259
|
+
# certificate or the hostname won't be checked.
|
|
260
|
+
def self.warn_if_unverified(ssl_context)
|
|
261
|
+
return if @@ssl_verification_warning_logged
|
|
262
|
+
|
|
263
|
+
problem = if !ssl_context.verify_mode.to_i.anybits?(::OpenSSL::SSL::VERIFY_PEER)
|
|
264
|
+
'verify_mode is VERIFY_NONE, so the server certificate is not checked'
|
|
265
|
+
elsif !ssl_context.verify_hostname
|
|
266
|
+
'verify_hostname is false, so the certificate is not checked against the hostname'
|
|
267
|
+
end
|
|
268
|
+
return unless problem
|
|
269
|
+
|
|
270
|
+
Dalli.logger.warn "SECURITY WARNING: Dalli's ssl_context #{problem}. " \
|
|
271
|
+
'Use ssl_context.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER, ' \
|
|
272
|
+
'verify_hostname: true) to verify the server.'
|
|
273
|
+
@@ssl_verification_warning_logged = true # rubocop:disable Style/ClassVars
|
|
237
274
|
end
|
|
238
275
|
end
|
|
239
276
|
|
|
@@ -261,7 +298,7 @@ module Dalli
|
|
|
261
298
|
attr_accessor :options
|
|
262
299
|
|
|
263
300
|
def self.open(path, options = {})
|
|
264
|
-
Timeout.timeout(options
|
|
301
|
+
Timeout.timeout(Dalli::Socket.connect_timeout_for(options)) do
|
|
265
302
|
sock = new(path)
|
|
266
303
|
sock.options = { path: path }.merge(options)
|
|
267
304
|
init_socket_options(sock, options)
|
data/lib/dalli/version.rb
CHANGED
data/lib/dalli.rb
CHANGED
|
@@ -31,6 +31,10 @@ module Dalli
|
|
|
31
31
|
# socket/server communication error that can be retried
|
|
32
32
|
class RetryableNetworkError < NetworkError; end
|
|
33
33
|
|
|
34
|
+
# a multi-get reply named a key that wasn't requested, so the rest of that
|
|
35
|
+
# server's replies can't be trusted
|
|
36
|
+
class UnexpectedReplyKeyError < DalliError; end
|
|
37
|
+
|
|
34
38
|
# Implements the NullObject pattern to store an application-defined value for 'Key not found' responses.
|
|
35
39
|
class NilObject; end # rubocop:disable Lint/EmptyClass
|
|
36
40
|
NOT_FOUND = NilObject.new
|
data/lib/rack/session/dalli.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rack/session/abstract/id'
|
|
4
4
|
require 'dalli'
|
|
5
|
-
require 'connection_pool'
|
|
6
5
|
require 'English'
|
|
7
6
|
|
|
8
7
|
module Rack
|
|
@@ -21,6 +20,11 @@ module Rack
|
|
|
21
20
|
namespace: 'rack:session'
|
|
22
21
|
}
|
|
23
22
|
|
|
23
|
+
# Our pool options and the ConnectionPool options they set. These are
|
|
24
|
+
# not passed on to Dalli::Client.
|
|
25
|
+
POOL_OPTIONS = { pool_size: :size, pool_timeout: :timeout }.freeze
|
|
26
|
+
private_constant :POOL_OPTIONS
|
|
27
|
+
|
|
24
28
|
# Brings in a new Rack::Session::Dalli middleware with the given
|
|
25
29
|
# `:memcache_server`. The server is either a hostname, or a
|
|
26
30
|
# host-with-port string in the form of "host_name:port", or an array of
|
|
@@ -73,10 +77,18 @@ module Rack
|
|
|
73
77
|
@data = build_data_source(options)
|
|
74
78
|
end
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
# If the session is deleted from memcached while a request is running
|
|
81
|
+
# (for example, by a logout in another request), the session is not
|
|
82
|
+
# written back, so a logged-out session can't come back. The app's
|
|
83
|
+
# response is replaced with a 401 that expires the session cookie.
|
|
84
|
+
# The app has already run by then, so any side effects it had (database
|
|
85
|
+
# writes, emails, and so on) have happened.
|
|
86
|
+
def context(env, app = @app)
|
|
87
|
+
body = nil
|
|
88
|
+
super(env, ->(app_env) { app.call(app_env).tap { |response| body = response[2] } })
|
|
78
89
|
rescue MissingSessionError
|
|
79
|
-
|
|
90
|
+
body.close if body.respond_to?(:close)
|
|
91
|
+
missing_session_response(env)
|
|
80
92
|
end
|
|
81
93
|
|
|
82
94
|
def find_session(req, sid)
|
|
@@ -111,13 +123,23 @@ module Rack
|
|
|
111
123
|
def delete_session(_req, sid, options)
|
|
112
124
|
with_dalli_client do |dc|
|
|
113
125
|
key = memcached_key_from_sid(sid)
|
|
114
|
-
dc
|
|
126
|
+
delete_session_key(dc, key) if key
|
|
115
127
|
generate_sid_with(dc) unless options[:drop]
|
|
116
128
|
end
|
|
117
129
|
end
|
|
118
130
|
|
|
119
131
|
private
|
|
120
132
|
|
|
133
|
+
# A failed delete leaves the old session readable in memcached (e.g.
|
|
134
|
+
# after logout), so always log it, even though with_dalli_client then
|
|
135
|
+
# handles the error as before.
|
|
136
|
+
def delete_session_key(client, key)
|
|
137
|
+
client.delete(key)
|
|
138
|
+
rescue ::Dalli::DalliError, Errno::ECONNREFUSED => e
|
|
139
|
+
::Dalli.logger.warn("#{self.class} failed to delete a session from memcached: #{e.class}: #{e.message}")
|
|
140
|
+
raise
|
|
141
|
+
end
|
|
142
|
+
|
|
121
143
|
def memcached_key_from_sid(sid)
|
|
122
144
|
sid.private_id if sid.respond_to?(:private_id)
|
|
123
145
|
end
|
|
@@ -162,6 +184,12 @@ module Rack
|
|
|
162
184
|
end
|
|
163
185
|
end
|
|
164
186
|
|
|
187
|
+
def missing_session_response(env)
|
|
188
|
+
options = env[Rack::RACK_SESSION_OPTIONS] || @default_options
|
|
189
|
+
cookie = Rack::Utils.delete_set_cookie_header(key, options.slice(:path, :domain))
|
|
190
|
+
[401, { 'content-type' => 'text/plain', 'set-cookie' => cookie }, ['Wrong session ID']]
|
|
191
|
+
end
|
|
192
|
+
|
|
165
193
|
def write_session_safely!(dalli_client, sid, persisted_data, write_args:)
|
|
166
194
|
if persisted_data && persisted_data[:id] == sid # That means that we update the existing session
|
|
167
195
|
# Override the session only if it still exists in the store!
|
|
@@ -181,15 +209,14 @@ module Rack
|
|
|
181
209
|
end
|
|
182
210
|
|
|
183
211
|
def retrieve_client_options(options)
|
|
184
|
-
# Filter out Rack::Session-specific options and apply our defaults
|
|
185
|
-
filtered_opts = options.reject { |k, _| DEFAULT_OPTIONS.key? k }
|
|
212
|
+
# Filter out Rack::Session-specific and pool options and apply our defaults
|
|
213
|
+
filtered_opts = options.reject { |k, _| DEFAULT_OPTIONS.key?(k) || POOL_OPTIONS.key?(k) }
|
|
186
214
|
DEFAULT_DALLI_OPTIONS.merge(filtered_opts)
|
|
187
215
|
end
|
|
188
216
|
|
|
189
217
|
def retrieve_pool_options(options)
|
|
190
|
-
{}
|
|
191
|
-
pool_options[
|
|
192
|
-
pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout]
|
|
218
|
+
POOL_OPTIONS.each_with_object({}) do |(option, pool_option), pool_options|
|
|
219
|
+
pool_options[pool_option] = options[option] if options[option]
|
|
193
220
|
end
|
|
194
221
|
end
|
|
195
222
|
|
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: 5.2.
|
|
4
|
+
version: 5.2.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter M. Goldstein
|
|
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
89
89
|
- !ruby/object:Gem::Version
|
|
90
90
|
version: '0'
|
|
91
91
|
requirements: []
|
|
92
|
-
rubygems_version: 4.0.
|
|
92
|
+
rubygems_version: 4.0.23
|
|
93
93
|
specification_version: 4
|
|
94
94
|
summary: High performance memcached client for Ruby
|
|
95
95
|
test_files: []
|