dalli 2.7.2 → 3.0.0

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.

data/lib/dalli/server.rb CHANGED
@@ -1,696 +1,6 @@
1
- require 'socket'
2
- require 'timeout'
1
+ # frozen_string_literal: true
3
2
 
4
3
  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
4
+ warn "Dalli::Server is deprecated, use Dalli::Protocol::Binary instead"
5
+ Server = Protocol::Binary
696
6
  end