secure_carrot 0.1.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,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,144 @@
1
+ module Carrot::AMQP
2
+ class Queue
3
+ attr_reader :name, :carrot
4
+ attr_accessor :delivery_tag
5
+
6
+ def initialize(carrot, name, opts = {})
7
+ @opts = opts
8
+ @name = name
9
+ @carrot = carrot
10
+ server.send_frame(
11
+ Protocol::Queue::Declare.new({ :queue => name, :nowait => true }.merge(opts))
12
+ )
13
+ end
14
+
15
+ def pop(opts = {})
16
+ self.delivery_tag = nil
17
+ server.send_frame(
18
+ Protocol::Basic::Get.new({ :queue => name, :consumer_tag => name, :no_ack => !opts.delete(:ack), :nowait => true }.merge(opts))
19
+ )
20
+ method = server.next_method
21
+ return unless method.kind_of?(Protocol::Basic::GetOk)
22
+
23
+ self.delivery_tag = method.delivery_tag
24
+
25
+ header = server.next_payload
26
+
27
+ msg = ''
28
+ while msg.length < header.size
29
+ msg << server.next_payload
30
+ end
31
+
32
+ msg
33
+ end
34
+
35
+ def ack
36
+ server.send_frame(
37
+ Protocol::Basic::Ack.new(:delivery_tag => delivery_tag)
38
+ )
39
+ end
40
+
41
+ def publish(data, opts = {})
42
+ exchange.publish(data, opts)
43
+ end
44
+
45
+ def message_count
46
+ status.first
47
+ end
48
+
49
+ def consumer_count
50
+ status.last
51
+ end
52
+
53
+ def status(opts = {}, &blk)
54
+ server.send_frame(
55
+ Protocol::Queue::Declare.new({ :queue => name, :passive => true }.merge(opts))
56
+ )
57
+ method = server.next_method
58
+ return [nil, nil] if method.kind_of?(Protocol::Connection::Close)
59
+
60
+ [method.message_count, method.consumer_count]
61
+ end
62
+
63
+ def bind(exchange, opts = {})
64
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
65
+ bindings[exchange] = opts
66
+ server.send_frame(
67
+ Protocol::Queue::Bind.new({ :queue => name, :exchange => exchange, :routing_key => opts.delete(:key), :nowait => true }.merge(opts))
68
+ )
69
+ end
70
+
71
+ def unbind(exchange, opts = {})
72
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
73
+ bindings.delete(exchange)
74
+
75
+ server.send_frame(
76
+ Protocol::Queue::Unbind.new({
77
+ :queue => name, :exchange => exchange, :routing_key => opts.delete(:key), :nowait => true }.merge(opts)
78
+ )
79
+ )
80
+ end
81
+
82
+ def delete(opts = {})
83
+ server.send_frame(
84
+ Protocol::Queue::Delete.new({ :queue => name, :nowait => true }.merge(opts))
85
+ )
86
+ carrot.queues.delete(name)
87
+ end
88
+
89
+ def purge(opts = {})
90
+ server.send_frame(
91
+ Protocol::Queue::Purge.new({ :queue => name, :nowait => true }.merge(opts))
92
+ )
93
+ end
94
+
95
+ def server
96
+ carrot.server
97
+ end
98
+
99
+ ##
100
+ # Is a wrapper around publish to send persistent messages.
101
+
102
+ def send_message(data,opts={})
103
+ opts.merge!(:persistent => true)
104
+ exchange.publish(data,opts)
105
+ end
106
+
107
+ def encrypt_message(message, password)
108
+ encrypted_message = message.encrypt(:symmetric, :password => password)
109
+ encrypted_message
110
+ end
111
+
112
+ def decrypt_message(message, password)
113
+ decrypted_message = message.decrypt(:symmetric, :password => password)
114
+ decrypted_message
115
+ end
116
+
117
+ ##
118
+ # Is a wrapper around publish to send persistent and encrypted messages using symmetric key.
119
+
120
+ def encrypt_and_send_message(message, password, opts={})
121
+ opts.merge!(:persistent => true)
122
+ encrypted_message = encrypt_message(message, password)
123
+ exchange.publish(encrypted_message,opts)
124
+ end
125
+
126
+ ##
127
+ # This method will receive and decrypt messages using symmetric key.
128
+
129
+ def receive_and_decrypt_message(password, opts={})
130
+ msg = pop(opts)
131
+ decrypted_message = decrypt_message(msg, password)
132
+ decrypted_message
133
+ end
134
+
135
+ private
136
+ def exchange
137
+ @exchange ||= Exchange.new(carrot, :direct, '', :key => name)
138
+ end
139
+
140
+ def bindings
141
+ @bindings ||= {}
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,187 @@
1
+ require 'socket'
2
+ require 'thread'
3
+ require 'timeout'
4
+
5
+ module Carrot::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 :channel, :ticket
13
+
14
+ class ServerDown < StandardError; end
15
+ class ProtocolError < StandardError; end
16
+
17
+ def initialize(opts = {})
18
+ @host = opts[:host] || 'localhost'
19
+ @port = opts[:port] || DEFAULT_PORT
20
+ @user = opts[:user] || 'guest'
21
+ @pass = opts[:pass] || 'guest'
22
+ @vhost = opts[:vhost] || '/'
23
+ @insist = opts[:insist]
24
+ @status = 'NOT CONNECTED'
25
+
26
+ @multithread = opts[:multithread]
27
+ start_session
28
+ end
29
+
30
+ def send_frame(*args)
31
+ args.each do |data|
32
+ data.ticket = ticket if ticket and data.respond_to?(:ticket=)
33
+ data = data.to_frame(channel) unless data.is_a?(Frame)
34
+ data.channel = channel
35
+
36
+ log :send, data
37
+ write(data.to_s)
38
+ end
39
+ nil
40
+ end
41
+
42
+ def next_frame
43
+ frame = Frame.parse(buffer)
44
+ log :received, frame
45
+ frame
46
+ end
47
+
48
+ def next_method
49
+ next_payload
50
+ end
51
+
52
+ def next_payload
53
+ frame = next_frame
54
+ frame and frame.payload
55
+ end
56
+
57
+ def close
58
+ send_frame(
59
+ Protocol::Channel::Close.new(:reply_code => 200, :reply_text => 'bye', :method_id => 0, :class_id => 0)
60
+ )
61
+ puts "Error closing channel #{channel}" unless next_method.is_a?(Protocol::Channel::CloseOk)
62
+
63
+ self.channel = 0
64
+ send_frame(
65
+ Protocol::Connection::Close.new(:reply_code => 200, :reply_text => 'Goodbye', :class_id => 0, :method_id => 0)
66
+ )
67
+ puts "Error closing connection" unless next_method.is_a?(Protocol::Connection::CloseOk)
68
+
69
+ rescue ServerDown => e
70
+ ensure
71
+ close_socket
72
+ end
73
+
74
+ def read(*args)
75
+ send_command(:read, *args)
76
+ end
77
+
78
+ def write(*args)
79
+ send_command(:write, *args)
80
+ end
81
+
82
+ private
83
+
84
+ def buffer
85
+ @buffer ||= Buffer.new(self)
86
+ end
87
+
88
+ def send_command(cmd, *args)
89
+ begin
90
+ socket.__send__(cmd, *args)
91
+ rescue Errno::EPIPE, IOError, Errno::ECONNRESET, Errno::EINVAL => e
92
+ raise ServerDown, e.message
93
+ end
94
+ end
95
+
96
+ def socket
97
+ return @socket if @socket and not @socket.closed?
98
+
99
+ begin
100
+ # Attempt to connect.
101
+ mutex.lock if multithread?
102
+ @socket = timeout(CONNECT_TIMEOUT) do
103
+ TCPSocket.new(host, port)
104
+ end
105
+
106
+ if Socket.constants.include? 'TCP_NODELAY'
107
+ @socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
108
+ end
109
+ @status = 'CONNECTED'
110
+ rescue SocketError, SystemCallError, IOError, Timeout::Error => e
111
+ msg = e.message << " - #{@host}:#{@port}"
112
+ raise ServerDown, e.message
113
+ ensure
114
+ mutex.unlock if multithread?
115
+ end
116
+
117
+ @socket
118
+ end
119
+
120
+ def start_session
121
+ @channel = 0
122
+ write(HEADER)
123
+ write([1, 1, VERSION_MAJOR, VERSION_MINOR].pack('C4'))
124
+ raise ProtocolError, 'bad start connection' unless next_method.is_a?(Protocol::Connection::Start)
125
+
126
+ send_frame(
127
+ Protocol::Connection::StartOk.new(
128
+ {:platform => 'Ruby', :product => 'Carrot', :information => 'http://github.com/famosagle/carrot', :version => VERSION},
129
+ 'AMQPLAIN',
130
+ {:LOGIN => @user, :PASSWORD => @pass},
131
+ 'en_US'
132
+ )
133
+ )
134
+
135
+ method = next_method
136
+ raise ProtocolError, "Bad AMQP Credentials. user: #{@user}, pass: #{@pass}" if method.nil?
137
+
138
+ if method.is_a?(Protocol::Connection::Tune)
139
+ send_frame(
140
+ Protocol::Connection::TuneOk.new( :channel_max => 0, :frame_max => 131072, :heartbeat => 0)
141
+ )
142
+ end
143
+
144
+ send_frame(
145
+ Protocol::Connection::Open.new(:virtual_host => @vhost, :capabilities => '', :insist => @insist)
146
+ )
147
+ raise ProtocolError, 'bad open connection' unless next_method.is_a?(Protocol::Connection::OpenOk)
148
+
149
+ @channel = 1
150
+ send_frame(Protocol::Channel::Open.new)
151
+ raise ProtocolError, "cannot open channel #{channel}" unless next_method.is_a?(Protocol::Channel::OpenOk)
152
+
153
+ send_frame(
154
+ Protocol::Access::Request.new(:realm => '/data', :read => true, :write => true, :active => true, :passive => true)
155
+ )
156
+ method = next_method
157
+ raise ProtocolError, 'access denied' unless method.is_a?(Protocol::Access::RequestOk)
158
+ self.ticket = method.ticket
159
+ end
160
+
161
+ def multithread?
162
+ @multithread
163
+ end
164
+
165
+ def close_socket(reason=nil)
166
+ # Close the socket. The server is not considered dead.
167
+ mutex.lock if multithread?
168
+ @socket.close if @socket and not @socket.closed?
169
+ @socket = nil
170
+ @status = "NOT CONNECTED"
171
+ ensure
172
+ mutex.unlock if multithread?
173
+ end
174
+
175
+ def mutex
176
+ @mutex ||= Mutex.new
177
+ end
178
+
179
+ def log(*args)
180
+ return unless Carrot.logging?
181
+ require 'pp'
182
+ pp args
183
+ puts
184
+ end
185
+
186
+ end
187
+ end