maxlapshin-carrot 0.6.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.
@@ -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
@@ -0,0 +1,94 @@
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
+ msg = server.next_payload
28
+ raise 'unexpected length' if msg.length < header.size
29
+
30
+ return msg, header
31
+ end
32
+
33
+ def ack
34
+ server.send_frame(
35
+ Protocol::Basic::Ack.new(:delivery_tag => delivery_tag)
36
+ )
37
+ end
38
+
39
+ def publish(data, opts = {})
40
+ exchange.publish(data, opts)
41
+ end
42
+
43
+ def message_count
44
+ status.first
45
+ end
46
+
47
+ def consumer_count
48
+ status.last
49
+ end
50
+
51
+ def status(opts = {}, &blk)
52
+ server.send_frame(
53
+ Protocol::Queue::Declare.new({ :queue => name, :passive => true }.merge(opts))
54
+ )
55
+ method = server.next_method
56
+ [method.message_count, method.consumer_count]
57
+ end
58
+
59
+ def bind(exchange, opts = {})
60
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
61
+ bindings[exchange] = opts
62
+ server.send_frame(
63
+ Protocol::Queue::Bind.new({ :queue => name, :exchange => exchange, :routing_key => opts.delete(:key), :nowait => true }.merge(opts))
64
+ )
65
+ end
66
+
67
+ def unbind(exchange, opts = {})
68
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
69
+ bindings.delete(exchange)
70
+
71
+ server.send_frame(
72
+ Protocol::Queue::Unbind.new({
73
+ :queue => name, :exchange => exchange, :routing_key => opts.delete(:key), :nowait => true }.merge(opts)
74
+ )
75
+ )
76
+ end
77
+
78
+ def delete(opts = {})
79
+ server.send_frame(
80
+ Protocol::Queue::Delete.new({ :queue => name, :nowait => true }.merge(opts))
81
+ )
82
+ carrot.queues.delete(name)
83
+ end
84
+
85
+ private
86
+ def exchange
87
+ @exchange ||= Exchange.new(carrot, :direct, '', :key => name)
88
+ end
89
+
90
+ def bindings
91
+ @bindings ||= {}
92
+ end
93
+ end
94
+ end