dalli 2.7.0 → 2.7.11
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of dalli might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/Gemfile +6 -3
- data/History.md +94 -0
- data/LICENSE +1 -1
- data/README.md +62 -43
- data/lib/action_dispatch/middleware/session/dalli_store.rb +2 -1
- data/lib/active_support/cache/dalli_store.rb +121 -41
- data/lib/dalli/cas/client.rb +5 -4
- data/lib/dalli/client.rb +103 -52
- data/lib/dalli/compressor.rb +2 -1
- data/lib/dalli/options.rb +1 -0
- data/lib/dalli/railtie.rb +1 -0
- data/lib/dalli/ring.rb +6 -6
- data/lib/dalli/server.rb +151 -91
- data/lib/dalli/socket.rb +104 -42
- data/lib/dalli/version.rb +2 -1
- data/lib/dalli.rb +3 -0
- data/lib/rack/session/dalli.rb +140 -27
- metadata +22 -95
- data/Performance.md +0 -42
- data/Rakefile +0 -42
- data/dalli.gemspec +0 -29
- data/test/benchmark_test.rb +0 -242
- data/test/helper.rb +0 -55
- data/test/memcached_mock.rb +0 -121
- data/test/sasldb +0 -1
- data/test/test_active_support.rb +0 -427
- data/test/test_cas_client.rb +0 -107
- data/test/test_compressor.rb +0 -53
- data/test/test_dalli.rb +0 -601
- data/test/test_encoding.rb +0 -32
- data/test/test_failover.rb +0 -128
- data/test/test_network.rb +0 -54
- data/test/test_rack_session.rb +0 -321
- data/test/test_ring.rb +0 -85
- data/test/test_sasl.rb +0 -110
- data/test/test_serializer.rb +0 -30
data/lib/dalli/server.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'socket'
|
2
3
|
require 'timeout'
|
3
4
|
|
@@ -8,10 +9,13 @@ module Dalli
|
|
8
9
|
attr_accessor :weight
|
9
10
|
attr_accessor :options
|
10
11
|
attr_reader :sock
|
12
|
+
attr_reader :socket_type # possible values: :unix, :tcp
|
11
13
|
|
14
|
+
DEFAULT_PORT = 11211
|
15
|
+
DEFAULT_WEIGHT = 1
|
12
16
|
DEFAULTS = {
|
13
17
|
# seconds between trying to contact a remote server
|
14
|
-
:down_retry_delay =>
|
18
|
+
:down_retry_delay => 60,
|
15
19
|
# connect/read/write timeout for socket operations
|
16
20
|
:socket_timeout => 0.5,
|
17
21
|
# times a socket operation may fail before considering the server dead
|
@@ -20,6 +24,8 @@ module Dalli
|
|
20
24
|
:socket_failure_delay => 0.01,
|
21
25
|
# max size of value in bytes (default is 1 MB, can be overriden with "memcached -I <size>")
|
22
26
|
:value_max_bytes => 1024 * 1024,
|
27
|
+
# surpassing value_max_bytes either warns (false) or throws (true)
|
28
|
+
:error_when_over_max_size => false,
|
23
29
|
:compressor => Compressor,
|
24
30
|
# min byte size to attempt compression
|
25
31
|
:compression_min_size => 1024,
|
@@ -28,15 +34,15 @@ module Dalli
|
|
28
34
|
:serializer => Marshal,
|
29
35
|
:username => nil,
|
30
36
|
:password => nil,
|
31
|
-
:keepalive => true
|
37
|
+
:keepalive => true,
|
38
|
+
# max byte size for SO_SNDBUF
|
39
|
+
:sndbuf => nil,
|
40
|
+
# max byte size for SO_RCVBUF
|
41
|
+
:rcvbuf => nil
|
32
42
|
}
|
33
43
|
|
34
44
|
def initialize(attribs, options = {})
|
35
|
-
|
36
|
-
@port ||= 11211
|
37
|
-
@port = Integer(@port)
|
38
|
-
@weight ||= 1
|
39
|
-
@weight = Integer(@weight)
|
45
|
+
@hostname, @port, @weight, @socket_type = parse_hostname(attribs)
|
40
46
|
@fail_count = 0
|
41
47
|
@down_at = nil
|
42
48
|
@last_down_at = nil
|
@@ -49,27 +55,28 @@ module Dalli
|
|
49
55
|
end
|
50
56
|
|
51
57
|
def name
|
52
|
-
|
58
|
+
if socket_type == :unix
|
59
|
+
hostname
|
60
|
+
else
|
61
|
+
"#{hostname}:#{port}"
|
62
|
+
end
|
53
63
|
end
|
54
64
|
|
55
65
|
# Chokepoint method for instrumentation
|
56
66
|
def request(op, *args)
|
57
67
|
verify_state
|
58
|
-
raise Dalli::NetworkError, "#{
|
68
|
+
raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}. If you are sure it is running, ensure memcached version is > 1.4." unless alive?
|
59
69
|
begin
|
60
70
|
send(op, *args)
|
61
|
-
rescue Dalli::NetworkError
|
62
|
-
raise
|
63
71
|
rescue Dalli::MarshalError => ex
|
64
72
|
Dalli.logger.error "Marshalling error for key '#{args.first}': #{ex.message}"
|
65
73
|
Dalli.logger.error "You are trying to cache a Ruby object which cannot be serialized to memcached."
|
66
74
|
Dalli.logger.error ex.backtrace.join("\n\t")
|
67
75
|
false
|
68
|
-
rescue Dalli::DalliError
|
76
|
+
rescue Dalli::DalliError, Dalli::NetworkError, Dalli::ValueOverMaxSize, Timeout::Error
|
69
77
|
raise
|
70
78
|
rescue => ex
|
71
|
-
Dalli.logger.error "Unexpected exception
|
72
|
-
Dalli.logger.error "This is a bug in Dalli, please enter an issue in Github if it does not already exist."
|
79
|
+
Dalli.logger.error "Unexpected exception during Dalli request: #{ex.class.name}: #{ex.message}"
|
73
80
|
Dalli.logger.error ex.backtrace.join("\n\t")
|
74
81
|
down!
|
75
82
|
end
|
@@ -80,7 +87,7 @@ module Dalli
|
|
80
87
|
|
81
88
|
if @last_down_at && @last_down_at + options[:down_retry_delay] >= Time.now
|
82
89
|
time = @last_down_at + options[:down_retry_delay] - Time.now
|
83
|
-
Dalli.logger.debug { "down_retry_delay not reached for #{
|
90
|
+
Dalli.logger.debug { "down_retry_delay not reached for #{name} (%.3f seconds left)" % time }
|
84
91
|
return false
|
85
92
|
end
|
86
93
|
|
@@ -120,7 +127,7 @@ module Dalli
|
|
120
127
|
def multi_response_start
|
121
128
|
verify_state
|
122
129
|
write_noop
|
123
|
-
@multi_buffer = ''
|
130
|
+
@multi_buffer = String.new('')
|
124
131
|
@position = 0
|
125
132
|
@inprogress = true
|
126
133
|
end
|
@@ -199,20 +206,28 @@ module Dalli
|
|
199
206
|
|
200
207
|
def verify_state
|
201
208
|
failure!(RuntimeError.new('Already writing to socket')) if @inprogress
|
202
|
-
|
209
|
+
if @pid && @pid != Process.pid
|
210
|
+
message = 'Fork detected, re-connecting child process...'
|
211
|
+
Dalli.logger.info { message }
|
212
|
+
reconnect! message
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def reconnect!(message)
|
217
|
+
close
|
218
|
+
sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
|
219
|
+
raise Dalli::NetworkError, message
|
203
220
|
end
|
204
221
|
|
205
222
|
def failure!(exception)
|
206
|
-
message = "#{
|
207
|
-
Dalli.logger.
|
223
|
+
message = "#{name} failed (count: #{@fail_count}) #{exception.class}: #{exception.message}"
|
224
|
+
Dalli.logger.warn { message }
|
208
225
|
|
209
226
|
@fail_count += 1
|
210
227
|
if @fail_count >= options[:socket_max_failures]
|
211
228
|
down!
|
212
229
|
else
|
213
|
-
|
214
|
-
sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
|
215
|
-
raise Dalli::NetworkError, "Socket operation failed, retrying..."
|
230
|
+
reconnect! 'Socket operation failed, retrying...'
|
216
231
|
end
|
217
232
|
end
|
218
233
|
|
@@ -223,21 +238,21 @@ module Dalli
|
|
223
238
|
|
224
239
|
if @down_at
|
225
240
|
time = Time.now - @down_at
|
226
|
-
Dalli.logger.debug { "#{
|
241
|
+
Dalli.logger.debug { "#{name} is still down (for %.3f seconds now)" % time }
|
227
242
|
else
|
228
243
|
@down_at = @last_down_at
|
229
|
-
Dalli.logger.warn { "#{
|
244
|
+
Dalli.logger.warn { "#{name} is down" }
|
230
245
|
end
|
231
246
|
|
232
247
|
@error = $! && $!.class.name
|
233
248
|
@msg = @msg || ($! && $!.message && !$!.message.empty? && $!.message)
|
234
|
-
raise Dalli::NetworkError, "#{
|
249
|
+
raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}"
|
235
250
|
end
|
236
251
|
|
237
252
|
def up!
|
238
253
|
if @down_at
|
239
254
|
time = Time.now - @down_at
|
240
|
-
Dalli.logger.warn { "#{
|
255
|
+
Dalli.logger.warn { "#{name} is back (downtime was %.3f seconds)" % time }
|
241
256
|
end
|
242
257
|
|
243
258
|
@fail_count = 0
|
@@ -251,14 +266,14 @@ module Dalli
|
|
251
266
|
Thread.current[:dalli_multi]
|
252
267
|
end
|
253
268
|
|
254
|
-
def get(key)
|
269
|
+
def get(key, options=nil)
|
255
270
|
req = [REQUEST, OPCODES[:get], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:get])
|
256
271
|
write(req)
|
257
|
-
generic_response(true)
|
272
|
+
generic_response(true, !!(options && options.is_a?(Hash) && options[:cache_nils]))
|
258
273
|
end
|
259
274
|
|
260
275
|
def send_multiget(keys)
|
261
|
-
req = ""
|
276
|
+
req = String.new("")
|
262
277
|
keys.each do |key|
|
263
278
|
req << [REQUEST, OPCODES[:getkq], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:getkq])
|
264
279
|
end
|
@@ -268,6 +283,7 @@ module Dalli
|
|
268
283
|
|
269
284
|
def set(key, value, ttl, cas, options)
|
270
285
|
(value, flags) = serialize(key, value, options)
|
286
|
+
ttl = sanitize_ttl(ttl)
|
271
287
|
|
272
288
|
guard_max_value(key, value) do
|
273
289
|
req = [REQUEST, OPCODES[multi? ? :setq : :set], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, cas, flags, ttl, key, value].pack(FORMAT[:set])
|
@@ -278,6 +294,7 @@ module Dalli
|
|
278
294
|
|
279
295
|
def add(key, value, ttl, options)
|
280
296
|
(value, flags) = serialize(key, value, options)
|
297
|
+
ttl = sanitize_ttl(ttl)
|
281
298
|
|
282
299
|
guard_max_value(key, value) do
|
283
300
|
req = [REQUEST, OPCODES[multi? ? :addq : :add], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, 0, flags, ttl, key, value].pack(FORMAT[:add])
|
@@ -288,6 +305,7 @@ module Dalli
|
|
288
305
|
|
289
306
|
def replace(key, value, ttl, cas, options)
|
290
307
|
(value, flags) = serialize(key, value, options)
|
308
|
+
ttl = sanitize_ttl(ttl)
|
291
309
|
|
292
310
|
guard_max_value(key, value) do
|
293
311
|
req = [REQUEST, OPCODES[multi? ? :replaceq : :replace], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, 0, cas, flags, ttl, key, value].pack(FORMAT[:replace])
|
@@ -308,26 +326,32 @@ module Dalli
|
|
308
326
|
generic_response
|
309
327
|
end
|
310
328
|
|
311
|
-
def
|
312
|
-
expiry = default ? ttl : 0xFFFFFFFF
|
329
|
+
def decr_incr(opcode, key, count, ttl, default)
|
330
|
+
expiry = default ? sanitize_ttl(ttl) : 0xFFFFFFFF
|
313
331
|
default ||= 0
|
314
332
|
(h, l) = split(count)
|
315
333
|
(dh, dl) = split(default)
|
316
|
-
req = [REQUEST, OPCODES[
|
334
|
+
req = [REQUEST, OPCODES[opcode], key.bytesize, 20, 0, 0, key.bytesize + 20, 0, 0, h, l, dh, dl, expiry, key].pack(FORMAT[opcode])
|
317
335
|
write(req)
|
318
336
|
body = generic_response
|
319
|
-
body ?
|
337
|
+
body ? body.unpack('Q>').first : body
|
338
|
+
end
|
339
|
+
|
340
|
+
def decr(key, count, ttl, default)
|
341
|
+
decr_incr :decr, key, count, ttl, default
|
320
342
|
end
|
321
343
|
|
322
344
|
def incr(key, count, ttl, default)
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
345
|
+
decr_incr :incr, key, count, ttl, default
|
346
|
+
end
|
347
|
+
|
348
|
+
def write_append_prepend(opcode, key, value)
|
349
|
+
write_generic [REQUEST, OPCODES[opcode], key.bytesize, 0, 0, 0, value.bytesize + key.bytesize, 0, 0, key, value].pack(FORMAT[opcode])
|
350
|
+
end
|
351
|
+
|
352
|
+
def write_generic(bytes)
|
353
|
+
write(bytes)
|
354
|
+
generic_response
|
331
355
|
end
|
332
356
|
|
333
357
|
def write_noop
|
@@ -343,15 +367,11 @@ module Dalli
|
|
343
367
|
end
|
344
368
|
|
345
369
|
def append(key, value)
|
346
|
-
|
347
|
-
write(req)
|
348
|
-
generic_response
|
370
|
+
write_append_prepend :append, key, value
|
349
371
|
end
|
350
372
|
|
351
373
|
def prepend(key, value)
|
352
|
-
|
353
|
-
write(req)
|
354
|
-
generic_response
|
374
|
+
write_append_prepend :prepend, key, value
|
355
375
|
end
|
356
376
|
|
357
377
|
def stats(info='')
|
@@ -361,9 +381,7 @@ module Dalli
|
|
361
381
|
end
|
362
382
|
|
363
383
|
def reset_stats
|
364
|
-
|
365
|
-
write(req)
|
366
|
-
generic_response
|
384
|
+
write_generic [REQUEST, OPCODES[:stat], 'reset'.bytesize, 0, 0, 0, 'reset'.bytesize, 0, 0, 'reset'].pack(FORMAT[:stat])
|
367
385
|
end
|
368
386
|
|
369
387
|
def cas(key)
|
@@ -373,15 +391,12 @@ module Dalli
|
|
373
391
|
end
|
374
392
|
|
375
393
|
def version
|
376
|
-
|
377
|
-
write(req)
|
378
|
-
generic_response
|
394
|
+
write_generic [REQUEST, OPCODES[:version], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
|
379
395
|
end
|
380
396
|
|
381
397
|
def touch(key, ttl)
|
382
|
-
|
383
|
-
|
384
|
-
generic_response
|
398
|
+
ttl = sanitize_ttl(ttl)
|
399
|
+
write_generic [REQUEST, OPCODES[:touch], key.bytesize, 4, 0, 0, key.bytesize + 4, 0, 0, ttl, key].pack(FORMAT[:touch])
|
385
400
|
end
|
386
401
|
|
387
402
|
# http://www.hjp.at/zettel/m/memcached_flags.rxml
|
@@ -396,6 +411,8 @@ module Dalli
|
|
396
411
|
marshalled = true
|
397
412
|
begin
|
398
413
|
self.serializer.dump(value)
|
414
|
+
rescue Timeout::Error => e
|
415
|
+
raise e
|
399
416
|
rescue => ex
|
400
417
|
# Marshalling can throw several different types of generic Ruby exceptions.
|
401
418
|
# Convert to a specific exception so we can special case it higher up the stack.
|
@@ -407,7 +424,8 @@ module Dalli
|
|
407
424
|
value.to_s
|
408
425
|
end
|
409
426
|
compressed = false
|
410
|
-
if
|
427
|
+
set_compress_option = true if options && options[:compress]
|
428
|
+
if (@options[:compress] || set_compress_option) && value.bytesize >= @options[:compression_min_size] &&
|
411
429
|
(!@options[:compression_max_size] || value.bytesize <= @options[:compression_max_size])
|
412
430
|
value = self.compressor.compress(value)
|
413
431
|
compressed = true
|
@@ -429,14 +447,15 @@ module Dalli
|
|
429
447
|
rescue ArgumentError
|
430
448
|
raise if $!.message !~ /undefined class|marshal data too short/
|
431
449
|
raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
|
450
|
+
rescue NameError
|
451
|
+
raise if $!.message !~ /uninitialized constant/
|
452
|
+
raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
|
432
453
|
rescue Zlib::Error
|
433
454
|
raise UnmarshalError, "Unable to uncompress value: #{$!.message}"
|
434
455
|
end
|
435
456
|
|
436
457
|
def data_cas_response
|
437
|
-
|
438
|
-
raise Dalli::NetworkError, 'No response' if !header
|
439
|
-
(extras, _, status, count, _, cas) = header.unpack(CAS_HEADER)
|
458
|
+
(extras, _, status, count, _, cas) = read_header.unpack(CAS_HEADER)
|
440
459
|
data = read(count) if count > 0
|
441
460
|
if status == 1
|
442
461
|
nil
|
@@ -458,18 +477,35 @@ module Dalli
|
|
458
477
|
if value.bytesize <= @options[:value_max_bytes]
|
459
478
|
yield
|
460
479
|
else
|
461
|
-
|
480
|
+
message = "Value for #{key} over max size: #{@options[:value_max_bytes]} <= #{value.bytesize}"
|
481
|
+
raise Dalli::ValueOverMaxSize, message if @options[:error_when_over_max_size]
|
482
|
+
|
483
|
+
Dalli.logger.error "#{message} - this value may be truncated by memcached"
|
462
484
|
false
|
463
485
|
end
|
464
486
|
end
|
465
487
|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
488
|
+
# https://github.com/memcached/memcached/blob/master/doc/protocol.txt#L79
|
489
|
+
# > An expiration time, in seconds. Can be up to 30 days. After 30 days, is treated as a unix timestamp of an exact date.
|
490
|
+
MAX_ACCEPTABLE_EXPIRATION_INTERVAL = 30*24*60*60 # 30 days
|
491
|
+
def sanitize_ttl(ttl)
|
492
|
+
ttl_as_i = ttl.to_i
|
493
|
+
return ttl_as_i if ttl_as_i <= MAX_ACCEPTABLE_EXPIRATION_INTERVAL
|
494
|
+
now = Time.now.to_i
|
495
|
+
return ttl_as_i if ttl_as_i > now # already a timestamp
|
496
|
+
Dalli.logger.debug "Expiration interval (#{ttl_as_i}) too long for Memcached, converting to an expiration timestamp"
|
497
|
+
now + ttl_as_i
|
498
|
+
end
|
499
|
+
|
500
|
+
# Implements the NullObject pattern to store an application-defined value for 'Key not found' responses.
|
501
|
+
class NilObject; end
|
502
|
+
NOT_FOUND = NilObject.new
|
503
|
+
|
504
|
+
def generic_response(unpack=false, cache_nils=false)
|
505
|
+
(extras, _, status, count) = read_header.unpack(NORMAL_HEADER)
|
470
506
|
data = read(count) if count > 0
|
471
507
|
if status == 1
|
472
|
-
nil
|
508
|
+
cache_nils ? NOT_FOUND : nil
|
473
509
|
elsif status == 2 || status == 5
|
474
510
|
false # Not stored, normal status for add operation
|
475
511
|
elsif status != 0
|
@@ -484,9 +520,7 @@ module Dalli
|
|
484
520
|
end
|
485
521
|
|
486
522
|
def cas_response
|
487
|
-
|
488
|
-
raise Dalli::NetworkError, 'No response' if !header
|
489
|
-
(_, _, status, count, _, cas) = header.unpack(CAS_HEADER)
|
523
|
+
(_, _, status, count, _, cas) = read_header.unpack(CAS_HEADER)
|
490
524
|
read(count) if count > 0 # this is potential data that we don't care about
|
491
525
|
if status == 1
|
492
526
|
nil
|
@@ -501,10 +535,8 @@ module Dalli
|
|
501
535
|
|
502
536
|
def keyvalue_response
|
503
537
|
hash = {}
|
504
|
-
|
505
|
-
|
506
|
-
raise Dalli::NetworkError, 'No response' if !header
|
507
|
-
(key_length, _, body_length, _) = header.unpack(KV_HEADER)
|
538
|
+
while true
|
539
|
+
(key_length, _, body_length, _) = read_header.unpack(KV_HEADER)
|
508
540
|
return hash if key_length == 0
|
509
541
|
key = read(key_length)
|
510
542
|
value = read(body_length - key_length) if body_length - key_length > 0
|
@@ -514,10 +546,8 @@ module Dalli
|
|
514
546
|
|
515
547
|
def multi_response
|
516
548
|
hash = {}
|
517
|
-
|
518
|
-
|
519
|
-
raise Dalli::NetworkError, 'No response' if !header
|
520
|
-
(key_length, _, body_length, _) = header.unpack(KV_HEADER)
|
549
|
+
while true
|
550
|
+
(key_length, _, body_length, _) = read_header.unpack(KV_HEADER)
|
521
551
|
return hash if key_length == 0
|
522
552
|
flags = read(4).unpack('N')[0]
|
523
553
|
key = read(key_length)
|
@@ -548,14 +578,22 @@ module Dalli
|
|
548
578
|
end
|
549
579
|
end
|
550
580
|
|
581
|
+
def read_header
|
582
|
+
read(24) || raise(Dalli::NetworkError, 'No response')
|
583
|
+
end
|
584
|
+
|
551
585
|
def connect
|
552
|
-
Dalli.logger.debug { "Dalli::Server#connect #{
|
586
|
+
Dalli.logger.debug { "Dalli::Server#connect #{name}" }
|
553
587
|
|
554
588
|
begin
|
555
589
|
@pid = Process.pid
|
556
|
-
|
557
|
-
|
590
|
+
if socket_type == :unix
|
591
|
+
@sock = KSocket::UNIX.open(hostname, self, options)
|
592
|
+
else
|
593
|
+
@sock = KSocket::TCP.open(hostname, port, self, options)
|
594
|
+
end
|
558
595
|
sasl_authentication if need_auth?
|
596
|
+
@version = version # trigger actual connect
|
559
597
|
up!
|
560
598
|
rescue Dalli::DalliError # SASL auth failure
|
561
599
|
raise
|
@@ -569,13 +607,11 @@ module Dalli
|
|
569
607
|
[n >> 32, 0xFFFFFFFF & n]
|
570
608
|
end
|
571
609
|
|
572
|
-
def longlong(a, b)
|
573
|
-
(a << 32) | b
|
574
|
-
end
|
575
|
-
|
576
610
|
REQUEST = 0x80
|
577
611
|
RESPONSE = 0x81
|
578
612
|
|
613
|
+
# Response codes taken from:
|
614
|
+
# https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped#response-status
|
579
615
|
RESPONSE_CODES = {
|
580
616
|
0 => 'No error',
|
581
617
|
1 => 'Key not found',
|
@@ -584,9 +620,16 @@ module Dalli
|
|
584
620
|
4 => 'Invalid arguments',
|
585
621
|
5 => 'Item not stored',
|
586
622
|
6 => 'Incr/decr on a non-numeric value',
|
623
|
+
7 => 'The vbucket belongs to another server',
|
624
|
+
8 => 'Authentication error',
|
625
|
+
9 => 'Authentication continue',
|
587
626
|
0x20 => 'Authentication required',
|
588
627
|
0x81 => 'Unknown command',
|
589
628
|
0x82 => 'Out of memory',
|
629
|
+
0x83 => 'Not supported',
|
630
|
+
0x84 => 'Internal error',
|
631
|
+
0x85 => 'Busy',
|
632
|
+
0x86 => 'Temporary failure'
|
590
633
|
}
|
591
634
|
|
592
635
|
OPCODES = {
|
@@ -661,11 +704,10 @@ module Dalli
|
|
661
704
|
# negotiate
|
662
705
|
req = [REQUEST, OPCODES[:auth_negotiation], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
|
663
706
|
write(req)
|
664
|
-
|
665
|
-
|
666
|
-
(extras, type, status, count) = header.unpack(NORMAL_HEADER)
|
707
|
+
|
708
|
+
(extras, _type, status, count) = read_header.unpack(NORMAL_HEADER)
|
667
709
|
raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
|
668
|
-
content = read(count)
|
710
|
+
content = read(count).gsub(/\u0000/, ' ')
|
669
711
|
return (Dalli.logger.debug("Authentication not required/supported by server")) if status == 0x81
|
670
712
|
mechanisms = content.split(' ')
|
671
713
|
raise NotImplementedError, "Dalli only supports the PLAIN authentication mechanism" if !mechanisms.include?('PLAIN')
|
@@ -676,9 +718,7 @@ module Dalli
|
|
676
718
|
req = [REQUEST, OPCODES[:auth_request], mechanism.bytesize, 0, 0, 0, mechanism.bytesize + msg.bytesize, 0, 0, mechanism, msg].pack(FORMAT[:auth_request])
|
677
719
|
write(req)
|
678
720
|
|
679
|
-
|
680
|
-
raise Dalli::NetworkError, 'No response' if !header
|
681
|
-
(extras, type, status, count) = header.unpack(NORMAL_HEADER)
|
721
|
+
(extras, _type, status, count) = read_header.unpack(NORMAL_HEADER)
|
682
722
|
raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
|
683
723
|
content = read(count)
|
684
724
|
return Dalli.logger.info("Dalli/SASL: #{content}") if status == 0
|
@@ -688,5 +728,25 @@ module Dalli
|
|
688
728
|
# (step, msg) = sasl.receive('challenge', content)
|
689
729
|
# raise Dalli::NetworkError, "Authentication failed" if sasl.failed? || step != 'response'
|
690
730
|
end
|
731
|
+
|
732
|
+
def parse_hostname(str)
|
733
|
+
res = str.match(/\A(\[([\h:]+)\]|[^:]+)(?::(\d+))?(?::(\d+))?\z/)
|
734
|
+
raise Dalli::DalliError, "Could not parse hostname #{str}" if res.nil? || res[1] == '[]'
|
735
|
+
hostnam = res[2] || res[1]
|
736
|
+
if hostnam =~ /\A\//
|
737
|
+
socket_type = :unix
|
738
|
+
# in case of unix socket, allow only setting of weight, not port
|
739
|
+
raise Dalli::DalliError, "Could not parse hostname #{str}" if res[4]
|
740
|
+
weigh = res[3]
|
741
|
+
else
|
742
|
+
socket_type = :tcp
|
743
|
+
por = res[3] || DEFAULT_PORT
|
744
|
+
por = Integer(por)
|
745
|
+
weigh = res[4]
|
746
|
+
end
|
747
|
+
weigh ||= DEFAULT_WEIGHT
|
748
|
+
weigh = Integer(weigh)
|
749
|
+
return hostnam, por, weigh, socket_type
|
750
|
+
end
|
691
751
|
end
|
692
752
|
end
|