amq-client 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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