dalli 2.7.2 → 3.2.4

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