sparqcode_bunny 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/.gitignore +10 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +15 -0
  4. data/.yardopts +9 -0
  5. data/CHANGELOG +27 -0
  6. data/Gemfile +39 -0
  7. data/LICENSE +21 -0
  8. data/README.textile +82 -0
  9. data/Rakefile +14 -0
  10. data/bunny.gemspec +43 -0
  11. data/examples/simple_08.rb +32 -0
  12. data/examples/simple_09.rb +32 -0
  13. data/examples/simple_ack_08.rb +35 -0
  14. data/examples/simple_ack_09.rb +35 -0
  15. data/examples/simple_consumer_08.rb +55 -0
  16. data/examples/simple_consumer_09.rb +55 -0
  17. data/examples/simple_fanout_08.rb +41 -0
  18. data/examples/simple_fanout_09.rb +41 -0
  19. data/examples/simple_headers_08.rb +42 -0
  20. data/examples/simple_headers_09.rb +42 -0
  21. data/examples/simple_publisher_08.rb +29 -0
  22. data/examples/simple_publisher_09.rb +29 -0
  23. data/examples/simple_topic_08.rb +61 -0
  24. data/examples/simple_topic_09.rb +61 -0
  25. data/ext/amqp-0.8.json +616 -0
  26. data/ext/amqp-0.9.1.json +388 -0
  27. data/ext/config.yml +4 -0
  28. data/ext/qparser.rb +469 -0
  29. data/lib/bunny/channel08.rb +39 -0
  30. data/lib/bunny/channel09.rb +39 -0
  31. data/lib/bunny/client08.rb +472 -0
  32. data/lib/bunny/client09.rb +374 -0
  33. data/lib/bunny/consumer.rb +35 -0
  34. data/lib/bunny/exchange08.rb +171 -0
  35. data/lib/bunny/exchange09.rb +159 -0
  36. data/lib/bunny/queue08.rb +403 -0
  37. data/lib/bunny/queue09.rb +325 -0
  38. data/lib/bunny/subscription08.rb +87 -0
  39. data/lib/bunny/subscription09.rb +87 -0
  40. data/lib/bunny/system_timer.rb +14 -0
  41. data/lib/bunny/version.rb +5 -0
  42. data/lib/bunny.rb +109 -0
  43. data/lib/qrack/amq-client-url.rb +165 -0
  44. data/lib/qrack/channel.rb +20 -0
  45. data/lib/qrack/client.rb +235 -0
  46. data/lib/qrack/errors.rb +5 -0
  47. data/lib/qrack/protocol/protocol08.rb +134 -0
  48. data/lib/qrack/protocol/protocol09.rb +135 -0
  49. data/lib/qrack/protocol/spec08.rb +828 -0
  50. data/lib/qrack/protocol/spec09.rb +524 -0
  51. data/lib/qrack/qrack08.rb +20 -0
  52. data/lib/qrack/qrack09.rb +20 -0
  53. data/lib/qrack/queue.rb +40 -0
  54. data/lib/qrack/subscription.rb +112 -0
  55. data/lib/qrack/transport/buffer08.rb +278 -0
  56. data/lib/qrack/transport/buffer09.rb +280 -0
  57. data/lib/qrack/transport/frame08.rb +117 -0
  58. data/lib/qrack/transport/frame09.rb +97 -0
  59. data/spec/spec_08/bunny_spec.rb +77 -0
  60. data/spec/spec_08/connection_spec.rb +25 -0
  61. data/spec/spec_08/exchange_spec.rb +173 -0
  62. data/spec/spec_08/queue_spec.rb +235 -0
  63. data/spec/spec_09/amqp_url_spec.rb +19 -0
  64. data/spec/spec_09/bunny_spec.rb +76 -0
  65. data/spec/spec_09/connection_spec.rb +29 -0
  66. data/spec/spec_09/exchange_spec.rb +173 -0
  67. data/spec/spec_09/queue_spec.rb +248 -0
  68. metadata +151 -0
