dalli 2.7.6 → 3.2.2

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.

Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +5 -1
  3. data/History.md +175 -0
  4. data/README.md +27 -213
  5. data/lib/dalli/cas/client.rb +2 -57
  6. data/lib/dalli/client.rb +228 -254
  7. data/lib/dalli/compressor.rb +13 -2
  8. data/lib/dalli/key_manager.rb +113 -0
  9. data/lib/dalli/options.rb +7 -7
  10. data/lib/dalli/pipelined_getter.rb +177 -0
  11. data/lib/dalli/protocol/base.rb +241 -0
  12. data/lib/dalli/protocol/binary/request_formatter.rb +117 -0
  13. data/lib/dalli/protocol/binary/response_header.rb +36 -0
  14. data/lib/dalli/protocol/binary/response_processor.rb +239 -0
  15. data/lib/dalli/protocol/binary/sasl_authentication.rb +60 -0
  16. data/lib/dalli/protocol/binary.rb +173 -0
  17. data/lib/dalli/protocol/connection_manager.rb +252 -0
  18. data/lib/dalli/protocol/meta/key_regularizer.rb +31 -0
  19. data/lib/dalli/protocol/meta/request_formatter.rb +108 -0
  20. data/lib/dalli/protocol/meta/response_processor.rb +211 -0
  21. data/lib/dalli/protocol/meta.rb +177 -0
  22. data/lib/dalli/protocol/response_buffer.rb +54 -0
  23. data/lib/dalli/protocol/server_config_parser.rb +84 -0
  24. data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
  25. data/lib/dalli/protocol/value_compressor.rb +85 -0
  26. data/lib/dalli/protocol/value_marshaller.rb +59 -0
  27. data/lib/dalli/protocol/value_serializer.rb +91 -0
  28. data/lib/dalli/protocol.rb +8 -0
  29. data/lib/dalli/ring.rb +95 -84
  30. data/lib/dalli/server.rb +4 -743
  31. data/lib/dalli/servers_arg_normalizer.rb +54 -0
  32. data/lib/dalli/socket.rb +118 -130
  33. data/lib/dalli/version.rb +5 -1
  34. data/lib/dalli.rb +45 -14
  35. data/lib/rack/session/dalli.rb +156 -50
  36. metadata +64 -27
  37. data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -81
  38. data/lib/active_support/cache/dalli_store.rb +0 -403
  39. data/lib/dalli/railtie.rb +0 -7
data/lib/dalli/server.rb CHANGED
@@ -1,745 +1,6 @@
1
- require 'socket'
2
- require 'timeout'
1
+ # frozen_string_literal: true
3
2
 
