sa-carrot 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ module Carrot::AMQP
2
+ class Exchange
3
+ attr_reader :server, :type, :name, :opts, :key, :carrot
4
+
5
+ def initialize(carrot, type, name, opts = {})
6
+ @server, @type, @name, @opts = carrot.server, type, name, opts
7
+ @key = opts[:key]
8
+ @carrot = carrot
9
+
10
+ unless name == "amq.#{type}" or name == ''
11
+ server.send_frame(
12
+ Protocol::Exchange::Declare.new(
13
+ { :exchange => name, :type => type, :nowait => true }.merge(opts)
14
+ )
15
+ )
16
+ end
17
+ end
18
+ attr_reader :name, :type, :key
19
+
20
+ def publish(data, opts = {})
21
+ out = []
22
+
23
+ out << Protocol::Basic::Publish.new(
24
+ { :exchange => name, :routing_key => opts.delete(:key) || key }.merge(opts)
25
+ )
26
+ data = data.to_s
27
+ out << Protocol::Header.new(
28
+ Protocol::Basic,
29
+ data.length, {
30
+ :content_type => 'application/octet-stream',
31
+ :delivery_mode => (opts.delete(:persistent) ? 2 : 1),
32
+ :priority => 0
33
+ }.merge(opts)
34
+ )
35
+ out << Frame::Body.new(data)
36
+
37
+ server.send_frame(*out)
38
+ end
39
+
40
+ def delete(opts = {})
41
+ server.send_frame(
42
+ Protocol::Exchange::Delete.new({ :exchange => name, :nowait => true }.merge(opts))
43
+ )
44
+ carrot.exchanges.delete(name)
45
+ end
46
+
47
+ def reset
48
+ initialize(server, type, name, opts)
49
+ end
50
+ end
51
+ end
data/lib/amqp/frame.rb ADDED
@@ -0,0 +1,121 @@
1
+ module Carrot::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
63
+
64
+ if $0 =~ /bacon/ or $0 == __FILE__
65
+ require 'rubygems'
66
+ require 'bacon'
67
+ include AMQP
68
+
69
+ describe Frame do
70
+ should 'handle basic frame types' do
71
+ Frame::Method.new.id.should == 1
72
+ Frame::Header.new.id.should == 2
73
+ Frame::Body.new.id.should == 3
74
+ end
75
+
76
+ should 'convert method frames to binary' do
77
+ meth = Protocol::Connection::Secure.new :challenge => 'secret'
78
+
79
+ frame = Frame::Method.new(meth)
80
+ frame.to_binary.should.be.kind_of? Buffer
81
+ frame.to_s.should == [ 1, 0, meth.to_s.length, meth.to_s, 206 ].pack('CnNa*C')
82
+ end
83
+
84
+ should 'convert binary to method frames' do
85
+ orig = Frame::Method.new Protocol::Connection::Secure.new(:challenge => 'secret')
86
+
87
+ copy = Frame.parse(orig.to_binary)
88
+ copy.should == orig
89
+ end
90
+
91
+ should 'ignore partial frames until ready' do
92
+ frame = Frame::Method.new Protocol::Connection::Secure.new(:challenge => 'secret')
93
+ data = frame.to_s
94
+
95
+ buf = Buffer.new
96
+ Frame.parse(buf).should == nil
97
+
98
+ buf << data[0..5]
99
+ Frame.parse(buf).should == nil
100
+
101
+ buf << data[6..-1]
102
+ Frame.parse(buf).should == frame
103
+
104
+ Frame.parse(buf).should == nil
105
+ end
106
+
107
+ should 'convert header frames to binary' do
108
+ head = Protocol::Header.new(Protocol::Basic, :priority => 1)
109
+
110
+ frame = Frame::Header.new(head)
111
+ frame.to_s.should == [ 2, 0, head.to_s.length, head.to_s, 206 ].pack('CnNa*C')
112
+ end
113
+
114
+ should 'convert binary to header frame' do
115
+ orig = Frame::Header.new Protocol::Header.new(Protocol::Basic, :priority => 1)
116
+
117
+ copy = Frame.parse(orig.to_binary)
118
+ copy.should == orig
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,27 @@
1
+ module Carrot::AMQP
2
+ class Header
3
+ def initialize(server, header_obj)
4
+ @server = server
5
+ @header = header_obj
6
+ end
7
+
8
+ # Acknowledges the receipt of this message with the server.
9
+ def ack
10
+ @server.send(Protocol::Basic::Ack.new(:delivery_tag => properties[:delivery_tag]))
11
+ end
12
+
13
+ # Reject this message (XXX currently unimplemented in rabbitmq)
14
+ # * :requeue => true | false (default false)
15
+ def reject(opts = {})
16
+ @server.send(Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag])))
17
+ end
18
+
19
+ def method_missing(meth, *args, &blk)
20
+ @header.send(meth, *args, &blk)
21
+ end
22
+
23
+ def inspect
24
+ @header.inspect
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,209 @@
1
+ module Carrot::AMQP
2
+ module Protocol
3
+ #:stopdoc:
4
+ class Class::Method
5
+ def initialize *args
6
+ opts = args.pop if args.last.is_a? Hash
7
+ opts ||= {}
8
+
9
+ @debug = 1 # XXX hack, p(obj) == '' if no instance vars are set
10
+
11
+ if args.size == 1 and args.first.is_a? Buffer
12
+ buf = args.shift
13
+ else
14
+ buf = nil
15
+ end
16
+
17
+ self.class.arguments.each do |type, name|
18
+ val = buf ? buf.read(type) :
19
+ args.shift || opts[name] || opts[name.to_s]
20
+ instance_variable_set("@#{name}", val)
21
+ end
22
+ end
23
+
24
+ def arguments
25
+ self.class.arguments.inject({}) do |hash, (type, name)|
26
+ hash.update name => instance_variable_get("@#{name}")
27
+ end
28
+ end
29
+
30
+ def to_binary
31
+ buf = Buffer.new
32
+ buf.write :short, self.class.parent.id
33
+ buf.write :short, self.class.id
34
+
35
+ bits = []
36
+
37
+ self.class.arguments.each do |type, name|
38
+ val = instance_variable_get("@#{name}")
39
+ if type == :bit
40
+ bits << (val || false)
41
+ else
42
+ unless bits.empty?
43
+ buf.write :bit, bits
44
+ bits = []
45
+ end
46
+ buf.write type, val
47
+ end
48
+ end
49
+
50
+ buf.write :bit, bits unless bits.empty?
51
+ buf.rewind
52
+
53
+ buf
54
+ end
55
+
56
+ def to_s
57
+ to_binary.to_s
58
+ end
59
+
60
+ def to_frame channel = 0
61
+ Frame::Method.new(self, channel)
62
+ end
63
+ end
64
+
65
+ #:startdoc:
66
+ #
67
+ # Contains a properties hash that holds some potentially interesting
68
+ # information.
69
+ # * :delivery_mode
70
+ # 1 equals transient.
71
+ # 2 equals persistent. Unconsumed persistent messages will survive
72
+ # a server restart when they are stored in a durable queue.
73
+ # * :redelivered
74
+ # True or False
75
+ # * :routing_key
76
+ # The routing string used for matching this message to this queue.
77
+ # * :priority
78
+ # An integer in the range of 0 to 9 inclusive.
79
+ # * :content_type
80
+ # Always "application/octet-stream" (byte stream)
81
+ # * :exchange
82
+ # The source exchange which published this message.
83
+ # * :message_count
84
+ # The number of unconsumed messages contained in the queue.
85
+ # * :delivery_tag
86
+ # A monotonically increasing integer. This number should not be trusted
87
+ # as a sequence number. There is no guarantee it won't get reset.
88
+ class Header
89
+ def initialize *args
90
+ opts = args.pop if args.last.is_a? Hash
91
+ opts ||= {}
92
+
93
+ first = args.shift
94
+
95
+ if first.is_a? ::Class and first.ancestors.include? Protocol::Class
96
+ @klass = first
97
+ @size = args.shift || 0
98
+ @weight = args.shift || 0
99
+ @properties = opts
100
+
101
+ elsif first.is_a? Buffer or first.is_a? String
102
+ buf = first
103
+ buf = Buffer.new(buf) unless buf.is_a? Buffer
104
+
105
+ @klass = Protocol.classes[buf.read(:short)]
106
+ @weight = buf.read(:short)
107
+ @size = buf.read(:longlong)
108
+
109
+ props = buf.read(:properties, *klass.properties.map{|type,_| type })
110
+ @properties = Hash[*klass.properties.map{|_,name| name }.zip(props).reject{|k,v| v.nil? }.flatten]
111
+
112
+ else
113
+ raise ArgumentError, 'Invalid argument'
114
+ end
115
+
116
+ end
117
+ attr_accessor :klass, :size, :weight, :properties
118
+
119
+ def to_binary
120
+ buf = Buffer.new
121
+ buf.write :short, klass.id
122
+ buf.write :short, weight # XXX rabbitmq only supports weight == 0
123
+ buf.write :longlong, size
124
+ buf.write :properties, (klass.properties.map do |type, name|
125
+ [ type, properties[name] || properties[name.to_s] ]
126
+ end)
127
+ buf.rewind
128
+ buf
129
+ end
130
+
131
+ def to_s
132
+ to_binary.to_s
133
+ end
134
+
135
+ def to_frame channel = 0
136
+ Frame::Header.new(self, channel)
137
+ end
138
+
139
+ def == header
140
+ [ :klass, :size, :weight, :properties ].inject(true) do |eql, field|
141
+ eql and __send__(field) == header.__send__(field)
142
+ end
143
+ end
144
+
145
+ def method_missing meth, *args, &blk
146
+ @properties.has_key?(meth) || @klass.properties.find{|_,name| name == meth } ? @properties[meth] :
147
+ super
148
+ end
149
+ end
150
+
151
+ def self.parse buf
152
+ buf = Buffer.new(buf) unless buf.is_a? Buffer
153
+ class_id, method_id = buf.read(:short, :short)
154
+ classes[class_id].methods[method_id].new(buf)
155
+ end
156
+ #:stopdoc:
157
+ end
158
+ end
159
+
160
+ if $0 =~ /bacon/ or $0 == __FILE__
161
+ require 'bacon'
162
+ include AMQP
163
+
164
+ describe Protocol do
165
+ should 'instantiate methods with arguments' do
166
+ meth = Protocol::Connection::StartOk.new nil, 'PLAIN', nil, 'en_US'
167
+ meth.locale.should == 'en_US'
168
+ end
169
+
170
+ should 'instantiate methods with named parameters' do
171
+ meth = Protocol::Connection::StartOk.new :locale => 'en_US',
172
+ :mechanism => 'PLAIN'
173
+ meth.locale.should == 'en_US'
174
+ end
175
+
176
+ should 'convert methods to binary' do
177
+ meth = Protocol::Connection::Secure.new :challenge => 'secret'
178
+ meth.to_binary.should.be.kind_of? Buffer
179
+
180
+ meth.to_s.should == [ 10, 20, 6, 'secret' ].pack('nnNa*')
181
+ end
182
+
183
+ should 'convert binary to method' do
184
+ orig = Protocol::Connection::Secure.new :challenge => 'secret'
185
+ copy = Protocol.parse orig.to_binary
186
+ orig.should == copy
187
+ end
188
+
189
+ should 'convert headers to binary' do
190
+ head = Protocol::Header.new Protocol::Basic,
191
+ size = 5,
192
+ weight = 0,
193
+ :content_type => 'text/json',
194
+ :delivery_mode => 1,
195
+ :priority => 1
196
+ head.to_s.should == [ 60, weight, 0, size, 0b1001_1000_0000_0000, 9, 'text/json', 1, 1 ].pack('nnNNnCa*CC')
197
+ end
198
+
199
+ should 'convert binary to header' do
200
+ orig = Protocol::Header.new Protocol::Basic,
201
+ size = 5,
202
+ weight = 0,
203
+ :content_type => 'text/json',
204
+ :delivery_mode => 1,
205
+ :priority => 1
206
+ Protocol::Header.new(orig.to_binary).should == orig
207
+ end
208
+ end
209
+ end
data/lib/amqp/queue.rb ADDED
@@ -0,0 +1,97 @@
1
+ module Carrot::AMQP
2
+ class Queue
3
+ attr_reader :name, :server, :carrot
4
+ attr_accessor :delivery_tag
5
+
6
+ def initialize(carrot, name, opts = {})
7
+ @server = carrot.server
8
+ @opts = opts
9
+ @name = name
10
+ @carrot = carrot
11
+ server.send_frame(
12
+ Protocol::Queue::Declare.new({ :queue => name, :nowait => true }.merge(opts))
13
+ )
14
+ end
15
+
16
+ def pop(opts = {})
17
+ self.delivery_tag = nil
18
+ server.send_frame(
19
+ Protocol::Basic::Get.new({ :queue => name, :consumer_tag => name, :no_ack => !opts.delete(:ack), :nowait => true }.merge(opts))
20
+ )
21
+ method = server.next_method
22
+ return unless method.is_a?(Protocol::Basic::GetOk)
23
+
24
+ self.delivery_tag = method.delivery_tag
25
+
26
+ header = server.next_payload
27
+
28
+ msg = ''
29
+ while msg.length < header.size
30
+ msg << server.next_payload
31
+ end
32
+
33
+ msg
34
+ end
35
+
36
+ def ack
37
+ server.send_frame(
38
+ Protocol::Basic::Ack.new(:delivery_tag => delivery_tag)
39
+ )
40
+ end
41
+
42
+ def publish(data, opts = {})
43
+ exchange.publish(data, opts)
44
+ end
45
+
46
+ def message_count
47
+ status.first
48
+ end
49
+
50
+ def consumer_count
51
+ status.last
52
+ end
53
+
54
+ def status(opts = {}, &blk)
55
+ server.send_frame(
56
+ Protocol::Queue::Declare.new({ :queue => name, :passive => true }.merge(opts))
57
+ )
58
+ method = server.next_method
59
+ [method.message_count, method.consumer_count]
60
+ end
61
+
62
+ def bind(exchange, opts = {})
63
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
64
+ bindings[exchange] = opts
65
+ server.send_frame(
66
+ Protocol::Queue::Bind.new({ :queue => name, :exchange => exchange, :routing_key => opts.delete(:key), :nowait => true }.merge(opts))
67
+ )
68
+ end
69
+
70
+ def unbind(exchange, opts = {})
71
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
72
+ bindings.delete(exchange)
73
+
74
+ server.send_frame(
75
+ Protocol::Queue::Unbind.new({
76
+ :queue => name, :exchange => exchange, :routing_key => opts.delete(:key), :nowait => true }.merge(opts)
77
+ )
78
+ )
79
+ end
80
+
81
+ def delete(opts = {})
82
+ server.send_frame(
83
+ Protocol::Queue::Delete.new({ :queue => name, :nowait => true }.merge(opts))
84
+ )
85
+ carrot.queues.delete(name)
86
+ end
87
+
88
+ private
89
+ def exchange
90
+ @exchange ||= Exchange.new(carrot, :direct, '', :key => name)
91
+ end
92
+
93
+ def bindings
94
+ @bindings ||= {}
95
+ end
96
+ end
97
+ end