dalli 2.7.3 → 3.2.3

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