celldee-bunny 0.0.1

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.
data/README.markdown ADDED
@@ -0,0 +1,43 @@
1
+ # bunny README
2
+
3
+ This project was started to enable me to interact with RabbitMQ using Ruby. It has borrowed heavily from two projects:
4
+
5
+ 1. **amqp** by *tmm1* (http://github.com/tmm1/amqp/tree/master)
6
+ 2. **carrot** by *famoseagle* (http://github.com/famoseagle/carrot/tree/master)
7
+
8
+ I will be creating tests, examples and generally tinkering, so please bear with me.
9
+
10
+ ## Quick Start
11
+
12
+ require 'bunny'
13
+
14
+ b = Bunny.new(:logging => true)
15
+
16
+ # start a communication session with the amqp server
17
+ begin
18
+ b.start
19
+ rescue Exception => e
20
+ puts 'ERROR - Could not start a session: ' + e
21
+ exit
22
+ end
23
+
24
+ # declare a queue
25
+ q = Queue.new(b.client, 'test1')
26
+
27
+ # create a direct exchange
28
+ exchange = Exchange.new(b.client, :direct, 'test_ex')
29
+
30
+ # bind the queue to the exchange
31
+ q.bind(exchange)
32
+
33
+ # publish a message to the exchange
34
+ exchange.publish('Hello everybody!')
35
+
36
+ # get message from the queue
37
+ msg = q.pop
38
+
39
+ puts 'This is the message: ' + msg + "\n\n"
40
+
41
+ ## LICENSE
42
+
43
+ Copyright (c) 2009 Chris Duncan; Published under The MIT License, see License
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ task :codegen do
2
+ sh 'ruby protocol/codegen.rb > lib/amqp/spec.rb'
3
+ sh 'ruby lib/amqp/spec.rb'
4
+ end
5
+
6
+ task :spec do
7
+ require 'spec/rake/spectask'
8
+ Spec::Rake::SpecTask.new do |t|
9
+ t.spec_opts = ['--color']
10
+ end
11
+ end
12
+
@@ -0,0 +1,30 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'bunny'
4
+
5
+ b = Bunny.new(:logging => true)
6
+
7
+ # start a communication session with the amqp server
8
+ begin
9
+ b.start
10
+ rescue Exception => e
11
+ puts 'ERROR - Could not start a session: ' + e
12
+ exit
13
+ end
14
+
15
+ # declare a queue
16
+ q = Queue.new(b.client, 'test1')
17
+
18
+ # create a direct exchange
19
+ exchange = Exchange.new(b.client, :direct, 'test_ex')
20
+
21
+ # bind the queue to the exchange
22
+ q.bind(exchange)
23
+
24
+ # publish a message to the exchange
25
+ exchange.publish('Hello everybody!')
26
+
27
+ # get message from the queue
28
+ msg = q.pop
29
+
30
+ puts 'This is the message: ' + msg + "\n\n"
@@ -0,0 +1,276 @@
1
+ if [].map.respond_to? :with_index
2
+ class Array #:nodoc:
3
+ def enum_with_index
4
+ each.with_index
5
+ end
6
+ end
7
+ else
8
+ require 'enumerator'
9
+ end
10
+
11
+ module AMQP
12
+ class Buffer #:nodoc: all
13
+ class Overflow < StandardError; end
14
+ class InvalidType < StandardError; end
15
+
16
+ def initialize data = ''
17
+ @data = data
18
+ @pos = 0
19
+ end
20
+
21
+ attr_reader :pos
22
+
23
+ def data
24
+ @data.clone
25
+ end
26
+ alias :contents :data
27
+ alias :to_s :data
28
+
29
+ def << data
30
+ @data << data.to_s
31
+ self
32
+ end
33
+
34
+ def length
35
+ @data.length
36
+ end
37
+
38
+ def empty?
39
+ pos == length
40
+ end
41
+
42
+ def rewind
43
+ @pos = 0
44
+ end
45
+
46
+ def read_properties *types
47
+ types.shift if types.first == :properties
48
+
49
+ i = 0
50
+ values = []
51
+
52
+ while props = read(:short)
53
+ (0..14).each do |n|
54
+ # no more property types
55
+ break unless types[i]
56
+
57
+ # if flag is set
58
+ if props & (1<<(15-n)) != 0
59
+ if types[i] == :bit
60
+ # bit values exist in flags only
61
+ values << true
62
+ else
63
+ # save type name for later reading
64
+ values << types[i]
65
+ end
66
+ else
67
+ # property not set or is false bit
68
+ values << (types[i] == :bit ? false : nil)
69
+ end
70
+
71
+ i+=1
72
+ end
73
+
74
+ # bit(0) == 0 means no more property flags
75
+ break unless props & 1 == 1
76
+ end
77
+
78
+ values.map do |value|
79
+ value.is_a?(Symbol) ? read(value) : value
80
+ end
81
+ end
82
+
83
+ def read *types
84
+ if types.first == :properties
85
+ return read_properties(*types)
86
+ end
87
+
88
+ values = types.map do |type|
89
+ case type
90
+ when :octet
91
+ _read(1, 'C')
92
+ when :short
93
+ _read(2, 'n')
94
+ when :long
95
+ _read(4, 'N')
96
+ when :longlong
97
+ upper, lower = _read(8, 'NN')
98
+ upper << 32 | lower
99
+ when :shortstr
100
+ _read read(:octet)
101
+ when :longstr
102
+ _read read(:long)
103
+ when :timestamp
104
+ Time.at read(:longlong)
105
+ when :table
106
+ t = Hash.new
107
+
108
+ table = Buffer.new(read(:longstr))
109
+ until table.empty?
110
+ key, type = table.read(:shortstr, :octet)
111
+ key = key.intern
112
+ t[key] ||= case type
113
+ when 83 # 'S'
114
+ table.read(:longstr)
115
+ when 73 # 'I'
116
+ table.read(:long)
117
+ when 68 # 'D'
118
+ exp = table.read(:octet)
119
+ num = table.read(:long)
120
+ num / 10.0**exp
121
+ when 84 # 'T'
122
+ table.read(:timestamp)
123
+ when 70 # 'F'
124
+ table.read(:table)
125
+ end
126
+ end
127
+
128
+ t
129
+ when :bit
130
+ if (@bits ||= []).empty?
131
+ val = read(:octet)
132
+ @bits = (0..7).map{|i| (val & 1<<i) != 0 }
133
+ end
134
+
135
+ @bits.shift
136
+ else
137
+ raise InvalidType, "Cannot read data of type #{type}"
138
+ end
139
+ end
140
+
141
+ types.size == 1 ? values.first : values
142
+ end
143
+
144
+ def write type, data
145
+ case type
146
+ when :octet
147
+ _write(data, 'C')
148
+ when :short
149
+ _write(data, 'n')
150
+ when :long
151
+ _write(data, 'N')
152
+ when :longlong
153
+ lower = data & 0xffffffff
154
+ upper = (data & ~0xffffffff) >> 32
155
+ _write([upper, lower], 'NN')
156
+ when :shortstr
157
+ data = (data || '').to_s
158
+ _write([data.length, data], 'Ca*')
159
+ when :longstr
160
+ if data.is_a? Hash
161
+ write(:table, data)
162
+ else
163
+ data = (data || '').to_s
164
+ _write([data.length, data], 'Na*')
165
+ end
166
+ when :timestamp
167
+ write(:longlong, data.to_i)
168
+ when :table
169
+ data ||= {}
170
+ write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
171
+ table.write(:shortstr, key.to_s)
172
+
173
+ case value
174
+ when String
175
+ table.write(:octet, 83) # 'S'
176
+ table.write(:longstr, value.to_s)
177
+ when Fixnum
178
+ table.write(:octet, 73) # 'I'
179
+ table.write(:long, value)
180
+ when Float
181
+ table.write(:octet, 68) # 'D'
182
+ # XXX there's gotta be a better way to do this..
183
+ exp = value.to_s.split('.').last.length
184
+ num = value * 10**exp
185
+ table.write(:octet, exp)
186
+ table.write(:long, num)
187
+ when Time
188
+ table.write(:octet, 84) # 'T'
189
+ table.write(:timestamp, value)
190
+ when Hash
191
+ table.write(:octet, 70) # 'F'
192
+ table.write(:table, value)
193
+ end
194
+
195
+ table
196
+ end)
197
+ when :bit
198
+ [*data].to_enum(:each_slice, 8).each{|bits|
199
+ write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
200
+ byte |= 1<<i if bit
201
+ byte
202
+ })
203
+ }
204
+ when :properties
205
+ values = []
206
+ data.enum_with_index.inject(0) do |short, ((type, value), i)|
207
+ n = i % 15
208
+ last = i+1 == data.size
209
+
210
+ if (n == 0 and i != 0) or last
211
+ if data.size > i+1
212
+ short |= 1<<0
213
+ elsif last and value
214
+ values << [type,value]
215
+ short |= 1<<(15-n)
216
+ end
217
+
218
+ write(:short, short)
219
+ short = 0
220
+ end
221
+
222
+ if value and !last
223
+ values << [type,value]
224
+ short |= 1<<(15-n)
225
+ end
226
+
227
+ short
228
+ end
229
+
230
+ values.each do |type, value|
231
+ write(type, value) unless type == :bit
232
+ end
233
+ else
234
+ raise InvalidType, "Cannot write data of type #{type}"
235
+ end
236
+
237
+ self
238
+ end
239
+
240
+ def extract
241
+ begin
242
+ cur_data, cur_pos = @data.clone, @pos
243
+ yield self
244
+ rescue Overflow
245
+ @data, @pos = cur_data, cur_pos
246
+ nil
247
+ end
248
+ end
249
+
250
+ def _read(size, pack = nil)
251
+ if @data.is_a?(Client)
252
+ raw = @data.read(size)
253
+ return raw if raw.nil? or pack.nil?
254
+ return raw.unpack(pack).first
255
+ end
256
+
257
+ if @pos + size > length
258
+ raise Overflow
259
+ else
260
+ data = @data[@pos,size]
261
+ @data[@pos,size] = ''
262
+ if pack
263
+ data = data.unpack(pack)
264
+ data = data.pop if data.size == 1
265
+ end
266
+ data
267
+ end
268
+ end
269
+
270
+ def _write data, pack = nil
271
+ data = [*data].pack(pack) if pack
272
+ @data[@pos,0] = data
273
+ @pos += data.length
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,160 @@
1
+ module AMQP
2
+ class Client
3
+ CONNECT_TIMEOUT = 1.0
4
+ RETRY_DELAY = 10.0
5
+
6
+ attr_reader :status
7
+ attr_accessor :channel, :host, :logging, :port, :ticket
8
+
9
+ class ServerDown < StandardError; end
10
+ class ProtocolError < StandardError; end
11
+
12
+ def initialize(opts = {})
13
+ @host = opts[:host] || 'localhost'
14
+ @port = opts[:port] || AMQP::PORT
15
+ @user = opts[:user] || 'guest'
16
+ @pass = opts[:pass] || 'guest'
17
+ @vhost = opts[:vhost] || '/'
18
+ @logging = opts[:logging] || false
19
+ @insist = opts[:insist]
20
+ @status = 'NOT CONNECTED'
21
+ end
22
+
23
+ def send_frame(*args)
24
+ args.each do |data|
25
+ data.ticket = ticket if ticket and data.respond_to?(:ticket=)
26
+ data = data.to_frame(channel) unless data.is_a?(Frame)
27
+ data.channel = channel
28
+
29
+ log :send, data
30
+ write(data.to_s)
31
+ end
32
+ nil
33
+ end
34
+
35
+ def next_frame
36
+ frame = Frame.parse(buffer)
37
+ log :received, frame
38
+ frame
39
+ end
40
+
41
+ def next_method
42
+ next_payload
43
+ end
44
+
45
+ def next_payload
46
+ frame = next_frame
47
+ frame and frame.payload
48
+ end
49
+
50
+ def close
51
+ send_frame(
52
+ Protocol::Channel::Close.new(:reply_code => 200, :reply_text => 'bye', :method_id => 0, :class_id => 0)
53
+ )
54
+ puts "Error closing channel #{channel}" unless next_method.is_a?(Protocol::Channel::CloseOk)
55
+
56
+ self.channel = 0
57
+ send_frame(
58
+ Protocol::Connection::Close.new(:reply_code => 200, :reply_text => 'Goodbye', :class_id => 0, :method_id => 0)
59
+ )
60
+ puts "Error closing connection" unless next_method.is_a?(Protocol::Connection::CloseOk)
61
+
62
+ close_socket
63
+ end
64
+
65
+ def read(*args)
66
+ send_command(:read, *args)
67
+ end
68
+
69
+ def write(*args)
70
+ send_command(:write, *args)
71
+ end
72
+
73
+ def start_session
74
+ @channel = 0
75
+ write(HEADER)
76
+ write([1, 1, VERSION_MAJOR, VERSION_MINOR].pack('C4'))
77
+ raise ProtocolError, 'bad start connection' unless next_method.is_a?(Protocol::Connection::Start)
78
+
79
+ send_frame(
80
+ Protocol::Connection::StartOk.new(
81
+ {:platform => 'Ruby', :product => 'Bunny', :information => 'http://github.com/celldee/bunny', :version => VERSION},
82
+ 'AMQPLAIN',
83
+ {:LOGIN => @user, :PASSWORD => @pass},
84
+ 'en_US'
85
+ )
86
+ )
87
+
88
+ if next_method.is_a?(Protocol::Connection::Tune)
89
+ send_frame(
90
+ Protocol::Connection::TuneOk.new( :channel_max => 0, :frame_max => 131072, :heartbeat => 0)
91
+ )
92
+ end
93
+
94
+ send_frame(
95
+ Protocol::Connection::Open.new(:virtual_host => @vhost, :capabilities => '', :insist => @insist)
96
+ )
97
+ raise ProtocolError, 'Cannot open connection' unless next_method.is_a?(Protocol::Connection::OpenOk)
98
+
99
+ @channel = 1
100
+ send_frame(Protocol::Channel::Open.new)
101
+ raise ProtocolError, "Cannot open channel #{channel}" unless next_method.is_a?(Protocol::Channel::OpenOk)
102
+
103
+ send_frame(
104
+ Protocol::Access::Request.new(:realm => '/data', :read => true, :write => true, :active => true, :passive => true)
105
+ )
106
+ method = next_method
107
+ raise ProtocolError, 'Access denied' unless method.is_a?(Protocol::Access::RequestOk)
108
+ self.ticket = method.ticket
109
+ end
110
+
111
+ private
112
+
113
+ def buffer
114
+ @buffer ||= Buffer.new(self)
115
+ end
116
+
117
+ def send_command(cmd, *args)
118
+ begin
119
+ socket.__send__(cmd, *args)
120
+ rescue Errno::EPIPE, IOError => e
121
+ raise ServerDown, e.message
122
+ end
123
+ end
124
+
125
+ def socket
126
+ return @socket if @socket and not @socket.closed?
127
+
128
+ begin
129
+ # Attempt to connect.
130
+ @socket = timeout(CONNECT_TIMEOUT) do
131
+ TCPSocket.new(host, port)
132
+ end
133
+
134
+ if Socket.constants.include? 'TCP_NODELAY'
135
+ @socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
136
+ end
137
+ @status = 'CONNECTED'
138
+ rescue SocketError, SystemCallError, IOError, Timeout::Error => e
139
+ raise ServerDown, e.message
140
+ end
141
+
142
+ @socket
143
+ end
144
+
145
+ def close_socket(reason=nil)
146
+ # Close the socket. The server is not considered dead.
147
+ @socket.close if @socket and not @socket.closed?
148
+ @socket = nil
149
+ @status = "NOT CONNECTED"
150
+ end
151
+
152
+ def log(*args)
153
+ return unless logging
154
+ require 'pp'
155
+ pp args
156
+ puts
157
+ end
158
+
159
+ end
160
+ end
data/lib/amqp/frame.rb ADDED
@@ -0,0 +1,62 @@
1
+ module AMQP
2
+ class Frame #:nodoc: all
3
+ def initialize payload = nil, channel = 0
4
+ @channel, @payload = channel, payload
5
+ end
6
+ attr_accessor :channel, :payload
7
+
8
+ def id
9
+ self.class::ID
10
+ end
11
+
12
+ def to_binary
13
+ buf = Buffer.new
14
+ buf.write :octet, id
15
+ buf.write :short, channel
16
+ buf.write :longstr, payload
17
+ buf.write :octet, FOOTER
18
+ buf.rewind
19
+ buf
20
+ end
21
+
22
+ def to_s
23
+ to_binary.to_s
24
+ end
25
+
26
+ def == frame
27
+ [ :id, :channel, :payload ].inject(true) do |eql, field|
28
+ eql and __send__(field) == frame.__send__(field)
29
+ end
30
+ end
31
+
32
+ class Invalid < StandardError; end
33
+
34
+ class Method
35
+ def initialize payload = nil, channel = 0
36
+ super
37
+ unless @payload.is_a? Protocol::Class::Method or @payload.nil?
38
+ @payload = Protocol.parse(@payload)
39
+ end
40
+ end
41
+ end
42
+
43
+ class Header
44
+ def initialize payload = nil, channel = 0
45
+ super
46
+ unless @payload.is_a? Protocol::Header or @payload.nil?
47
+ @payload = Protocol::Header.new(@payload)
48
+ end
49
+ end
50
+ end
51
+
52
+ class Body; end
53
+
54
+ def self.parse buf
55
+ buf = Buffer.new(buf) unless buf.is_a? Buffer
56
+ buf.extract do
57
+ id, channel, payload, footer = buf.read(:octet, :short, :longstr, :octet)
58
+ Frame.types[id].new(payload, channel) if footer == FOOTER
59
+ end
60
+ end
61
+ end
62
+ end