amq-client 0.5.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.
Files changed (108) hide show
  1. data/.gitignore +8 -0
  2. data/.gitmodules +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +7 -0
  5. data/.yardopts +1 -0
  6. data/CONTRIBUTORS +3 -0
  7. data/Gemfile +27 -0
  8. data/LICENSE +20 -0
  9. data/README.textile +61 -0
  10. data/amq-client.gemspec +34 -0
  11. data/bin/jenkins.sh +23 -0
  12. data/bin/set_test_suite_realms_up.sh +24 -0
  13. data/examples/coolio_adapter/basic_consume.rb +49 -0
  14. data/examples/coolio_adapter/basic_consume_with_acknowledgements.rb +43 -0
  15. data/examples/coolio_adapter/basic_consume_with_rejections.rb +43 -0
  16. data/examples/coolio_adapter/basic_publish.rb +35 -0
  17. data/examples/coolio_adapter/channel_close.rb +24 -0
  18. data/examples/coolio_adapter/example_helper.rb +39 -0
  19. data/examples/coolio_adapter/exchange_declare.rb +28 -0
  20. data/examples/coolio_adapter/kitchen_sink1.rb +48 -0
  21. data/examples/coolio_adapter/queue_bind.rb +32 -0
  22. data/examples/coolio_adapter/queue_purge.rb +32 -0
  23. data/examples/coolio_adapter/queue_unbind.rb +37 -0
  24. data/examples/eventmachine_adapter/authentication/plain_password_with_custom_role_credentials.rb +36 -0
  25. data/examples/eventmachine_adapter/authentication/plain_password_with_default_role_credentials.rb +27 -0
  26. data/examples/eventmachine_adapter/authentication/plain_password_with_incorrect_credentials.rb +18 -0
  27. data/examples/eventmachine_adapter/basic_cancel.rb +49 -0
  28. data/examples/eventmachine_adapter/basic_consume.rb +51 -0
  29. data/examples/eventmachine_adapter/basic_consume_with_acknowledgements.rb +45 -0
  30. data/examples/eventmachine_adapter/basic_consume_with_rejections.rb +45 -0
  31. data/examples/eventmachine_adapter/basic_get.rb +57 -0
  32. data/examples/eventmachine_adapter/basic_get_with_empty_queue.rb +53 -0
  33. data/examples/eventmachine_adapter/basic_publish.rb +38 -0
  34. data/examples/eventmachine_adapter/basic_qos.rb +29 -0
  35. data/examples/eventmachine_adapter/basic_recover.rb +29 -0
  36. data/examples/eventmachine_adapter/basic_return.rb +34 -0
  37. data/examples/eventmachine_adapter/channel_close.rb +24 -0
  38. data/examples/eventmachine_adapter/channel_flow.rb +36 -0
  39. data/examples/eventmachine_adapter/channel_level_exception_handling.rb +44 -0
  40. data/examples/eventmachine_adapter/example_helper.rb +39 -0
  41. data/examples/eventmachine_adapter/exchange_declare.rb +54 -0
  42. data/examples/eventmachine_adapter/extensions/rabbitmq/handling_confirm_select_ok.rb +31 -0
  43. data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +56 -0
  44. data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_unroutable_message.rb +46 -0
  45. data/examples/eventmachine_adapter/kitchen_sink1.rb +50 -0
  46. data/examples/eventmachine_adapter/queue_bind.rb +32 -0
  47. data/examples/eventmachine_adapter/queue_declare.rb +34 -0
  48. data/examples/eventmachine_adapter/queue_purge.rb +32 -0
  49. data/examples/eventmachine_adapter/queue_unbind.rb +37 -0
  50. data/examples/eventmachine_adapter/tx_commit.rb +29 -0
  51. data/examples/eventmachine_adapter/tx_rollback.rb +29 -0
  52. data/examples/eventmachine_adapter/tx_select.rb +27 -0
  53. data/examples/socket_adapter/basics.rb +19 -0
  54. data/examples/socket_adapter/connection.rb +53 -0
  55. data/examples/socket_adapter/multiple_connections.rb +17 -0
  56. data/irb.rb +66 -0
  57. data/lib/amq/client.rb +15 -0
  58. data/lib/amq/client/adapter.rb +356 -0
  59. data/lib/amq/client/adapters/coolio.rb +221 -0
  60. data/lib/amq/client/adapters/event_machine.rb +228 -0
  61. data/lib/amq/client/adapters/socket.rb +89 -0
  62. data/lib/amq/client/channel.rb +338 -0
  63. data/lib/amq/client/connection.rb +246 -0
  64. data/lib/amq/client/entity.rb +117 -0
  65. data/lib/amq/client/exceptions.rb +86 -0
  66. data/lib/amq/client/exchange.rb +163 -0
  67. data/lib/amq/client/extensions/rabbitmq.rb +5 -0
  68. data/lib/amq/client/extensions/rabbitmq/basic.rb +36 -0
  69. data/lib/amq/client/extensions/rabbitmq/confirm.rb +254 -0
  70. data/lib/amq/client/framing/io/frame.rb +32 -0
  71. data/lib/amq/client/framing/string/frame.rb +62 -0
  72. data/lib/amq/client/logging.rb +56 -0
  73. data/lib/amq/client/mixins/anonymous_entity.rb +21 -0
  74. data/lib/amq/client/mixins/status.rb +62 -0
  75. data/lib/amq/client/protocol/get_response.rb +55 -0
  76. data/lib/amq/client/queue.rb +450 -0
  77. data/lib/amq/client/settings.rb +83 -0
  78. data/lib/amq/client/version.rb +5 -0
  79. data/spec/benchmarks/adapters.rb +77 -0
  80. data/spec/client/framing/io_frame_spec.rb +57 -0
  81. data/spec/client/framing/string_frame_spec.rb +57 -0
  82. data/spec/client/protocol/get_response_spec.rb +79 -0
  83. data/spec/integration/coolio/basic_ack_spec.rb +41 -0
  84. data/spec/integration/coolio/basic_get_spec.rb +73 -0
  85. data/spec/integration/coolio/basic_return_spec.rb +33 -0
  86. data/spec/integration/coolio/channel_close_spec.rb +26 -0
  87. data/spec/integration/coolio/channel_flow_spec.rb +46 -0
  88. data/spec/integration/coolio/spec_helper.rb +31 -0
  89. data/spec/integration/coolio/tx_commit_spec.rb +40 -0
  90. data/spec/integration/coolio/tx_rollback_spec.rb +44 -0
  91. data/spec/integration/eventmachine/basic_ack_spec.rb +40 -0
  92. data/spec/integration/eventmachine/basic_get_spec.rb +73 -0
  93. data/spec/integration/eventmachine/basic_return_spec.rb +35 -0
  94. data/spec/integration/eventmachine/channel_close_spec.rb +26 -0
  95. data/spec/integration/eventmachine/channel_flow_spec.rb +32 -0
  96. data/spec/integration/eventmachine/spec_helper.rb +22 -0
  97. data/spec/integration/eventmachine/tx_commit_spec.rb +47 -0
  98. data/spec/integration/eventmachine/tx_rollback_spec.rb +35 -0
  99. data/spec/regression/bad_frame_slicing_in_adapters_spec.rb +59 -0
  100. data/spec/spec_helper.rb +24 -0
  101. data/spec/unit/client/adapter_spec.rb +49 -0
  102. data/spec/unit/client/entity_spec.rb +49 -0
  103. data/spec/unit/client/logging_spec.rb +60 -0
  104. data/spec/unit/client/mixins/status_spec.rb +72 -0
  105. data/spec/unit/client/settings_spec.rb +27 -0
  106. data/spec/unit/client_spec.rb +11 -0
  107. data/tasks.rb +11 -0
  108. metadata +202 -0