4
- module Dalli
5
- class Server
6
- attr_accessor :hostname
7
- attr_accessor :port
8
- attr_accessor :weight
9
- attr_accessor :options
10
- attr_reader :sock
11
- attr_reader :socket_type # possible values: :unix, :tcp
12
-
13
- DEFAULT_PORT = 11211
14
- DEFAULT_WEIGHT = 1
15
- DEFAULTS = {
16
- # seconds between trying to contact a remote server
17
- :down_retry_delay => 1,
18
- # connect/read/write timeout for socket operations
19
- :socket_timeout => 0.5,
20
- # times a socket operation may fail before considering the server dead
21
- :socket_max_failures => 2,
22
- # amount of time to sleep between retries when a failure occurs
23
- :socket_failure_delay => 0.01,
24
- # max size of value in bytes (default is 1 MB, can be overriden with "memcached -I <size>")
25
- :value_max_bytes => 1024 * 1024,
26
- :compressor => Compressor,
27
- # min byte size to attempt compression
28
- :compression_min_size => 1024,
29
- # max byte size for compression
30
- :compression_max_size => false,
31
- :serializer => Marshal,
32
- :username => nil,
33
- :password => nil,
34
- :keepalive => true,
35
- # max byte size for SO_SNDBUF
36
- :sndbuf => nil,
37
- # max byte size for SO_RCVBUF
38
- :rcvbuf => nil
39
- }
40
-
41
- def initialize(attribs, options = {})
42
- @hostname, @port, @weight, @socket_type = parse_hostname(attribs)
43
- @fail_count = 0
44
- @down_at = nil
45
- @last_down_at = nil
46
- @options = DEFAULTS.merge(options)
47
- @sock = nil
48
- @msg = nil
49
- @error = nil
50
- @pid = nil
51
- @inprogress = nil
52
- end
53
-
54
- def name
55
- if socket_type == :unix
56
- hostname
57
- else
58
- "#{hostname}:#{port}"
59
- end
60
- end
61
-
62
- # Chokepoint method for instrumentation
63
- def request(op, *args)
64
- verify_state
65
- raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}. If you are sure it is running, ensure memcached version is > 1.4." unless alive?
66
- begin
67
- send(op, *args)
68
- rescue Dalli::NetworkError
69
- raise
70
- rescue Dalli::MarshalError => ex
71
- Dalli.logger.error "Marshalling error for key '#{args.first}': #{ex.message}"
72
- Dalli.logger.error "You are trying to cache a Ruby object which cannot be serialized to memcached."
73
- Dalli.logger.error ex.backtrace.join("\n\t")
74
- false
75
- rescue Dalli::DalliError
76
- raise
77
- rescue Timeout::Error
78
- raise
79
- rescue => ex
80
- Dalli.logger.error "Unexpected exception during Dalli request: #{ex.class.name}: #{ex.message}"
81
- Dalli.logger.error ex.backtrace.join("\n\t")
82
- down!
83
- end
84
- end
85
-
86
- def alive?
87
- return true if @sock
88
-
89
- if @last_down_at && @last_down_at + options[:down_retry_delay] >= Time.now
90
- time = @last_down_at + options[:down_retry_delay] - Time.now
91
- Dalli.logger.debug { "down_retry_delay not reached for #{name} (%.3f seconds left)" % time }
92
- return false
93
- end
94
-
95
- connect
96
- !!@sock
97
- rescue Dalli::NetworkError
98
- false
99
- end
100
-
101
- def close
102
- return unless @sock
103
- @sock.close rescue nil
104
- @sock = nil
105
- @pid = nil
106
- @inprogress = false
107
- end
108
-
109
- def lock!
110
- end
111
-
112
- def unlock!
113
- end
114
-
115
- def serializer
116
- @options[:serializer]
117
- end
118
-
119
- def compressor
120
- @options[:compressor]
121
- end
122
-
123
- # Start reading key/value pairs from this connection. This is usually called
124
- # after a series of GETKQ commands. A NOOP is sent, and the server begins
125
- # flushing responses for kv pairs that were found.
126
- #
127
- # Returns nothing.
128
- def multi_response_start
129
- verify_state
130
- write_noop
131
- @multi_buffer = ''
132
- @position = 0
133
- @inprogress = true
134
- end
135
-
136
- # Did the last call to #multi_response_start complete successfully?
137
- def multi_response_completed?
138
- @multi_buffer.nil?
139
- end
140
-
141
- # Attempt to receive and parse as many key/value pairs as possible
142
- # from this server. After #multi_response_start, this should be invoked
143
- # repeatedly whenever this server's socket is readable until
144
- # #multi_response_completed?.
145
- #
146
- # Returns a Hash of kv pairs received.
147
- def multi_response_nonblock
148
- raise 'multi_response has completed' if @multi_buffer.nil?
149
-
150
- @multi_buffer << @sock.read_available
151
- buf = @multi_buffer
152
- pos = @position
153
- values = {}
154
-
155
- while buf.bytesize - pos >= 24
156
- header = buf.slice(pos, 24)
157
- (key_length, _, body_length, cas) = header.unpack(KV_HEADER)
158
-
159
- if key_length == 0
160
- # all done!
161
- @multi_buffer = nil
162
- @position = nil
163
- @inprogress = false
164
- break
165
-
166
- elsif buf.bytesize - pos >= 24 + body_length
167
- flags = buf.slice(pos + 24, 4).unpack('N')[0]
168
- key = buf.slice(pos + 24 + 4, key_length)
169
- value = buf.slice(pos + 24 + 4 + key_length, body_length - key_length - 4) if body_length - key_length - 4 > 0
170
-
171
- pos = pos + 24 + body_length
172
-
173
- begin
174
- values[key] = [deserialize(value, flags), cas]
175
- rescue DalliError
176
- end
177
-
178
- else
179
- # not enough data yet, wait for more
180
- break
181
- end
182
- end
183
- @position = pos
184
-
185
- values
186
- rescue SystemCallError, Timeout::Error, EOFError => e
187
- failure!(e)
188
- end
189
-
190
- # Abort an earlier #multi_response_start. Used to signal an external
191
- # timeout. The underlying socket is disconnected, and the exception is
192
- # swallowed.
193
- #
194
- # Returns nothing.
195
- def multi_response_abort
196
- @multi_buffer = nil
197
- @position = nil
198
- @inprogress = false
199
- failure!(RuntimeError.new('External timeout'))
200
- rescue NetworkError
201
- true
202
- end
203
-
204
- # NOTE: Additional public methods should be overridden in Dalli::Threadsafe
205
-
206
- private
207
-
208
- def verify_state
209
- failure!(RuntimeError.new('Already writing to socket')) if @inprogress
210
- if @pid && @pid != Process.pid
211
- message = 'Fork detected, re-connecting child process...'
212
- Dalli.logger.info { message }
213
- reconnect! message
214
- end
215
- end
216
-
217
- def reconnect!(message)
218
- close
219
- sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
220
- raise Dalli::NetworkError, message
221
- end
222
-
223
- def failure!(exception)
224
- message = "#{name} failed (count: #{@fail_count}) #{exception.class}: #{exception.message}"
225
- Dalli.logger.warn { message }
226
-
227
- @fail_count += 1
228
- if @fail_count >= options[:socket_max_failures]
229
- down!
230
- else
231
- reconnect! 'Socket operation failed, retrying...'
232
- end
233
- end
234
-
235
- def down!
236
- close
237
-
238
- @last_down_at = Time.now
239
-
240
- if @down_at
241
- time = Time.now - @down_at
242
- Dalli.logger.debug { "#{name} is still down (for %.3f seconds now)" % time }
243
- else
244
- @down_at = @last_down_at
245
- Dalli.logger.warn { "#{name} is down" }
246
- end
247
-
248
- @error = $! && $!.class.name
249
- @msg = @msg || ($! && $!.message && !$!.message.empty? && $!.message)
250
- raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}"
251
- end
252
-
253
- def up!
254
- if @down_at
255
- time = Time.now - @down_at
256
- Dalli.logger.warn { "#{name} is back (downtime was %.3f seconds)" % time }
257
- end
258
-
259
- @fail_count = 0
260
- @down_at = nil
261
- @last_down_at = nil
262
- @msg = nil
263
- @error = nil
264
- end
265
-
266
- def multi?
267
- Thread.current[:dalli_multi]
268
- end
269
-
270
- def get(key, options=nil)
271
- req = [REQUEST, OPCODES[:get], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:get])
272
- write(req)
273
- generic_response(true, !!(options && options.is_a?(Hash) && options[:cache_nils]))
274
- end
275
-
276
- def send_multiget(keys)
277
- req = ""
278
- keys.each do |key|
279
- req << [REQUEST, OPCODES[:getkq], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:getkq])
280
- end
281
- # Could send noop here instead of in multi_response_start
282
- write(req)
283
- end
284
-
285
- def set(key, value, ttl, cas, options)
286
- (value, flags) = serialize(key, value, options)
287
- ttl = sanitize_ttl(ttl)
288
-
289
- guard_max_value(key, value) do
290
- 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])
291
- write(req)
292
- cas_response unless multi?
293
- end
294
- end
295
-
296
- def add(key, value, ttl, options)
297
- (value, flags) = serialize(key, value, options)
298
- ttl = sanitize_ttl(ttl)
299
-
300
- guard_max_value(key, value) do
301
- 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])
302
- write(req)
303
- cas_response unless multi?
304
- end
305
- end
306
-
307
- def replace(key, value, ttl, cas, options)
308
- (value, flags) = serialize(key, value, options)
309
- ttl = sanitize_ttl(ttl)
310
-
311
- guard_max_value(key, value) do
312
- 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])
313
- write(req)
314
- cas_response unless multi?
315
- end
316
- end
317
-
318
- def delete(key, cas)
319
- req = [REQUEST, OPCODES[multi? ? :deleteq : :delete], key.bytesize, 0, 0, 0, key.bytesize, 0, cas, key].pack(FORMAT[:delete])
320
- write(req)
321
- generic_response unless multi?
322
- end
323
-
324
- def flush(ttl)
325
- req = [REQUEST, OPCODES[:flush], 0, 4, 0, 0, 4, 0, 0, 0].pack(FORMAT[:flush])
326
- write(req)
327
- generic_response
328
- end
329
-
330
- def decr_incr(opcode, key, count, ttl, default)
331
- expiry = default ? sanitize_ttl(ttl) : 0xFFFFFFFF
332
- default ||= 0
333
- (h, l) = split(count)
334
- (dh, dl) = split(default)
335
- req = [REQUEST, OPCODES[opcode], key.bytesize, 20, 0, 0, key.bytesize + 20, 0, 0, h, l, dh, dl, expiry, key].pack(FORMAT[opcode])
336
- write(req)
337
- body = generic_response
338
- body ? body.unpack('Q>').first : body
339
- end
340
-
341
- def decr(key, count, ttl, default)
342
- decr_incr :decr, key, count, ttl, default
343
- end
344
-
345
- def incr(key, count, ttl, default)
346
- decr_incr :incr, key, count, ttl, default
347
- end
348
-
349
- def write_append_prepend(opcode, key, value)
350
- write_generic [REQUEST, OPCODES[opcode], key.bytesize, 0, 0, 0, value.bytesize + key.bytesize, 0, 0, key, value].pack(FORMAT[opcode])
351
- end
352
-
353
- def write_generic(bytes)
354
- write(bytes)
355
- generic_response
356
- end
357
-
358
- def write_noop
359
- req = [REQUEST, OPCODES[:noop], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
360
- write(req)
361
- end
362
-
363
- # Noop is a keepalive operation but also used to demarcate the end of a set of pipelined commands.
364
- # We need to read all the responses at once.
365
- def noop
366
- write_noop
367
- multi_response
368
- end
369
-
370
- def append(key, value)
371
- write_append_prepend :append, key, value
372
- end
373
-
374
- def prepend(key, value)
375
- write_append_prepend :prepend, key, value
376
- end
377
-
378
- def stats(info='')
379
- req = [REQUEST, OPCODES[:stat], info.bytesize, 0, 0, 0, info.bytesize, 0, 0, info].pack(FORMAT[:stat])
380
- write(req)
381
- keyvalue_response
382
- end
383
-
384
- def reset_stats
385
- write_generic [REQUEST, OPCODES[:stat], 'reset'.bytesize, 0, 0, 0, 'reset'.bytesize, 0, 0, 'reset'].pack(FORMAT[:stat])
386
- end
387
-
388
- def cas(key)
389
- req = [REQUEST, OPCODES[:get], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:get])
390
- write(req)
391
- data_cas_response
392
- end
393
-
394
- def version
395
- write_generic [REQUEST, OPCODES[:version], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
396
- end
397
-
398
- def touch(key, ttl)
399
- ttl = sanitize_ttl(ttl)
400
- write_generic [REQUEST, OPCODES[:touch], key.bytesize, 4, 0, 0, key.bytesize + 4, 0, 0, ttl, key].pack(FORMAT[:touch])
401
- end
402
-
403
- # http://www.hjp.at/zettel/m/memcached_flags.rxml
404
- # Looks like most clients use bit 0 to indicate native language serialization
405
- # and bit 1 to indicate gzip compression.
406
- FLAG_SERIALIZED = 0x1
407
- FLAG_COMPRESSED = 0x2
408
-
409
- def serialize(key, value, options=nil)
410
- marshalled = false
411
- value = unless options && options[:raw]
412
- marshalled = true
413
- begin
414
- self.serializer.dump(value)
415
- rescue Timeout::Error => e
416
- raise e
417
- rescue => ex
418
- # Marshalling can throw several different types of generic Ruby exceptions.
419
- # Convert to a specific exception so we can special case it higher up the stack.
420
- exc = Dalli::MarshalError.new(ex.message)
421
- exc.set_backtrace ex.backtrace
422
- raise exc
423
- end
424
- else
425
- value.to_s
426
- end
427
- compressed = false
428
- set_compress_option = true if options && options[:compress]
429
- if (@options[:compress] || set_compress_option) && value.bytesize >= @options[:compression_min_size] &&
430
- (!@options[:compression_max_size] || value.bytesize <= @options[:compression_max_size])
431
- value = self.compressor.compress(value)
432
- compressed = true
433
- end
434
-
435
- flags = 0
436
- flags |= FLAG_COMPRESSED if compressed
437
- flags |= FLAG_SERIALIZED if marshalled
438
- [value, flags]
439
- end
440
-
441
- def deserialize(value, flags)
442
- value = self.compressor.decompress(value) if (flags & FLAG_COMPRESSED) != 0
443
- value = self.serializer.load(value) if (flags & FLAG_SERIALIZED) != 0
444
- value
445
- rescue TypeError
446
- raise if $!.message !~ /needs to have method `_load'|exception class\/object expected|instance of IO needed|incompatible marshal file format/
447
- raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
448
- rescue ArgumentError
449
- raise if $!.message !~ /undefined class|marshal data too short/
450
- raise UnmarshalError, "Unable to unmarshal value: #{$!.message}"
451
- rescue Zlib::Error
452
- raise UnmarshalError, "Unable to uncompress value: #{$!.message}"
453
- end
454
-
455
- def data_cas_response
456
- (extras, _, status, count, _, cas) = read_header.unpack(CAS_HEADER)
457
- data = read(count) if count > 0
458
- if status == 1
459
- nil
460
- elsif status != 0
461
- raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
462
- elsif data
463
- flags = data[0...extras].unpack('N')[0]
464
- value = data[extras..-1]
465
- data = deserialize(value, flags)
466
- end
467
- [data, cas]
468
- end
469
-
470
- CAS_HEADER = '@4CCnNNQ'
471
- NORMAL_HEADER = '@4CCnN'
472
- KV_HEADER = '@2n@6nN@16Q'
473
-
474
- def guard_max_value(key, value)
475
- if value.bytesize <= @options[:value_max_bytes]
476
- yield
477
- else
478
- Dalli.logger.warn "Value for #{key} over max size: #{@options[:value_max_bytes]} <= #{value.bytesize}"
479
- false
480
- end
481
- end
482
-
483
- # https://code.google.com/p/memcached/wiki/NewCommands#Standard_Protocol
484
- # > An expiration time, in seconds. Can be up to 30 days. After 30 days, is treated as a unix timestamp of an exact date.
485
- MAX_ACCEPTABLE_EXPIRATION_INTERVAL = 30*24*60*60 # 30 days
486
- def sanitize_ttl(ttl)
487
- ttl_as_i = ttl.to_i
488
- return ttl_as_i if ttl_as_i <= MAX_ACCEPTABLE_EXPIRATION_INTERVAL
489
- Dalli.logger.debug "Expiration interval (#{ttl_as_i}) too long for Memcached, converting to an expiration timestamp"
490
- Time.now.to_i + ttl_as_i
491
- end
492
-
493
- # Implements the NullObject pattern to store an application-defined value for 'Key not found' responses.
494
- class NilObject; end
495
- NOT_FOUND = NilObject.new
496
-
497
- def generic_response(unpack=false, cache_nils=false)
498
- (extras, _, status, count) = read_header.unpack(NORMAL_HEADER)
499
- data = read(count) if count > 0
500
- if status == 1
501
- cache_nils ? NOT_FOUND : nil
502
- elsif status == 2 || status == 5
503
- false # Not stored, normal status for add operation
504
- elsif status != 0
505
- raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
506
- elsif data
507
- flags = data[0...extras].unpack('N')[0]
508
- value = data[extras..-1]
509
- unpack ? deserialize(value, flags) : value
510
- else
511
- true
512
- end
513
- end
514
-
515
- def cas_response
516
- (_, _, status, count, _, cas) = read_header.unpack(CAS_HEADER)
517
- read(count) if count > 0 # this is potential data that we don't care about
518
- if status == 1
519
- nil
520
- elsif status == 2 || status == 5
521
- false # Not stored, normal status for add operation
522
- elsif status != 0
523
- raise Dalli::DalliError, "Response error #{status}: #{RESPONSE_CODES[status]}"
524
- else
525
- cas
526
- end
527
- end
528
-
529
- def keyvalue_response
530
- hash = {}
531
- while true
532
- (key_length, _, body_length, _) = read_header.unpack(KV_HEADER)
533
- return hash if key_length == 0
534
- key = read(key_length)
535
- value = read(body_length - key_length) if body_length - key_length > 0
536
- hash[key] = value
537
- end
538
- end
539
-
540
- def multi_response
541
- hash = {}
542
- while true
543
- (key_length, _, body_length, _) = read_header.unpack(KV_HEADER)
544
- return hash if key_length == 0
545
- flags = read(4).unpack('N')[0]
546
- key = read(key_length)
547
- value = read(body_length - key_length - 4) if body_length - key_length - 4 > 0
548
- hash[key] = deserialize(value, flags)
549
- end
550
- end
551
-
552
- def write(bytes)
553
- begin
554
- @inprogress = true
555
- result = @sock.write(bytes)
556
- @inprogress = false
557
- result
558
- rescue SystemCallError, Timeout::Error => e
559
- failure!(e)
560
- end
561
- end
562
-
563
- def read(count)
564
- begin
565
- @inprogress = true
566
- data = @sock.readfull(count)
567
- @inprogress = false
568
- data
569
- rescue SystemCallError, Timeout::Error, EOFError => e
570
- failure!(e)
571
- end
572
- end
573
-
574
- def read_header
575
- read(24) || raise(Dalli::NetworkError, 'No response')
576
- end
577
-
578
- def connect
579
- Dalli.logger.debug { "Dalli::Server#connect #{name}" }
580
-
581
- begin
582
- @pid = Process.pid
583
- if socket_type == :unix
584
- @sock = KSocket::UNIX.open(hostname, self, options)
585
- else
586
- @sock = KSocket::TCP.open(hostname, port, self, options)
587
- end
588
- sasl_authentication if need_auth?
589
- @version = version # trigger actual connect
590
- up!
591
- rescue Dalli::DalliError # SASL auth failure
592
- raise
593
- rescue SystemCallError, Timeout::Error, EOFError, SocketError => e
594
- # SocketError = DNS resolution failure
595
- failure!(e)
596
- end
597
- end
598
-
599
- def split(n)
600
- [n >> 32, 0xFFFFFFFF & n]
601
- end
602
-
603
- REQUEST = 0x80
604
- RESPONSE = 0x81
605
-
606
- # Response codes taken from:
607
- # https://code.google.com/p/memcached/wiki/BinaryProtocolRevamped#Response_Status
608
- RESPONSE_CODES = {
609
- 0 => 'No error',
610
- 1 => 'Key not found',
611
- 2 => 'Key exists',
612
- 3 => 'Value too large',
613
- 4 => 'Invalid arguments',
614
- 5 => 'Item not stored',
615
- 6 => 'Incr/decr on a non-numeric value',
616
- 7 => 'The vbucket belongs to another server',
617
- 8 => 'Authentication error',
618
- 9 => 'Authentication continue',
619
- 0x20 => 'Authentication required',
620
- 0x81 => 'Unknown command',
621
- 0x82 => 'Out of memory',
622
- 0x83 => 'Not supported',
623
- 0x84 => 'Internal error',
624
- 0x85 => 'Busy',
625
- 0x86 => 'Temporary failure'
626
- }
627
-
628
- OPCODES = {
629
- :get => 0x00,
630
- :set => 0x01,
631
- :add => 0x02,
632
- :replace => 0x03,
633
- :delete => 0x04,
634
- :incr => 0x05,
635
- :decr => 0x06,
636
- :flush => 0x08,
637
- :noop => 0x0A,
638
- :version => 0x0B,
639
- :getkq => 0x0D,
640
- :append => 0x0E,
641
- :prepend => 0x0F,
642
- :stat => 0x10,
643
- :setq => 0x11,
644
- :addq => 0x12,
645
- :replaceq => 0x13,
646
- :deleteq => 0x14,
647
- :incrq => 0x15,
648
- :decrq => 0x16,
649
- :auth_negotiation => 0x20,
650
- :auth_request => 0x21,
651
- :auth_continue => 0x22,
652
- :touch => 0x1C,
653
- }
654
-
655
- HEADER = "CCnCCnNNQ"
656
- OP_FORMAT = {
657
- :get => 'a*',
658
- :set => 'NNa*a*',
659
- :add => 'NNa*a*',
660
- :replace => 'NNa*a*',
661
- :delete => 'a*',
662
- :incr => 'NNNNNa*',
663
- :decr => 'NNNNNa*',
664
- :flush => 'N',
665
- :noop => '',
666
- :getkq => 'a*',
667
- :version => '',
668
- :stat => 'a*',
669
- :append => 'a*a*',
670
- :prepend => 'a*a*',
671
- :auth_request => 'a*a*',
672
- :auth_continue => 'a*a*',
673
- :touch => 'Na*',
674
- }
675
- FORMAT = OP_FORMAT.inject({}) { |memo, (k, v)| memo[k] = HEADER + v; memo }
676
-
677
-
678
- #######
679
- # SASL authentication support for NorthScale
680
- #######
681
-
682
- def need_auth?
683
- @options[:username] || ENV['MEMCACHE_USERNAME']
684
- end
685
-
686
- def username
687
- @options[:username] || ENV['MEMCACHE_USERNAME']
688
- end
689
-
690
- def password
691
- @options[:password] || ENV['MEMCACHE_PASSWORD']
692
- end
693
-
694
- def sasl_authentication
695
- Dalli.logger.info { "Dalli/SASL authenticating as #{username}" }
696
-
697
- # negotiate
698
- req = [REQUEST, OPCODES[:auth_negotiation], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
699
- write(req)
700
-
701
- (extras, type, status, count) = read_header.unpack(NORMAL_HEADER)
702
- raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
703
- content = read(count).gsub(/\u0000/, ' ')
704
- return (Dalli.logger.debug("Authentication not required/supported by server")) if status == 0x81
705
- mechanisms = content.split(' ')
706
- raise NotImplementedError, "Dalli only supports the PLAIN authentication mechanism" if !mechanisms.include?('PLAIN')
707
-
708
- # request
709
- mechanism = 'PLAIN'
710
- msg = "\x0#{username}\x0#{password}"
711
- req = [REQUEST, OPCODES[:auth_request], mechanism.bytesize, 0, 0, 0, mechanism.bytesize + msg.bytesize, 0, 0, mechanism, msg].pack(FORMAT[:auth_request])
712
- write(req)
713
-
714
- (extras, type, status, count) = read_header.unpack(NORMAL_HEADER)
715
- raise Dalli::NetworkError, "Unexpected message format: #{extras} #{count}" unless extras == 0 && count > 0
716
- content = read(count)
717
- return Dalli.logger.info("Dalli/SASL: #{content}") if status == 0
718
-
719
- raise Dalli::DalliError, "Error authenticating: #{status}" unless status == 0x21
720
- raise NotImplementedError, "No two-step authentication mechanisms supported"
721
- # (step, msg) = sasl.receive('challenge', content)
722
- # raise Dalli::NetworkError, "Authentication failed" if sasl.failed? || step != 'response'
723
- end
724
-
725
- def parse_hostname(str)
726
- res = str.match(/\A(\[([\h:]+)\]|[^:]+)(?::(\d+))?(?::(\d+))?\z/)
727
- raise Dalli::DalliError, "Could not parse hostname #{str}" if res.nil? || res[1] == '[]'
728
- hostnam = res[2] || res[1]
729
- if hostnam =~ /\A\//
730
- socket_type = :unix
731
- # in case of unix socket, allow only setting of weight, not port
732
- raise Dalli::DalliError, "Could not parse hostname #{str}" if res[4]
733
- weigh = res[3]
734
- else
735
- socket_type = :tcp
736
- por = res[3] || DEFAULT_PORT
737
- por = Integer(por)
738
- weigh = res[4]
739
- end
740
- weigh ||= DEFAULT_WEIGHT
741
- weigh = Integer(weigh)
742
- return hostnam, por, weigh, socket_type
743
- end
744
- end
3
+ module Dalli # rubocop:disable Style/Documentation
4
+ warn 'Dalli::Server is deprecated, use Dalli::Protocol::Binary instead'
5
+ Server = Protocol::Binary
745
6
  end