nsq-krakow 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +84 -0
- data/CONTRIBUTING.md +25 -0
- data/LICENSE +13 -0
- data/README.md +249 -0
- data/krakow.gemspec +22 -0
- data/lib/krakow.rb +25 -0
- data/lib/krakow/command.rb +89 -0
- data/lib/krakow/command/auth.rb +36 -0
- data/lib/krakow/command/cls.rb +24 -0
- data/lib/krakow/command/fin.rb +31 -0
- data/lib/krakow/command/identify.rb +55 -0
- data/lib/krakow/command/mpub.rb +39 -0
- data/lib/krakow/command/nop.rb +14 -0
- data/lib/krakow/command/pub.rb +37 -0
- data/lib/krakow/command/rdy.rb +31 -0
- data/lib/krakow/command/req.rb +32 -0
- data/lib/krakow/command/sub.rb +36 -0
- data/lib/krakow/command/touch.rb +31 -0
- data/lib/krakow/connection.rb +417 -0
- data/lib/krakow/connection_features.rb +10 -0
- data/lib/krakow/connection_features/deflate.rb +82 -0
- data/lib/krakow/connection_features/snappy_frames.rb +129 -0
- data/lib/krakow/connection_features/ssl.rb +75 -0
- data/lib/krakow/consumer.rb +355 -0
- data/lib/krakow/consumer/queue.rb +151 -0
- data/lib/krakow/discovery.rb +57 -0
- data/lib/krakow/distribution.rb +229 -0
- data/lib/krakow/distribution/default.rb +159 -0
- data/lib/krakow/exceptions.rb +30 -0
- data/lib/krakow/frame_type.rb +66 -0
- data/lib/krakow/frame_type/error.rb +26 -0
- data/lib/krakow/frame_type/message.rb +74 -0
- data/lib/krakow/frame_type/response.rb +26 -0
- data/lib/krakow/ksocket.rb +102 -0
- data/lib/krakow/producer.rb +162 -0
- data/lib/krakow/producer/http.rb +224 -0
- data/lib/krakow/utils.rb +9 -0
- data/lib/krakow/utils/lazy.rb +125 -0
- data/lib/krakow/utils/logging.rb +43 -0
- data/lib/krakow/version.rb +4 -0
- metadata +184 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
class Command
|
5
|
+
# Publish single message
|
6
|
+
class Auth < Command
|
7
|
+
|
8
|
+
# @!group Attributes
|
9
|
+
|
10
|
+
# @!macro [attach] attribute
|
11
|
+
# @!method $1
|
12
|
+
# @return [$2] the $1 $0
|
13
|
+
# @!method $1?
|
14
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
15
|
+
attribute :secret, String, :required => true
|
16
|
+
|
17
|
+
# @!endgroup
|
18
|
+
|
19
|
+
def to_line
|
20
|
+
scrt = secret.to_s
|
21
|
+
[name, "\n", scrt.bytesize, scrt].pack('a*a*a*a*l>a*')
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def ok
|
26
|
+
%w(OK)
|
27
|
+
end
|
28
|
+
|
29
|
+
def error
|
30
|
+
%w(E_AUTH_FAILED E_UNAUTHORIZED)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
class Command
|
5
|
+
# Close connection
|
6
|
+
class Cls < Command
|
7
|
+
|
8
|
+
def to_line
|
9
|
+
"#{name}\n"
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def ok
|
14
|
+
%w(CLOSE_WAIT)
|
15
|
+
end
|
16
|
+
|
17
|
+
def error
|
18
|
+
%w(E_INVALID)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
class Command
|
5
|
+
# Finish a message
|
6
|
+
class Fin < Command
|
7
|
+
|
8
|
+
# @!group Attributes
|
9
|
+
|
10
|
+
# @!macro [attach] attribute
|
11
|
+
# @!method $1
|
12
|
+
# @return [$2] the $1 $0
|
13
|
+
# @!method $1?
|
14
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
15
|
+
attribute :message_id, String, :required => true
|
16
|
+
|
17
|
+
# @!endgroup
|
18
|
+
|
19
|
+
def to_line
|
20
|
+
"#{name} #{message_id}\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def error
|
25
|
+
%w(E_INVALID E_FIN_FAILED)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'krakow'
|
3
|
+
|
4
|
+
module Krakow
|
5
|
+
class Command
|
6
|
+
# Update client metadata on server / negotiate features
|
7
|
+
class Identify < Command
|
8
|
+
|
9
|
+
# @!group Attributes
|
10
|
+
|
11
|
+
# @!macro [attach] attribute
|
12
|
+
# @!method $1
|
13
|
+
# @return [$2] the $1 $0
|
14
|
+
# @!method $1?
|
15
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
16
|
+
attribute :short_id, [String, Numeric], :required => true
|
17
|
+
attribute :long_id, [String, Numeric], :required => true
|
18
|
+
attribute :feature_negotiation, [TrueClass, FalseClass]
|
19
|
+
attribute :heartbeat_interval, Numeric
|
20
|
+
attribute :output_buffer_size, Integer
|
21
|
+
attribute :output_buffer_timeout, Integer
|
22
|
+
attribute :tls_v1, [TrueClass, FalseClass]
|
23
|
+
attribute :snappy, [TrueClass, FalseClass]
|
24
|
+
attribute :deflate, [TrueClass, FalseClass]
|
25
|
+
attribute :deflate_level, Integer
|
26
|
+
attribute :sample_rate, Integer
|
27
|
+
|
28
|
+
# @!endgroup
|
29
|
+
|
30
|
+
def to_line
|
31
|
+
filtered = Hash[*
|
32
|
+
arguments.map do |key, value|
|
33
|
+
unless(value.nil?)
|
34
|
+
[key, value]
|
35
|
+
end
|
36
|
+
end.compact.flatten
|
37
|
+
]
|
38
|
+
payload = MultiJson.dump(filtered)
|
39
|
+
[name, "\n", payload.bytesize, payload].pack('a*a*l>a*')
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
def ok
|
44
|
+
%w(OK)
|
45
|
+
end
|
46
|
+
|
47
|
+
def error
|
48
|
+
%w(E_INVALID E_BAD_BODY)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
class Command
|
5
|
+
# Publish multiple messages
|
6
|
+
class Mpub < Command
|
7
|
+
|
8
|
+
# @!group Attributes
|
9
|
+
|
10
|
+
# @!macro [attach] attribute
|
11
|
+
# @!method $1
|
12
|
+
# @return [$2] the $1 $0
|
13
|
+
# @!method $1?
|
14
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
15
|
+
attribute :topic_name, String, :required => true
|
16
|
+
attribute :messages, Array, :required => true
|
17
|
+
|
18
|
+
# @!endgroup
|
19
|
+
def to_line
|
20
|
+
formatted_messages = messages.map do |message|
|
21
|
+
message = message.to_s
|
22
|
+
[message.bytesize, message].pack('l>a*')
|
23
|
+
end.join
|
24
|
+
[name, ' ', topic_name, "\n", formatted_messages.bytesize, messages.size, formatted_messages].pack('a*a*a*a*l>l>a*')
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def ok
|
29
|
+
%w(OK)
|
30
|
+
end
|
31
|
+
|
32
|
+
def error
|
33
|
+
%w(E_INVALID E_BAD_TOPIC E_BAD_BODY E_BAD_MESSAGE E_MPUB_FAILED)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
class Command
|
5
|
+
# Publish single message
|
6
|
+
class Pub < Command
|
7
|
+
|
8
|
+
# @!group Attributes
|
9
|
+
|
10
|
+
# @!macro [attach] attribute
|
11
|
+
# @!method $1
|
12
|
+
# @return [$2] the $1 $0
|
13
|
+
# @!method $1?
|
14
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
15
|
+
attribute :topic_name, String, :required => true
|
16
|
+
attribute :message, Object, :required => true
|
17
|
+
|
18
|
+
# @!endgroup
|
19
|
+
|
20
|
+
def to_line
|
21
|
+
msg = message.to_s
|
22
|
+
[name, ' ', topic_name, "\n", msg.bytesize, msg].pack('a*a*a*a*l>a*')
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def ok
|
27
|
+
%w(OK)
|
28
|
+
end
|
29
|
+
|
30
|
+
def error
|
31
|
+
%w(E_INVALID E_BAD_TOPIC E_BAD_MESSAGE E_PUB_FAILED)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
class Command
|
5
|
+
# Update RDY state
|
6
|
+
class Rdy < Command
|
7
|
+
|
8
|
+
# @!group Attributes
|
9
|
+
|
10
|
+
# @!macro [attach] attribute
|
11
|
+
# @!method $1
|
12
|
+
# @return [$2] the $1 $0
|
13
|
+
# @!method $1?
|
14
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
15
|
+
attribute :count, Integer, :required => true
|
16
|
+
|
17
|
+
# @!endgroup
|
18
|
+
|
19
|
+
def to_line
|
20
|
+
"#{name} #{count}\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def error
|
25
|
+
%w(E_INVALID)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
class Command
|
5
|
+
# Re-queue a message
|
6
|
+
class Req < Command
|
7
|
+
|
8
|
+
# @!group Attributes
|
9
|
+
|
10
|
+
# @!macro [attach] attribute
|
11
|
+
# @!method $1
|
12
|
+
# @return [$2] the $1 $0
|
13
|
+
# @!method $1?
|
14
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
15
|
+
attribute :message_id, String, :required => true
|
16
|
+
attribute :timeout, Integer, :required => true
|
17
|
+
|
18
|
+
# @!endgroup
|
19
|
+
|
20
|
+
def to_line
|
21
|
+
"#{name} #{message_id} #{self.timeout}\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def error
|
26
|
+
%w(E_INVALID E_REQ_FAILED)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
class Command
|
5
|
+
# Subscribe to topic/channel
|
6
|
+
class Sub < Command
|
7
|
+
|
8
|
+
# @!group Attributes
|
9
|
+
|
10
|
+
# @!macro [attach] attribute
|
11
|
+
# @!method $1
|
12
|
+
# @return [$2] the $1 $0
|
13
|
+
# @!method $1?
|
14
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
15
|
+
attribute :topic_name, String, :required => true
|
16
|
+
attribute :channel_name, String, :required => true
|
17
|
+
|
18
|
+
# @!endgroup
|
19
|
+
|
20
|
+
def to_line
|
21
|
+
"#{name} #{topic_name} #{channel_name}\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def ok
|
26
|
+
%w(OK)
|
27
|
+
end
|
28
|
+
|
29
|
+
def error
|
30
|
+
%w(E_INVALID E_BAD_TOPIC E_BAD_CHANNEL)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
class Command
|
5
|
+
# Reset timeout for in-flight message
|
6
|
+
class Touch < Command
|
7
|
+
|
8
|
+
# @!group Attributes
|
9
|
+
|
10
|
+
# @!macro [attach] attribute
|
11
|
+
# @!method $1
|
12
|
+
# @return [$2] the $1 $0
|
13
|
+
# @!method $1?
|
14
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
15
|
+
attribute :message_id, String, :required => true
|
16
|
+
|
17
|
+
# @!endgroup
|
18
|
+
|
19
|
+
def to_line
|
20
|
+
"#{name} #{message_id}\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def error
|
25
|
+
%w(E_INVALID E_TOUCH_FAILED)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,417 @@
|
|
1
|
+
require 'krakow'
|
2
|
+
|
3
|
+
module Krakow
|
4
|
+
|
5
|
+
# Provides TCP connection to NSQD
|
6
|
+
class Connection
|
7
|
+
|
8
|
+
# Generate identifier for connection
|
9
|
+
#
|
10
|
+
# @param host [String]
|
11
|
+
# @param port [String, Integer]
|
12
|
+
# @param topic [String]
|
13
|
+
# @param channel [String]
|
14
|
+
# @return [String]
|
15
|
+
def self.identifier(host, port, topic, channel)
|
16
|
+
[host, port, topic, channel].compact.join('__')
|
17
|
+
end
|
18
|
+
|
19
|
+
include Utils::Lazy
|
20
|
+
# @!parse include Krakow::Utils::Lazy::InstanceMethods
|
21
|
+
# @!parse extend Krakow::Utils::Lazy::ClassMethods
|
22
|
+
|
23
|
+
include Celluloid
|
24
|
+
|
25
|
+
# Available connection features
|
26
|
+
FEATURES = [
|
27
|
+
:max_rdy_count,
|
28
|
+
:max_msg_timeout,
|
29
|
+
:msg_timeout,
|
30
|
+
:tls_v1,
|
31
|
+
:deflate,
|
32
|
+
:deflate_level,
|
33
|
+
:max_deflate_level,
|
34
|
+
:snappy,
|
35
|
+
:sample_rate,
|
36
|
+
:auth_required
|
37
|
+
]
|
38
|
+
|
39
|
+
# List of features that may not be enabled together
|
40
|
+
EXCLUSIVE_FEATURES = [[:snappy, :deflate]]
|
41
|
+
|
42
|
+
# List of features that may be enabled by the client
|
43
|
+
ENABLEABLE_FEATURES = [:tls_v1, :snappy, :deflate, :auth_required]
|
44
|
+
|
45
|
+
finalizer :connection_cleanup
|
46
|
+
|
47
|
+
# @return [Hash] current configuration for endpoint
|
48
|
+
attr_reader :endpoint_settings
|
49
|
+
# @return [Ksocket] underlying socket like instance
|
50
|
+
attr_reader :socket
|
51
|
+
# @return [TrueClass, FalseClass]
|
52
|
+
attr_reader :running
|
53
|
+
|
54
|
+
# @!group Attributes
|
55
|
+
|
56
|
+
# @!macro [attach] attribute
|
57
|
+
# @!method $1
|
58
|
+
# @return [$2] the $1 $0
|
59
|
+
# @!method $1?
|
60
|
+
# @return [TrueClass, FalseClass] truthiness of the $1 $0
|
61
|
+
attribute :host, String, :required => true
|
62
|
+
attribute :port, [String,Integer], :required => true
|
63
|
+
attribute :topic, String
|
64
|
+
attribute :channel, String
|
65
|
+
attribute :version, String, :default => 'v2'
|
66
|
+
attribute :queue, [Queue, Consumer::Queue], :default => ->{ Queue.new }
|
67
|
+
attribute :callbacks, Hash, :default => ->{ Hash.new }
|
68
|
+
attribute :responses, Queue, :default => ->{ Queue.new }
|
69
|
+
attribute :notifier, [Celluloid::Signals, Celluloid::Condition, Celluloid::Actor]
|
70
|
+
attribute :features, Hash, :default => ->{ Hash.new }
|
71
|
+
attribute :response_wait, Numeric, :default => 1.0
|
72
|
+
attribute :response_interval, Numeric, :default => 0.03
|
73
|
+
attribute :error_wait, Numeric, :default => 0
|
74
|
+
attribute :enforce_features, [TrueClass,FalseClass], :default => true
|
75
|
+
attribute :features_args, Hash, :default => ->{ Hash.new }
|
76
|
+
|
77
|
+
# @!endgroup
|
78
|
+
|
79
|
+
# Create new instance
|
80
|
+
#
|
81
|
+
# @param args [Hash]
|
82
|
+
# @option args [String] :host (required) server host
|
83
|
+
# @option args [String, Numeric] :port (required) server port
|
84
|
+
# @option args [String] :version
|
85
|
+
# @option args [Queue] :queue received message queue
|
86
|
+
# @option args [Hash] :callbacks
|
87
|
+
# @option args [Queue] :responses received responses queue
|
88
|
+
# @option args [Celluloid::Actor] :notifier actor to notify on new message
|
89
|
+
# @option args [Hash] :features features to enable
|
90
|
+
# @option args [Numeric] :response_wait time to wait for response
|
91
|
+
# @option args [Numeric] :response_interval sleep interval for wait loop
|
92
|
+
# @option args [Numeric] :error_wait time to wait for error response
|
93
|
+
# @option args [TrueClass, FalseClass] :enforce_features fail if features are unavailable
|
94
|
+
# @option args [Hash] :feature_args options for connection features
|
95
|
+
def initialize(args={})
|
96
|
+
super
|
97
|
+
@endpoint_settings = {}
|
98
|
+
@running = false
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [String] identifier for this connection
|
102
|
+
def identifier
|
103
|
+
self.class.identifier(host, port, topic, channel)
|
104
|
+
end
|
105
|
+
|
106
|
+
# @return [String] stringify object
|
107
|
+
def to_s
|
108
|
+
"<#{self.class.name}:#{object_id} {#{host}:#{port}}>"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Initialize the connection
|
112
|
+
#
|
113
|
+
# @return [nil]
|
114
|
+
def init!
|
115
|
+
connect!
|
116
|
+
async.process_to_queue!
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
|
120
|
+
# Send message to remote server
|
121
|
+
#
|
122
|
+
# @param message [Krakow::Message] message to send
|
123
|
+
# @return [TrueClass, Krakow::FrameType] response if expected or true
|
124
|
+
def transmit(message)
|
125
|
+
unless(message.respond_to?(:to_line))
|
126
|
+
abort TypeError.new("Expecting type `Krakow::FrameType` but received `#{message.class}`")
|
127
|
+
end
|
128
|
+
output = message.to_line
|
129
|
+
response_wait = wait_time_for(message)
|
130
|
+
if(response_wait > 0)
|
131
|
+
transmit_with_response(message, response_wait)
|
132
|
+
else
|
133
|
+
debug ">>> #{output}"
|
134
|
+
socket.put(output)
|
135
|
+
true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Sends message and waits for response
|
140
|
+
#
|
141
|
+
# @param message [Krakow::Message] message to send
|
142
|
+
# @return [Krakow::FrameType] response
|
143
|
+
def transmit_with_response(message, wait_time)
|
144
|
+
responses.clear
|
145
|
+
socket.put(message.to_line)
|
146
|
+
response = nil
|
147
|
+
(wait_time / response_interval).to_i.times do |i|
|
148
|
+
response = responses.pop unless responses.empty?
|
149
|
+
break if response
|
150
|
+
sleep(response_interval)
|
151
|
+
end
|
152
|
+
if(response)
|
153
|
+
message.response = response
|
154
|
+
if(message.error?(response))
|
155
|
+
res = Error::BadResponse.new "Message transmission failed #{message}"
|
156
|
+
res.result = response
|
157
|
+
abort res
|
158
|
+
end
|
159
|
+
response
|
160
|
+
else
|
161
|
+
unless(Command.response_for(message) == :error_only)
|
162
|
+
abort Error::BadResponse::NoResponse.new "No response provided for message #{message}"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Destructor method for cleanup
|
168
|
+
#
|
169
|
+
# @return [nil]
|
170
|
+
def connection_cleanup
|
171
|
+
debug 'Tearing down connection'
|
172
|
+
@running = false
|
173
|
+
if(connected?)
|
174
|
+
socket.terminate
|
175
|
+
end
|
176
|
+
@socket = nil
|
177
|
+
info 'Connection torn down'
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
|
181
|
+
# Receive from server
|
182
|
+
#
|
183
|
+
# @return [Krakow::FrameType, nil] message or nothing if read was empty
|
184
|
+
# @raise [Error::ConnectionUnavailable] socket is closed
|
185
|
+
def receive
|
186
|
+
debug 'Read wait for frame start'
|
187
|
+
buf = socket.get(8)
|
188
|
+
if(buf)
|
189
|
+
@receiving = true
|
190
|
+
debug "<<< #{buf.inspect}"
|
191
|
+
struct = FrameType.decode(buf)
|
192
|
+
debug "Decoded structure: #{struct.inspect}"
|
193
|
+
struct[:data] = socket.get(struct[:size])
|
194
|
+
debug "<<< #{struct[:data].inspect}"
|
195
|
+
@receiving = false
|
196
|
+
frame = FrameType.build(struct)
|
197
|
+
debug "Struct: #{struct.inspect} Frame: #{frame.inspect}"
|
198
|
+
frame
|
199
|
+
else
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# @return [TrueClass, FalseClass] is connection currently receiving a message
|
205
|
+
def receiving?
|
206
|
+
!!@receiving
|
207
|
+
end
|
208
|
+
|
209
|
+
# Receive messages and place into queue
|
210
|
+
#
|
211
|
+
# @return [nil]
|
212
|
+
def process_to_queue!
|
213
|
+
unless(@running)
|
214
|
+
@running = true
|
215
|
+
while(@running)
|
216
|
+
message = handle(receive)
|
217
|
+
if(message)
|
218
|
+
debug "Adding message to queue #{message}"
|
219
|
+
queue << message
|
220
|
+
if(notifier)
|
221
|
+
warn "Sending new message notification: #{notifier} - #{message}"
|
222
|
+
notifier.broadcast(message)
|
223
|
+
end
|
224
|
+
else
|
225
|
+
debug 'Received `nil` message. Ignoring.'
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
nil
|
230
|
+
end
|
231
|
+
|
232
|
+
# Handle non-message type Krakow::FrameType
|
233
|
+
#
|
234
|
+
# @param message [Krakow::FrameType] received message
|
235
|
+
# @return [Krakow::FrameType, nil]
|
236
|
+
def handle(message)
|
237
|
+
# Grab heartbeats upfront
|
238
|
+
if(message.is_a?(FrameType::Response) && message.response == '_heartbeat_')
|
239
|
+
debug 'Responding to heartbeat'
|
240
|
+
transmit Command::Nop.new
|
241
|
+
nil
|
242
|
+
else
|
243
|
+
message = callback_for(:handle, message)
|
244
|
+
if(!message.is_a?(FrameType::Message))
|
245
|
+
debug "Captured non-message type response: #{message}"
|
246
|
+
responses << message
|
247
|
+
nil
|
248
|
+
else
|
249
|
+
message
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Execute callback for given type
|
255
|
+
#
|
256
|
+
# @overload callback_for(type, arg, connection)
|
257
|
+
# @param type [Symbol] type of callback
|
258
|
+
# @param arg [Object] argument for callback (can be multiple)
|
259
|
+
# @param connection [Krakow::Connection] current connection
|
260
|
+
# @return [Object] result of callback
|
261
|
+
def callback_for(type, *args)
|
262
|
+
callback = callbacks[type]
|
263
|
+
if(callback)
|
264
|
+
debug "Processing connection callback for #{type.inspect} (#{callback.inspect})"
|
265
|
+
if(callback[:actor].alive?)
|
266
|
+
callback[:actor].send(callback[:method], *(args + [current_actor]))
|
267
|
+
else
|
268
|
+
error "Expected actor for callback processing is not alive! (type: `#{type.inspect}`)"
|
269
|
+
end
|
270
|
+
else
|
271
|
+
debug "No connection callback defined for #{type.inspect}"
|
272
|
+
args.size == 1 ? args.first : args
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Returns configured wait time for given message type
|
277
|
+
#
|
278
|
+
# @param message [Krakow::Command]
|
279
|
+
# @return [Numeric] seconds to wait
|
280
|
+
def wait_time_for(message)
|
281
|
+
case Command.response_for(message)
|
282
|
+
when :required
|
283
|
+
response_wait
|
284
|
+
when :error_only
|
285
|
+
error_wait
|
286
|
+
else
|
287
|
+
0
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# @return [Hash] default settings for IDENTIFY
|
292
|
+
def identify_defaults
|
293
|
+
unless(@identify_defaults)
|
294
|
+
@identify_defaults = {
|
295
|
+
:short_id => Socket.gethostname,
|
296
|
+
:long_id => Socket.gethostbyname(Socket.gethostname).flatten.compact.first,
|
297
|
+
:user_agent => "krakow/#{Krakow::VERSION}",
|
298
|
+
:feature_negotiation => true
|
299
|
+
}
|
300
|
+
end
|
301
|
+
@identify_defaults
|
302
|
+
end
|
303
|
+
|
304
|
+
# IDENTIFY with server and negotiate features
|
305
|
+
#
|
306
|
+
# @return [TrueClass]
|
307
|
+
def identify_and_negotiate
|
308
|
+
expected_features = identify_defaults.merge(features)
|
309
|
+
ident = Command::Identify.new(
|
310
|
+
expected_features
|
311
|
+
)
|
312
|
+
socket.put(ident.to_line)
|
313
|
+
response = receive
|
314
|
+
if(expected_features[:feature_negotiation])
|
315
|
+
begin
|
316
|
+
@endpoint_settings = MultiJson.load(response.content, :symbolize_keys => true)
|
317
|
+
info "Connection settings: #{endpoint_settings.inspect}"
|
318
|
+
# Enable things we need to enable
|
319
|
+
ENABLEABLE_FEATURES.each do |key|
|
320
|
+
if(endpoint_settings[key])
|
321
|
+
send(key)
|
322
|
+
elsif(enforce_features && expected_features[key])
|
323
|
+
abort Error::ConnectionFeatureFailure.new("Failed to enable #{key} feature on connection!")
|
324
|
+
end
|
325
|
+
end
|
326
|
+
rescue MultiJson::LoadError => e
|
327
|
+
error "Failed to parse response from Identify request: #{e} - #{response}"
|
328
|
+
abort e
|
329
|
+
end
|
330
|
+
else
|
331
|
+
@endpoint_settings = {}
|
332
|
+
end
|
333
|
+
true
|
334
|
+
end
|
335
|
+
|
336
|
+
# Send authentication request for connection
|
337
|
+
#
|
338
|
+
# @return [TrueClass]
|
339
|
+
def auth_required
|
340
|
+
info 'Authentication required for this connection'
|
341
|
+
if(feature_args[:auth])
|
342
|
+
transmit(Command::Auth.new(:secret => feature_args[:auth]))
|
343
|
+
response = receive
|
344
|
+
true
|
345
|
+
else
|
346
|
+
error 'No authentication information provided for connection!'
|
347
|
+
abort 'Authentication failure. No authentication secret provided'
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# Enable snappy feature on underlying socket
|
352
|
+
#
|
353
|
+
# @return [TrueClass]
|
354
|
+
def snappy
|
355
|
+
info 'Loading support for snappy compression and converting connection'
|
356
|
+
@socket = ConnectionFeatures::SnappyFrames::Io.new(socket, features_args)
|
357
|
+
response = receive
|
358
|
+
info "Snappy connection conversion complete. Response: #{response.inspect}"
|
359
|
+
true
|
360
|
+
end
|
361
|
+
|
362
|
+
# Enable deflate feature on underlying socket
|
363
|
+
#
|
364
|
+
# @return [TrueClass]
|
365
|
+
def deflate
|
366
|
+
debug 'Loading support for deflate compression and converting connection'
|
367
|
+
@socket = ConnectionFeatures::Deflate::Io.new(socket, features_args)
|
368
|
+
response = receive
|
369
|
+
info "Deflate connection conversion complete. Response: #{response.inspect}"
|
370
|
+
true
|
371
|
+
end
|
372
|
+
|
373
|
+
# Enable TLS feature on underlying socket
|
374
|
+
#
|
375
|
+
# @return [TrueClass]
|
376
|
+
def tls_v1
|
377
|
+
info 'Enabling TLS for connection'
|
378
|
+
@socket = ConnectionFeatures::Ssl::Io.new(socket, features_args)
|
379
|
+
response = receive
|
380
|
+
info "TLS enable complete. Response: #{response.inspect}"
|
381
|
+
true
|
382
|
+
end
|
383
|
+
|
384
|
+
# @return [TrueClass, FalseClass] underlying socket is connected
|
385
|
+
def connected?
|
386
|
+
begin
|
387
|
+
!!(socket && socket.alive?)
|
388
|
+
rescue Celluloid::DeadActorError
|
389
|
+
false
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
protected
|
394
|
+
|
395
|
+
# Connect the underlying socket
|
396
|
+
#
|
397
|
+
# @return [nil]
|
398
|
+
def connect!
|
399
|
+
debug 'Initializing connection'
|
400
|
+
unless(@connecting)
|
401
|
+
@connecting = true
|
402
|
+
if(socket && socket.alive?)
|
403
|
+
socket.terminate
|
404
|
+
@socket = nil
|
405
|
+
end
|
406
|
+
@socket = Ksocket.new(:host => host, :port => port)
|
407
|
+
self.link socket
|
408
|
+
socket.put version.rjust(4).upcase
|
409
|
+
identify_and_negotiate
|
410
|
+
info 'Connection initialized'
|
411
|
+
@connecting = false
|
412
|
+
end
|
413
|
+
nil
|
414
|
+
end
|
415
|
+
|
416
|
+
end
|
417
|
+
end
|