@@ -0,0 +1,221 @@
1
+ # encoding: utf-8
2
+
3
+ # http://coolio.github.com
4
+
5
+ require "cool.io"
6
+ require "amq/client"
7
+
8
+ require "amq/client/framing/string/frame"
9
+
10
+ module AMQ
11
+ module Client
12
+ class Coolio
13
+ class Socket < ::Coolio::TCPSocket
14
+ attr_accessor :adapter
15
+
16
+ def self.connect(adapter, host, port)
17
+ socket = super(host, port)
18
+ socket.adapter = adapter
19
+ socket
20
+ end
21
+
22
+ def on_connect
23
+ #puts "On connect"
24
+ adapter.on_socket_connect
25
+ end
26
+
27
+ def on_read(data)
28
+ # puts "Received data"
29
+ # puts_data(data)
30
+ adapter.on_read(data)
31
+ end
32
+
33
+ # This handler should never trigger in normal circumstances
34
+ def on_close
35
+ adapter.on_socket_disconnect
36
+ end
37
+
38
+ def send_raw(data)
39
+ # puts "Sending data"
40
+ # puts_data(data)
41
+ write(data)
42
+ end
43
+
44
+ protected
45
+ def puts_data(data)
46
+ puts " As string: #{data.inspect}"
47
+ puts " As byte array: #{data.bytes.to_a.inspect}"
48
+ end
49
+ end
50
+
51
+ # Behaviors
52
+ include AMQ::Client::Adapter
53
+
54
+ self.sync = false
55
+
56
+ # API
57
+ attr_accessor :socket
58
+ attr_accessor :callbacks
59
+ attr_accessor :connections
60
+
61
+ class << self
62
+ def connect(settings, &block)
63
+ settings = self.settings.merge(settings)
64
+ host, port = settings[:host], settings[:port]
65
+ instance = new
66
+ socket = Socket.connect(instance, settings[:host], settings[:port])
67
+ socket.attach Cool.io::Loop.default
68
+ instance.socket = socket
69
+ instance.on_connection(&block)
70
+ instance
71
+ end
72
+ end
73
+
74
+ def initialize
75
+ # Be careful with default values for #ruby hashes: h = Hash.new(Array.new); h[:key] ||= 1
76
+ # won't assign anything to :key. MK.
77
+ @callbacks = {}
78
+ @connections = []
79
+ super
80
+ end
81
+
82
+ # sets a callback for connection
83
+ def on_connection(&block)
84
+ define_callback :connect, &block
85
+ end
86
+
87
+ # sets a callback for disconnection
88
+ def on_disconnection(&block)
89
+ define_callback :disconnect, &block
90
+ end
91
+
92
+ def on_open(&block)
93
+ define_callback :open, &block
94
+ end # on_open(&block)
95
+
96
+
97
+ # called by AMQ::Client::Connection after we receive connection.open-ok.
98
+ def connection_successful
99
+ exec_callback_yielding_self(:connect)
100
+ end
101
+
102
+ # called by AMQ::Client::Connection after we receive connection.close-ok.
103
+ def disconnection_successful
104
+ exec_callback_yielding_self(:disconnect)
105
+ close_connection
106
+ end
107
+
108
+ def open_successful
109
+ @authenticating = false
110
+ exec_callback_yielding_self(:open)
111
+ end # open_successful
112
+
113
+
114
+ # triggered when socket is connected but before handshake is done
115
+ def on_socket_connect
116
+ post_init
117
+ end
118
+
119
+ # triggered after socket is closed
120
+ def on_socket_disconnect
121
+ end
122
+
123
+ def send_raw(data)
124
+ socket.send_raw data
125
+ end
126
+
127
+ # The story about the buffering is kinda similar to EventMachine,
128
+ # you keep receiving more than one frame in a single packet.
129
+ def on_read(chunk)
130
+ @chunk_buffer << chunk
131
+ while frame = get_next_frame
132
+ receive_frame(AMQ::Client::Framing::String::Frame.decode(frame))
133
+ end
134
+ end
135
+
136
+ def close_connection
137
+ @socket.close
138
+ end
139
+
140
+
141
+
142
+ #
143
+ # Callbacks
144
+ #
145
+
146
+ def redefine_callback(event, callable = nil, &block)
147
+ f = (callable || block)
148
+ # yes, re-assign!
149
+ @callbacks[event] = [f]
150
+
151
+ self
152
+ end
153
+
154
+ def define_callback(event, callable = nil, &block)
155
+ f = (callable || block)
156
+ @callbacks[event] ||= []
157
+
158
+ @callbacks[event] << f if f
159
+
160
+ self
161
+ end # define_callback(event, &block)
162
+ alias append_callback define_callback
163
+
164
+ def prepend_callback(event, &block)
165
+ @callbacks[event] ||= []
166
+ @callbacks[event].unshift(block)
167
+
168
+ self
169
+ end # prepend_callback(event, &block)
170
+
171
+
172
+ def exec_callback(name, *args, &block)
173
+ callbacks = Array(self.callbacks[name])
174
+ callbacks.map { |c| c.call(*args, &block) } if callbacks.any?
175
+ end
176
+
177
+ def exec_callback_once(name, *args, &block)
178
+ callbacks = Array(self.callbacks.delete(name))
179
+ callbacks.map { |c| c.call(*args, &block) } if callbacks.any?
180
+ end
181
+
182
+ def exec_callback_yielding_self(name, *args, &block)
183
+ callbacks = Array(self.callbacks[name])
184
+ callbacks.map { |c| c.call(self, *args, &block) } if callbacks.any?
185
+ end
186
+
187
+ def exec_callback_once_yielding_self(name, *args, &block)
188
+ callbacks = Array(self.callbacks.delete(name))
189
+ callbacks.map { |c| c.call(self, *args, &block) } if callbacks.any?
190
+ end
191
+
192
+
193
+
194
+ protected
195
+
196
+ def post_init
197
+ reset
198
+ handshake
199
+ end
200
+
201
+ def reset
202
+ @chunk_buffer = ""
203
+ end
204
+
205
+ def get_next_frame
206
+ return nil unless @chunk_buffer.size > 7 # otherwise, cannot read the length
207
+ # octet + short
208
+ offset = 1 + 2
209
+ # length
210
+ payload_length = @chunk_buffer[offset, 4].unpack('N')[0]
211
+ # 4 bytes for long payload length, 1 byte final octet
212
+ frame_length = offset + 4 + payload_length + 1
213
+ if frame_length <= @chunk_buffer.size
214
+ @chunk_buffer.slice!(0, frame_length)
215
+ else
216
+ nil
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,228 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client"
4
+ require "amq/client/channel"
5
+ require "amq/client/exchange"
6
+ require "amq/client/framing/string/frame"
7
+
8
+ require "eventmachine"
9
+
10
+ module AMQ
11
+ module Client
12
+ class EventMachineClient < EM::Connection
13
+
14
+ class Deferrable
15
+ include EventMachine::Deferrable
16
+ end
17
+
18
+ #
19
+ # Behaviors
20
+ #
21
+
22
+ include AMQ::Client::Adapter
23
+
24
+ self.sync = false
25
+
26
+ register_entity :channel, AMQ::Client::Channel
27
+ register_entity :exchange, AMQ::Client::Exchange
28
+
29
+ #
30
+ # API
31
+ #
32
+
33
+ def self.connect(settings = nil, &block)
34
+ settings = AMQ::Client::Settings.configure(settings)
35
+ instance = EM.connect(settings[:host], settings[:port], self, settings)
36
+
37
+ unless block.nil?
38
+ # delay calling block we were given till after we receive
39
+ # connection.open-ok. Connection will notify us when
40
+ # that happens.
41
+ instance.on_connection do
42
+ block.call(instance)
43
+ end
44
+ end
45
+
46
+ instance
47
+ end
48
+
49
+
50
+ attr_reader :connections
51
+
52
+
53
+ def initialize(*args)
54
+ super(*args)
55
+
56
+ # EventMachine::Connection's and Adapter's constructors arity
57
+ # make it easier to use *args. MK.
58
+ @settings = args.first
59
+ @connections = Array.new
60
+ @on_possible_authentication_failure = @settings[:on_possible_authentication_failure]
61
+
62
+ @chunk_buffer = ""
63
+ @connection_deferrable = Deferrable.new
64
+ @disconnection_deferrable = Deferrable.new
65
+
66
+ @authenticating = false
67
+
68
+ # succeeds when connection is open, that is, vhost is selected
69
+ # and client is given green light to proceed.
70
+ @connection_opened_deferrable = Deferrable.new
71
+
72
+ @tcp_connection_established = false
73
+
74
+ if self.heartbeat_interval > 0
75
+ @last_server_heartbeat = Time.now
76
+ EM.add_periodic_timer(self.heartbeat_interval, &method(:send_heartbeat))
77
+ end
78
+ end # initialize(*args)
79
+
80
+
81
+ def establish_connection(settings)
82
+ # an intentional no-op
83
+ end
84
+
85
+ alias send_raw send_data
86
+
87
+
88
+ def authenticating?
89
+ @authenticating
90
+ end # authenticating?
91
+
92
+ def tcp_connection_established?
93
+ @tcp_connection_established
94
+ end # tcp_connection_established?
95
+
96
+ #
97
+ # Implementation
98
+ #
99
+
100
+ def post_init
101
+ reset
102
+
103
+ @tcp_connection_established = true
104
+
105
+ self.handshake
106
+ rescue Exception => error
107
+ raise error
108
+ end # post_init
109
+
110
+ #
111
+ # EventMachine receives data in chunks, sometimes those chunks are smaller
112
+ # than the size of AMQP frame. That's why you need to add some kind of buffer.
113
+ #
114
+ def receive_data(chunk)
115
+ @chunk_buffer << chunk
116
+ while frame = get_next_frame
117
+ self.receive_frame(AMQ::Client::Framing::String::Frame.decode(frame))
118
+ end
119
+ end
120
+
121
+ def unbind
122
+ closing!
123
+
124
+ @tcp_connection_established = false
125
+
126
+ @connections.each { |c| c.on_connection_interruption }
127
+ @disconnection_deferrable.succeed
128
+
129
+ closed!
130
+
131
+ # since AMQP spec dictates that authentication failure is a protocol exception
132
+ # and protocol exceptions result in connection closure, check whether we are
133
+ # in the authentication stage. If so, it is likely to signal an authentication
134
+ # issue. Java client behaves the same way. MK.
135
+ if authenticating?
136
+ if sync?
137
+ raise PossibleAuthenticationFailureError.new(@settings)
138
+ else
139
+ @on_possible_authentication_failure.call(@settings) if @on_possible_authentication_failure
140
+ end
141
+ end
142
+ end # unbind
143
+
144
+
145
+
146
+ def on_connection(&block)
147
+ @connection_deferrable.callback(&block)
148
+ end # on_connection(&block)
149
+
150
+ # called by AMQ::Client::Connection after we receive connection.open-ok.
151
+ def connection_successful
152
+ @connection_deferrable.succeed
153
+ end # connection_successful
154
+
155
+
156
+ def on_open(&block)
157
+ @connection_opened_deferrable.callback(&block)
158
+ end # on_open(&block)
159
+
160
+ def open_successful
161
+ @authenticating = false
162
+ @connection_opened_deferrable.succeed
163
+
164
+ opened!
165
+ end # open_successful
166
+
167
+
168
+ def on_disconnection(&block)
169
+ @disconnection_deferrable.callback(&block)
170
+ end # on_disconnection(&block)
171
+
172
+ # called by AMQ::Client::Connection after we receive connection.close-ok.
173
+ def disconnection_successful
174
+ @disconnection_deferrable.succeed
175
+
176
+ self.close_connection
177
+ closed!
178
+ end # disconnection_successful
179
+
180
+
181
+
182
+ def on_possible_authentication_failure(&block)
183
+ @on_possible_authentication_failure = block
184
+ end
185
+
186
+
187
+ protected
188
+
189
+ def handshake(mechanism = "PLAIN", response = nil, locale = "en_GB")
190
+ username = @settings[:user] || @settings[:username]
191
+ password = @settings[:pass] || @settings[:password]
192
+
193
+ self.logger.info "[authentication] Credentials are #{username}/#{'*' * password.bytesize}"
194
+
195
+ self.connection = AMQ::Client::Connection.new(self, mechanism, self.encode_credentials(username, password), locale)
196
+
197
+ @authenticating = true
198
+ self.send_preamble
199
+ end
200
+
201
+ def reset
202
+ @size = 0
203
+ @payload = ""
204
+ @frames = Array.new
205
+ end
206
+
207
+ # @see http://tools.ietf.org/rfc/rfc2595.txt RFC 2595
208
+ def encode_credentials(username, password)
209
+ "\0#{username}\0#{password}"
210
+ end # encode_credentials(username, password)
211
+
212
+ def get_next_frame
213
+ return unless @chunk_buffer.size > 7 # otherwise, cannot read the length
214
+ # octet + short
215
+ offset = 3 # 1 + 2
216
+ # length
217
+ payload_length = @chunk_buffer[offset, 4].unpack('N')[0]
218
+ # 5: 4 bytes for long payload length, 1 byte final octet
219
+ frame_length = offset + 5 + payload_length
220
+ if frame_length <= @chunk_buffer.size
221
+ @chunk_buffer.slice!(0, frame_length)
222
+ else
223
+ nil
224
+ end
225
+ end
226
+ end # EventMachineClient
227
+ end # Client
228
+ end # AMQ
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+
3
+ require "socket"
4
+ require "amq/client"
5
+ require "amq/client/channel"
6
+ require "amq/client/exchange"
7
+ require "amq/client/queue"
8
+ require "amq/client/framing/io/frame"
9
+
10
+ module AMQ
11
+ module Client
12
+ class SocketClient
13
+
14
+ #
15
+ # Behaviors
16
+ #
17
+
18
+ include AMQ::Client::Adapter
19
+
20
+ self.sync = true
21
+
22
+ register_entity :channel, AMQ::Client::Channel
23
+ register_entity :exchange, AMQ::Client::Exchange
24
+ register_entity :queue, AMQ::Client::Queue
25
+
26
+
27
+ #
28
+ # API
29
+ #
30
+
31
+ def establish_connection(settings)
32
+ # NOTE: this doesn't work with "localhost", I don't know why:
33
+ settings[:host] = "127.0.0.1" if settings[:host] == "localhost"
34
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
35
+ sockaddr = Socket.pack_sockaddr_in(settings[:port], settings[:host])
36
+
37
+ @socket.connect(sockaddr)
38
+ rescue Errno::ECONNREFUSED => exception
39
+ message = "Don't forget to start an AMQP broker first!\nThe original message: #{exception.message}"
40
+ raise exception.class.new(message)
41
+ rescue Exception => exception
42
+ self.disconnect if self.connected?
43
+ raise exception
44
+ end
45
+
46
+ def connection
47
+ @socket
48
+ end # connection
49
+
50
+ def connected?
51
+ @socket && !@socket.closed?
52
+ end
53
+
54
+ def close_connection
55
+ @socket.close
56
+ end
57
+
58
+ def send_raw(data)
59
+ @socket.write(data)
60
+ end
61
+
62
+ def receive
63
+ frame = AMQ::Client::Framing::IO::Frame.decode(@socket)
64
+ self.receive_frame(frame)
65
+ frame
66
+ end
67
+
68
+ def receive_async
69
+ # NOTE: this might work with Socket#eof? as well, it can be better ...
70
+ # self.receive unless @socket.eof?
71
+
72
+ @sockets ||= [@socket] # It'll be always only one socket, but we don't want to create many arrays, mind the GC!
73
+ array = IO.select(@sockets, nil, nil, nil)
74
+ array[0].each do |socket|
75
+ res = self.receive
76
+ end
77
+ res
78
+ end
79
+
80
+ def read_until_receives(klass)
81
+ if self.sync?
82
+ until (frame = self.receive) && frame.is_a?(Protocol::MethodFrame) && frame.method_class == klass
83
+ sleep 0.1
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end