@@ -0,0 +1,165 @@
1
+ # encoding: utf-8
2
+
3
+ ################################################################
4
+ # DO NOT EDIT THIS FILE ! #
5
+ # The file comes from https://github.com/ruby-amqp/amq-client, #
6
+ # it's located in lib/amq/client/settings.rb, so if you want #
7
+ # to make some changes, please do them in the amq-client repo. #
8
+ ################################################################
9
+
10
+ # TODO: When we start to work on porting Bunny on AMQ Client,
11
+ # this file will become obsolete.
12
+
13
+ require "uri"
14
+
15
+ module AMQ
16
+ module Client
17
+ # @see AMQ::Client::Settings.configure
18
+ module Settings
19
+ # @private
20
+ AMQP_PORTS = {"amqp" => 5672, "amqps" => 5671}.freeze
21
+
22
+ # @private
23
+ AMQPS = "amqps".freeze
24
+
25
+ # Default connection settings used by AMQ clients
26
+ #
27
+ # @see AMQ::Client::Settings.configure
28
+ def self.default
29
+ @default ||= {
30
+ # server
31
+ :host => "127.0.0.1",
32
+ :port => AMQ::Protocol::DEFAULT_PORT,
33
+
34
+ # login
35
+ :user => "guest",
36
+ :pass => "guest",
37
+ :vhost => "/",
38
+
39
+ # connection timeout
40
+ :timeout => nil,
41
+
42
+ # logging
43
+ :logging => false,
44
+
45
+ # ssl
46
+ :ssl => false,
47
+
48
+ # broker
49
+ # if you want to load broker-specific extensions
50
+ :broker => nil,
51
+
52
+ :frame_max => 131072
53
+ }
54
+ end
55
+
56
+
57
+ def self.client_properties
58
+ @client_properties ||= {
59
+ :platform => ::RUBY_DESCRIPTION,
60
+ :product => "AMQ Client",
61
+ :information => "http://github.com/ruby-amqp/amq-client",
62
+ :version => AMQ::Client::VERSION
63
+ }
64
+ end
65
+
66
+
67
+ # Merges given configuration parameters with defaults and returns
68
+ # the result.
69
+ #
70
+ # @param [Hash] Configuration parameters to use.
71
+ #
72
+ # @option settings [String] :host ("127.0.0.1") Hostname AMQ broker runs on.
73
+ # @option settings [String] :port (5672) Port AMQ broker listens on.
74
+ # @option settings [String] :vhost ("/") Virtual host to use.
75
+ # @option settings [String] :user ("guest") Username to use for authentication.
76
+ # @option settings [String] :pass ("guest") Password to use for authentication.
77
+ # @option settings [String] :ssl (false) Should be use TLS (SSL) for connection?
78
+ # @option settings [String] :timeout (nil) Connection timeout.
79
+ # @option settings [String] :logging (false) Turns logging on or off.
80
+ # @option settings [String] :broker (nil) Broker name (use if you intend to use broker-specific features).
81
+ # @option settings [Fixnum] :frame_max (131072) Maximum frame size to use. If broker cannot support frames this large, broker's maximum value will be used instead.
82
+ #
83
+ # @return [Hash] Merged configuration parameters.
84
+ def self.configure(settings = nil)
85
+ case settings
86
+ when Hash then
87
+ if username = settings.delete(:username)
88
+ settings[:user] ||= username
89
+ end
90
+
91
+ if password = settings.delete(:password)
92
+ settings[:pass] ||= password
93
+ end
94
+
95
+
96
+ self.default.merge(settings)
97
+ when String then
98
+ settings = self.parse_amqp_url(settings)
99
+ self.default.merge(settings)
100
+ when NilClass then
101
+ self.default
102
+ end
103
+ end
104
+
105
+ # Parses AMQP connection URI and returns its components as a hash.
106
+ #
107
+ # h2. vhost naming schemes
108
+ #
109
+ # It is convenient to be able to specify the AMQP connection
110
+ # parameters as a URI string, and various "amqp" URI schemes
111
+ # exist. Unfortunately, there is no standard for these URIs, so
112
+ # while the schemes share the basic idea, they differ in some
113
+ # details. This implementation aims to encourage URIs that work
114
+ # as widely as possible.
115
+ #
116
+ # The URI scheme should be "amqp", or "amqps" if SSL is required.
117
+ #
118
+ # The host, port, username and password are represented in the
119
+ # authority component of the URI in the same way as in http URIs.
120
+ #
121
+ # The vhost is obtained from the first segment of the path, with the
122
+ # leading slash removed. The path should contain only a single
123
+ # segment (i.e, the only slash in it should be the leading one).
124
+ # If the vhost is to include slashes or other reserved URI
125
+ # characters, these should be percent-escaped.
126
+ #
127
+ # @example How vhost is parsed
128
+ #
129
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com") # => vhost is nil, so default (/) will be used
130
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/") # => vhost is an empty string
131
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/%2Fvault") # => vhost is /vault
132
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/production") # => vhost is production
133
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/a.b.c") # => vhost is a.b.c
134
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/foo/bar") # => ArgumentError
135
+ #
136
+ #
137
+ # @param [String] connection_string AMQP connection URI, à la JDBC connection string. For example: amqp://bus.megacorp.internal:5877.
138
+ # @return [Hash] Connection parameters (:username, :password, :vhost, :host, :port, :ssl)
139
+ #
140
+ # @raise [ArgumentError] When connection URI schema is not amqp or amqps, or the path contains multiple segments
141
+ #
142
+ # @see http://bit.ly/ks8MXK Connecting to The Broker documentation guide
143
+ # @api public
144
+ def self.parse_amqp_url(connection_string)
145
+ uri = URI.parse(connection_string)
146
+ raise ArgumentError.new("Connection URI must use amqp or amqps schema (example: amqp://bus.megacorp.internal:5766), learn more at http://bit.ly/ks8MXK") unless %w{amqp amqps}.include?(uri.scheme)
147
+
148
+ opts = {}
149
+
150
+ opts[:scheme] = uri.scheme
151
+ opts[:user] = URI.unescape(uri.user) if uri.user
152
+ opts[:pass] = URI.unescape(uri.password) if uri.password
153
+ opts[:host] = uri.host if uri.host
154
+ opts[:port] = uri.port || AMQ::Client::Settings::AMQP_PORTS[uri.scheme]
155
+ opts[:ssl] = uri.scheme == AMQ::Client::Settings::AMQPS
156
+ if uri.path =~ %r{^/(.*)}
157
+ raise ArgumentError.new("#{uri} has multiple-segment path; please percent-encode any slashes in the vhost name (e.g. /production => %2Fproduction). Learn more at http://bit.ly/amqp-gem-and-connection-uris") if $1.index('/')
158
+ opts[:vhost] = URI.unescape($1)
159
+ end
160
+
161
+ opts
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module Qrack
4
+ # Channel ancestor class
5
+ class Channel
6
+
7
+ attr_accessor :number, :active, :frame_buffer
8
+ attr_reader :client
9
+
10
+ def initialize(client)
11
+ @frame_buffer = []
12
+ @client = client
13
+ @number = client.channels.size
14
+ @active = false
15
+ client.channels[@number] = self
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,235 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ # try loading AMQ::Client::Setttings form the amq client gem, if it has been already loaded
5
+ # this avoids "warning: already initialized constant AMQP_PORTS"
6
+ require "amq/client/settings"
7
+ rescue LoadError
8
+ require "qrack/amq-client-url"
9
+ end
10
+
11
+ module Qrack
12
+
13
+ class ClientTimeout < Timeout::Error; end
14
+ class ConnectionTimeout < Timeout::Error; end
15
+ class FrameTimeout < Timeout::Error; end
16
+
17
+ # Client ancestor class
18
+ class Client
19
+
20
+ CONNECT_TIMEOUT = 5.0
21
+ RETRY_DELAY = 10.0
22
+
23
+ attr_reader :status, :host, :vhost, :port, :logging, :spec, :heartbeat
24
+ attr_accessor :channel, :logfile, :exchanges, :queues, :channels, :message_in, :message_out, :connecting
25
+
26
+ # Temporary hack to make Bunny 0.7 work with port number in AMQP URL.
27
+ # This is not necessary on Bunny 0.8 as it removes support of AMQP 0.8.
28
+ attr_reader :__opts__
29
+
30
+ def initialize(connection_string_or_opts = Hash.new, opts = Hash.new)
31
+ opts = case connection_string_or_opts
32
+ when String then
33
+ AMQ::Client::Settings.parse_amqp_url(connection_string_or_opts)
34
+ when Hash then
35
+ connection_string_or_opts
36
+ else
37
+ Hash.new
38
+ end.merge(opts)
39
+
40
+ # Temporary hack to make Bunny 0.7 work with port number in AMQP URL.
41
+ # This is not necessary on Bunny 0.8 as it removes support of AMQP 0.8.
42
+ @__opts__ = opts
43
+
44
+
45
+ @host = opts[:host] || 'localhost'
46
+ @user = opts[:user] || 'guest'
47
+ @pass = opts[:pass] || 'guest'
48
+ @vhost = opts[:vhost] || '/'
49
+ @logfile = opts[:logfile] || nil
50
+ @logging = opts[:logging] || false
51
+ @ssl = opts[:ssl] || false
52
+ @verify_ssl = opts[:verify_ssl].nil? || opts[:verify_ssl]
53
+ @status = :not_connected
54
+ @frame_max = opts[:frame_max] || 131072
55
+ @channel_max = opts[:channel_max] || 0
56
+ @heartbeat = opts[:heartbeat] || 0
57
+ @connect_timeout = opts[:connect_timeout] || CONNECT_TIMEOUT
58
+ @read_write_timeout = opts[:socket_timeout]
59
+ @read_write_timeout = nil if @read_write_timeout == 0
60
+ @disconnect_timeout = @read_write_timeout || @connect_timeout
61
+ @logger = nil
62
+ create_logger if @logging
63
+ @message_in = false
64
+ @message_out = false
65
+ @connecting = false
66
+ @channels ||= []
67
+ # Create channel 0
68
+ @channel = create_channel()
69
+ @exchanges ||= {}
70
+ @queues ||= {}
71
+ end
72
+
73
+
74
+ # Closes all active communication channels and connection. If an error occurs a @Bunny::ProtocolError@ is raised. If successful, @Client.status@ is set to @:not_connected@.
75
+
76
+ # @return [Symbol] @:not_connected@ if successful.
77
+ def close
78
+ return if @socket.nil? || @socket.closed?
79
+
80
+ # Close all active channels
81
+ channels.each do |c|
82
+ Bunny::Timer::timeout(@disconnect_timeout) { c.close } if c.open?
83
+ end
84
+
85
+ # Close connection to AMQP server
86
+ Bunny::Timer::timeout(@disconnect_timeout) { close_connection }
87
+
88
+ rescue Exception
89
+ # http://cheezburger.com/Asset/View/4033311488
90
+ ensure
91
+ # Clear the channels
92
+ @channels = []
93
+
94
+ # Create channel 0
95
+ @channel = create_channel()
96
+
97
+ # Close TCP Socket
98
+ close_socket
99
+ end
100
+
101
+ alias stop close
102
+
103
+ def connected?
104
+ status == :connected
105
+ end
106
+
107
+ def connecting?
108
+ connecting
109
+ end
110
+
111
+ def logging=(bool)
112
+ @logging = bool
113
+ create_logger if @logging
114
+ end
115
+
116
+ def next_payload(options = {})
117
+ res = next_frame(options)
118
+ res.payload if res
119
+ end
120
+
121
+ alias next_method next_payload
122
+
123
+ def read(*args)
124
+ send_command(:read, *args)
125
+ # Got a SIGINT while waiting; give any traps a chance to run
126
+ rescue Errno::EINTR
127
+ retry
128
+ end
129
+
130
+ # Checks to see whether or not an undeliverable message has been returned as a result of a publish
131
+ # with the <tt>:immediate</tt> or <tt>:mandatory</tt> options.
132
+
133
+ # @param [Hash] opts Options.
134
+ # @option opts [Numeric] :timeout (0.1) The method will wait for a return message until this timeout interval is reached.
135
+ # @return [Hash] @{:header => nil, :payload => :no_return, :return_details => nil}@ if message is not returned before timeout. @{:header, :return_details, :payload}@ if message is returned. @:return_details@ is a hash @{:reply_code, :reply_text, :exchange, :routing_key}@.
136
+ def returned_message(opts = {})
137
+
138
+ begin
139
+ frame = next_frame(:timeout => opts[:timeout] || 0.1)
140
+ rescue Qrack::FrameTimeout
141
+ return {:header => nil, :payload => :no_return, :return_details => nil}
142
+ end
143
+
144
+ method = frame.payload
145
+ header = next_payload
146
+
147
+ # If maximum frame size is smaller than message payload body then message
148
+ # will have a message header and several message bodies
149
+ msg = ''
150
+ while msg.length < header.size
151
+ msg << next_payload
152
+ end
153
+
154
+ # Return the message and related info
155
+ {:header => header, :payload => msg, :return_details => method.arguments}
156
+ end
157
+
158
+ def switch_channel(chann)
159
+ if (0...channels.size).include? chann
160
+ @channel = channels[chann]
161
+ chann
162
+ else
163
+ raise RuntimeError, "Invalid channel number - #{chann}"
164
+ end
165
+ end
166
+
167
+ def write(*args)
168
+ send_command(:write, *args)
169
+ end
170
+
171
+ private
172
+
173
+ def close_socket(reason=nil)
174
+ # Close the socket. The server is not considered dead.
175
+ @socket.close if @socket and not @socket.closed?
176
+ @socket = nil
177
+ @status = :not_connected
178
+ end
179
+
180
+ def create_logger
181
+ @logfile ? @logger = Logger.new("#{logfile}") : @logger = Logger.new(STDOUT)
182
+ @logger.level = Logger::INFO
183
+ @logger.datetime_format = "%Y-%m-%d %H:%M:%S"
184
+ end
185
+
186
+ def send_command(cmd, *args)
187
+ begin
188
+ raise Bunny::ConnectionError, 'No connection - socket has not been created' if !@socket
189
+ if @read_write_timeout
190
+ Bunny::Timer::timeout(@read_write_timeout, Qrack::ClientTimeout) do
191
+ @socket.__send__(cmd, *args)
192
+ end
193
+ else
194
+ @socket.__send__(cmd, *args)
195
+ end
196
+ rescue Errno::EPIPE, Errno::EAGAIN, Qrack::ClientTimeout, IOError => e
197
+ # Ensure we close the socket when we are down to prevent further
198
+ # attempts to write to a closed socket
199
+ close_socket
200
+ raise Bunny::ServerDownError, e.message
201
+ end
202
+ end
203
+
204
+ def socket
205
+ return @socket if @socket and (@status == :connected) and not @socket.closed?
206
+
207
+ begin
208
+ # Attempt to connect.
209
+ @socket = Bunny::Timer::timeout(@connect_timeout, ConnectionTimeout) do
210
+ TCPSocket.new(host, port)
211
+ end
212
+
213
+ if Socket.constants.include?('TCP_NODELAY') || Socket.constants.include?(:TCP_NODELAY)
214
+ @socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
215
+ end
216
+
217
+ if @ssl
218
+ require 'openssl' unless defined? OpenSSL::SSL
219
+ @socket = OpenSSL::SSL::SSLSocket.new(@socket)
220
+ @socket.sync_close = true
221
+ @socket.connect
222
+ @socket.post_connection_check(host) if @verify_ssl
223
+ @socket
224
+ end
225
+ rescue => e
226
+ @status = :not_connected
227
+ raise Bunny::ServerDownError, e.message
228
+ end
229
+
230
+ @socket
231
+ end
232
+
233
+ end
234
+
235
+ end
@@ -0,0 +1,5 @@
1
+ module Qrack
2
+ # Errors
3
+ class BufferOverflowError < StandardError; end
4
+ class InvalidTypeError < StandardError; end
5
+ end
@@ -0,0 +1,134 @@
1
+ # encoding: utf-8
2
+
3
+ module Qrack
4
+ module Protocol
5
+ #:stopdoc:
6
+ class Class::Method
7
+ def initialize *args
8
+ opts = args.pop if args.last.is_a? Hash
9
+ opts ||= {}
10
+
11
+ if args.size == 1 and args.first.is_a? Transport::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 = Transport::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
+ Transport::Method.new(self, channel)
62
+ end
63
+ end
64
+
65
+ class Header
66
+ def initialize *args
67
+ opts = args.pop if args.last.is_a? Hash
68
+ opts ||= {}
69
+
70
+ first = args.shift
71
+
72
+ if first.is_a? ::Class and first.ancestors.include? Protocol::Class
73
+ @klass = first
74
+ @size = args.shift || 0
75
+ @weight = args.shift || 0
76
+ @properties = opts
77
+
78
+ elsif first.is_a? Transport::Buffer or first.is_a? String
79
+ buf = first
80
+ buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
81
+
82
+ @klass = Protocol.classes[buf.read(:short)]
83
+ @weight = buf.read(:short)
84
+ @size = buf.read(:longlong)
85
+
86
+ props = buf.read(:properties, *klass.properties.map{|type,_| type })
87
+ @properties = Hash[*klass.properties.map{|_,name| name }.zip(props).reject{|k,v| v.nil? }.flatten]
88
+
89
+ else
90
+ raise ArgumentError, 'Invalid argument'
91
+ end
92
+
93
+ end
94
+ attr_accessor :klass, :size, :weight, :properties
95
+
96
+ def to_binary
97
+ buf = Transport::Buffer.new
98
+ buf.write :short, klass.id
99
+ buf.write :short, weight # XXX rabbitmq only supports weight == 0
100
+ buf.write :longlong, size
101
+ buf.write :properties, (klass.properties.map do |type, name|
102
+ [ type, properties[name] || properties[name.to_s] ]
103
+ end)
104
+ buf.rewind
105
+ buf
106
+ end
107
+
108
+ def to_s
109
+ to_binary.to_s
110
+ end
111
+
112
+ def to_frame channel = 0
113
+ Transport::Header.new(self, channel)
114
+ end
115
+
116
+ def == header
117
+ [ :klass, :size, :weight, :properties ].inject(true) do |eql, field|
118
+ eql and __send__(field) == header.__send__(field)
119
+ end
120
+ end
121
+
122
+ def method_missing meth, *args, &blk
123
+ @properties.has_key?(meth) || @klass.properties.find{|_,name| name == meth } ? @properties[meth] : super
124
+ end
125
+ end
126
+
127
+ def self.parse buf
128
+ buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
129
+ class_id, method_id = buf.read(:short, :short)
130
+ classes[class_id].methods[method_id].new(buf)
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,135 @@
1
+ # encoding: utf-8
2
+
3
+ module Qrack
4
+ module Protocol09
5
+ #:stopdoc:
6
+ class Class::Method
7
+ def initialize *args
8
+ opts = args.pop if args.last.is_a? Hash
9
+ opts ||= {}
10
+
11
+ if args.size == 1 and args.first.is_a? Transport09::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 = Transport09::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
+ Transport09::Method.new(self, channel)
62
+ end
63
+ end
64
+
65
+ class Header
66
+ def initialize *args
67
+ opts = args.pop if args.last.is_a? Hash
68
+ opts ||= {}
69
+
70
+ first = args.shift
71
+
72
+ if first.is_a? ::Class and first.ancestors.include? Protocol09::Class
73
+ @klass = first
74
+ @size = args.shift || 0
75
+ @weight = args.shift || 0
76
+ @properties = opts
77
+
78
+ elsif first.is_a? Transport09::Buffer or first.is_a? String
79
+ buf = first
80
+ buf = Transport09::Buffer.new(buf) unless buf.is_a? Transport09::Buffer
81
+
82
+ @klass = Protocol09.classes[buf.read(:short)]
83
+ @weight = buf.read(:short)
84
+ @size = buf.read(:longlong)
85
+
86
+ props = buf.read(:properties, *klass.properties.map{|type,_| type })
87
+ @properties = Hash[*klass.properties.map{|_,name| name }.zip(props).reject{|k,v| v.nil? }.flatten]
88
+
89
+ else
90
+ raise ArgumentError, 'Invalid argument'
91
+ end
92
+
93
+ end
94
+ attr_accessor :klass, :size, :weight, :properties
95
+
96
+ def to_binary
97
+ buf = Transport09::Buffer.new
98
+ buf.write :short, klass.id
99
+ buf.write :short, weight # XXX rabbitmq only supports weight == 0
100
+ buf.write :longlong, size
101
+ buf.write :properties, (klass.properties.map do |type, name|
102
+ [ type, properties[name] || properties[name.to_s] ]
103
+ end)
104
+ buf.rewind
105
+ buf
106
+ end
107
+
108
+ def to_s
109
+ to_binary.to_s
110
+ end
111
+
112
+ def to_frame channel = 0
113
+ Transport09::Header.new(self, channel)
114
+ end
115
+
116
+ def == header
117
+ [ :klass, :size, :weight, :properties ].inject(true) do |eql, field|
118
+ eql and __send__(field) == header.__send__(field)
119
+ end
120
+ end
121
+
122
+ def method_missing meth, *args, &blk
123
+ @properties.has_key?(meth) || @klass.properties.find{|_,name| name == meth } ? @properties[meth] :
124
+ super
125
+ end
126
+ end
127
+
128
+ def self.parse buf
129
+ buf = Transport09::Buffer.new(buf) unless buf.is_a? Transport09::Buffer
130
+ class_id, method_id = buf.read(:short, :short)
131
+ classes[class_id].methods[method_id].new(buf)
132
+ end
133
+
134
+ end
135
+ end