famoseagle-carrot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,209 @@
1
+ module 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,70 @@
1
+ module AMQP
2
+ class Queue
3
+ attr_reader :name, :server
4
+ attr_accessor :delivery_tag
5
+
6
+ def initialize(server, name, opts = {})
7
+ @server = server
8
+ @opts = opts
9
+ @name = name
10
+ server.send_frame(
11
+ Protocol::Queue::Declare.new({ :queue => name, :nowait => true }.merge(opts))
12
+ )
13
+ end
14
+
15
+ def delete(opts = {})
16
+ server.send_frame(
17
+ Protocol::Queue::Delete.new({ :queue => name, :nowait => true }.merge(opts))
18
+ )
19
+ pp server.next_method
20
+ end
21
+
22
+ def pop(opts = {})
23
+ self.delivery_tag = nil
24
+ server.send_frame(
25
+ Protocol::Basic::Get.new({ :queue => name, :consumer_tag => name, :no_ack => !opts.delete(:ack), :nowait => true }.merge(opts))
26
+ )
27
+ method = server.next_method
28
+ return if method.is_a?(Protocol::Basic::GetEmpty)
29
+
30
+ self.delivery_tag = method.delivery_tag
31
+
32
+ header = server.next_payload
33
+ msg = server.next_payload
34
+ raise 'unexpected length' if msg.length < header.size
35
+
36
+ msg
37
+ end
38
+
39
+ def ack
40
+ server.send_frame(
41
+ Protocol::Basic::Ack.new(:delivery_tag => delivery_tag)
42
+ )
43
+ end
44
+
45
+ def publish(data, opts = {})
46
+ exchange.publish(data, opts)
47
+ end
48
+
49
+ def message_count
50
+ status.first
51
+ end
52
+
53
+ def consumer_count
54
+ status.last
55
+ end
56
+
57
+ def status(opts = {}, &blk)
58
+ server.send_frame(
59
+ Protocol::Queue::Declare.new({ :queue => name, :passive => true }.merge(opts))
60
+ )
61
+ method = server.next_method
62
+ [method.message_count, method.consumer_count]
63
+ end
64
+
65
+ private
66
+ def exchange
67
+ @exchange ||= Exchange.new(server, :direct, '', :key => name)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,213 @@
1
+ require 'socket'
2
+ require 'thread'
3
+ require 'timeout'
4
+
5
+ module AMQP
6
+ class Server
7
+ CONNECT_TIMEOUT = 1.0
8
+ RETRY_DELAY = 10.0
9
+ DEFAULT_PORT = 5672
10
+
11
+ attr_reader :host, :port, :status
12
+ attr_accessor :retry_at, :channel, :ticket
13
+
14
+ class ConnectionError < StandardError; end
15
+ class ServerError < StandardError; end
16
+ class ClientError < StandardError; end
17
+ class ServerDown < StandardError; end
18
+ class ProtocolError < StandardError; end
19
+
20
+ def initialize(opts = {})
21
+ @host = opts[:host] || 'localhost'
22
+ @port = opts[:port] || DEFAULT_PORT
23
+ @user = opts[:user] || 'guest'
24
+ @pass = opts[:pass] || 'guest'
25
+ @vhost = opts[:vhost] || '/'
26
+ @insist = opts[:insist]
27
+ @status = 'NOT CONNECTED'
28
+
29
+ @multithread = opts[:multithread]
30
+ start_session
31
+ end
32
+
33
+ def start_session
34
+ @channel = 0
35
+ write(HEADER)
36
+ write([1, 1, VERSION_MAJOR, VERSION_MINOR].pack('C4'))
37
+ raise ProtocolError, 'bad start connection' unless next_method.is_a?(Protocol::Connection::Start)
38
+
39
+ send_frame(
40
+ Protocol::Connection::StartOk.new(
41
+ {:platform => 'Ruby', :product => 'Carrot', :information => 'http://github.com/famosagle/carrot', :version => VERSION},
42
+ 'AMQPLAIN',
43
+ {:LOGIN => @user, :PASSWORD => @pass},
44
+ 'en_US'
45
+ )
46
+ )
47
+
48
+ if next_method.is_a?(Protocol::Connection::Tune)
49
+ send_frame(
50
+ Protocol::Connection::TuneOk.new( :channel_max => 0, :frame_max => 131072, :heartbeat => 0)
51
+ )
52
+ end
53
+
54
+ send_frame(
55
+ Protocol::Connection::Open.new(:virtual_host => @vhost, :capabilities => '', :insist => @insist)
56
+ )
57
+ raise ProtocolError, 'bad open connection' unless next_method.is_a?(Protocol::Connection::OpenOk)
58
+
59
+ @channel = 1
60
+ send_frame(Protocol::Channel::Open.new)
61
+ raise ProtocolError, "cannot open channel #{channel}" unless next_method.is_a?(Protocol::Channel::OpenOk)
62
+
63
+ send_frame(
64
+ Protocol::Access::Request.new(:realm => '/data', :read => true, :write => true, :active => true, :passive => true)
65
+ )
66
+ method = next_method
67
+ raise ProtocolError, 'access denied' unless method.is_a?(Protocol::Access::RequestOk)
68
+ self.ticket = method.ticket
69
+ end
70
+
71
+ def send_frame(*args)
72
+ args.each do |data|
73
+ data.ticket = ticket if ticket and data.respond_to?(:ticket=)
74
+ data = data.to_frame(channel) unless data.is_a?(Frame)
75
+ data.channel = channel
76
+
77
+ log :send, data
78
+ write(data.to_s)
79
+ end
80
+ end
81
+
82
+ def next_frame
83
+ frame = Frame.get(self)
84
+ log :received, frame
85
+ frame
86
+ end
87
+
88
+ def next_method
89
+ next_payload
90
+ end
91
+
92
+ def next_payload
93
+ next_frame.payload
94
+ end
95
+
96
+ def close
97
+ send_frame(
98
+ Protocol::Channel::Close.new(:reply_code => 200, :reply_text => 'bye', :method_id => 0, :class_id => 0)
99
+ )
100
+ puts "Error closing channel #{channel}" unless next_method.is_a?(Protocol::Channel::CloseOk)
101
+
102
+ self.channel = 0
103
+ send_frame(
104
+ Protocol::Connection::Close.new(:reply_code => 200, :reply_text => 'Goodbye', :class_id => 0, :method_id => 0)
105
+ )
106
+ puts "Error closing connection" unless next_method.is_a?(Protocol::Connection::CloseOk)
107
+
108
+ close_socket
109
+ end
110
+
111
+ def read(*args)
112
+ with_socket_management do |socket|
113
+ socket.read(*args)
114
+ end
115
+ end
116
+
117
+ def write(*args)
118
+ with_socket_management do |socket|
119
+ socket.write(*args)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def with_socket_management(&block)
126
+ retried = false
127
+ begin
128
+ mutex.lock if multithread?
129
+ yield socket
130
+
131
+ rescue ClientError, ServerError, SocketError, SystemCallError, IOError => error
132
+ if not retried
133
+ # Close the socket and retry once.
134
+ close_socket
135
+ #start_session
136
+ retried = true
137
+ retry
138
+ else
139
+ # Mark the server dead and raise an error.
140
+ close(error.message)
141
+
142
+ # Reraise as a ConnectionError
143
+ new_error = ConnectionError.new("#{error.class}: #{error.message}")
144
+ new_error.set_backtrace(error.backtrace)
145
+ raise new_error
146
+ end
147
+ ensure
148
+ mutex.unlock if multithread?
149
+ end
150
+ end
151
+
152
+ def socket
153
+ return @socket if @socket and not @socket.closed?
154
+ raise ServerDown, "will retry at #{retry_at}" unless retry?
155
+
156
+ begin
157
+ # Attempt to connect.
158
+ mutex.lock if multithread?
159
+ @socket = timeout(CONNECT_TIMEOUT) do
160
+ TCPSocket.new(host, port)
161
+ end
162
+
163
+ if Socket.constants.include? 'TCP_NODELAY'
164
+ @socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
165
+ end
166
+ @retry_at = nil
167
+ @status = 'CONNECTED'
168
+ rescue SocketError, SystemCallError, IOError, Timeout::Error => e
169
+ close_socket
170
+ raise ServerDown, e.message
171
+ ensure
172
+ mutex.unlock if multithread?
173
+ end
174
+
175
+ @socket
176
+ end
177
+
178
+ def multithread?
179
+ @multithread
180
+ end
181
+
182
+ def retry?
183
+ @retry_at.nil? or @retry_at < Time.now
184
+ end
185
+
186
+ def unexpected_eof!
187
+ raise ConnectionError, 'unexpected end of file'
188
+ end
189
+
190
+ def close_socket(reason=nil)
191
+ # Close the socket. The server is not considered dead.
192
+ mutex.lock if multithread?
193
+ @socket.close if @socket and not @socket.closed?
194
+ @socket = nil
195
+ @retry_at = nil
196
+ @status = "NOT CONNECTED"
197
+ ensure
198
+ mutex.unlock if multithread?
199
+ end
200
+
201
+ def mutex
202
+ @mutex ||= Mutex.new
203
+ end
204
+
205
+ def log(*args)
206
+ return unless Carrot.logging?
207
+ require 'pp'
208
+ pp args
209
+ puts
210
+ end
211
+
212
+ end
213
+ end