sparqcode_bunny 0.0.1

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.
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