ivanvanderbyl-amqp 0.6.13.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 (51) hide show
  1. data/.gitignore +4 -0
  2. data/HISTORY +27 -0
  3. data/README.md +169 -0
  4. data/Rakefile +24 -0
  5. data/TODO +32 -0
  6. data/VERSION +1 -0
  7. data/doc/EXAMPLE_01_PINGPONG +2 -0
  8. data/doc/EXAMPLE_02_CLOCK +2 -0
  9. data/doc/EXAMPLE_03_STOCKS +2 -0
  10. data/doc/EXAMPLE_04_MULTICLOCK +2 -0
  11. data/doc/EXAMPLE_05_ACK +2 -0
  12. data/doc/EXAMPLE_05_POP +2 -0
  13. data/doc/EXAMPLE_06_HASHTABLE +2 -0
  14. data/lib/amqp.rb +110 -0
  15. data/lib/amqp/buffer.rb +270 -0
  16. data/lib/amqp/client.rb +225 -0
  17. data/lib/amqp/frame.rb +66 -0
  18. data/lib/amqp/protocol.rb +161 -0
  19. data/lib/amqp/server.rb +99 -0
  20. data/lib/amqp/spec.rb +832 -0
  21. data/lib/amqp/version.rb +6 -0
  22. data/lib/ext/blankslate.rb +7 -0
  23. data/lib/ext/em.rb +8 -0
  24. data/lib/ext/emfork.rb +69 -0
  25. data/lib/mq.rb +875 -0
  26. data/lib/mq/exchange.rb +351 -0
  27. data/lib/mq/header.rb +33 -0
  28. data/lib/mq/logger.rb +89 -0
  29. data/lib/mq/queue.rb +455 -0
  30. data/lib/mq/rpc.rb +100 -0
  31. data/old/README +30 -0
  32. data/old/Rakefile +12 -0
  33. data/old/amqp-0.8.json +606 -0
  34. data/old/amqp_spec.rb +796 -0
  35. data/old/amqpc.rb +695 -0
  36. data/old/codegen.rb +148 -0
  37. data/protocol/amqp-0.8.json +617 -0
  38. data/protocol/amqp-0.8.xml +3908 -0
  39. data/protocol/codegen.rb +173 -0
  40. data/protocol/doc.txt +281 -0
  41. data/research/api.rb +88 -0
  42. data/research/primes-forked.rb +63 -0
  43. data/research/primes-processes.rb +135 -0
  44. data/research/primes-threaded.rb +49 -0
  45. data/tasks/common.rake +18 -0
  46. data/tasks/doc.rake +14 -0
  47. data/tasks/gem.rake +40 -0
  48. data/tasks/git.rake +34 -0
  49. data/tasks/spec.rake +15 -0
  50. data/tasks/version.rake +71 -0
  51. metadata +158 -0
@@ -0,0 +1,351 @@
1
+ class MQ
2
+ # An Exchange acts as an ingress point for all published messages. An
3
+ # exchange may also be described as a router or a matcher. Every
4
+ # published message is received by an exchange which, depending on its
5
+ # type (described below), determines how to deliver the message.
6
+ #
7
+ # It determines the next delivery hop by examining the bindings associated
8
+ # with the exchange.
9
+ #
10
+ # There are three (3) supported Exchange types: direct, fanout and topic.
11
+ #
12
+ # As part of the standard, the server _must_ predeclare the direct exchange
13
+ # 'amq.direct' and the fanout exchange 'amq.fanout' (all exchange names
14
+ # starting with 'amq.' are reserved). Attempts to declare an exchange using
15
+ # 'amq.' as the name will raise an MQ:Error and fail. In practice these
16
+ # default exchanges are never used directly by client code.
17
+ #
18
+ # These predececlared exchanges are used when the client code declares
19
+ # an exchange without a name. In these cases the library will use
20
+ # the default exchange for publishing the messages.
21
+ #
22
+ class Exchange
23
+ include AMQP
24
+
25
+ # Defines, intializes and returns an Exchange to act as an ingress
26
+ # point for all published messages.
27
+ #
28
+ # There are three (3) supported Exchange types: direct, fanout and topic.
29
+ #
30
+ # As part of the standard, the server _must_ predeclare the direct exchange
31
+ # 'amq.direct' and the fanout exchange 'amq.fanout' (all exchange names
32
+ # starting with 'amq.' are reserved). Attempts to declare an exchange using
33
+ # 'amq.' as the name will raise an MQ:Error and fail. In practice these
34
+ # default exchanges are never used directly by client code.
35
+ #
36
+ # == Direct
37
+ # A direct exchange is useful for 1:1 communication between a publisher and
38
+ # subscriber. Messages are routed to the queue with a binding that shares
39
+ # the same name as the exchange. Alternately, the messages are routed to
40
+ # the bound queue that shares the same name as the routing key used for
41
+ # defining the exchange. This exchange type does not honor the :key option
42
+ # when defining a new instance with a name. It _will_ honor the :key option
43
+ # if the exchange name is the empty string. This is because an exchange
44
+ # defined with the empty string uses the default pre-declared exchange
45
+ # called 'amq.direct'. In this case it needs to use :key to do its matching.
46
+ #
47
+ # # exchange is named 'foo'
48
+ # exchange = MQ::Exchange.new(MQ.new, :direct, 'foo')
49
+ #
50
+ # # or, the exchange can use the default name (amq.direct) and perform
51
+ # # routing comparisons using the :key
52
+ # exchange = MQ::Exchange.new(MQ.new, :direct, "", :key => 'foo')
53
+ # exchange.publish('some data') # will be delivered to queue bound to 'foo'
54
+ #
55
+ # queue = MQ::Queue.new(MQ.new, 'foo')
56
+ # # can receive data since the queue name and the exchange key match exactly
57
+ # queue.pop { |data| puts "received data [#{data}]" }
58
+ #
59
+ # == Fanout
60
+ # A fanout exchange is useful for 1:N communication where one publisher
61
+ # feeds multiple subscribers. Like direct exchanges, messages published
62
+ # to a fanout exchange are delivered to queues whose name matches the
63
+ # exchange name (or are bound to that exchange name). Each queue gets
64
+ # its own copy of the message.
65
+ #
66
+ # Like the direct exchange type, this exchange type does not honor the
67
+ # :key option when defining a new instance with a name. It _will_ honor
68
+ # the :key option if the exchange name is the empty string. Fanout exchanges
69
+ # defined with the empty string as the name use the default 'amq.fanout'.
70
+ # In this case it needs to use :key to do its matching.
71
+ #
72
+ # EM.run do
73
+ # clock = MQ::Exchange.new(MQ.new, :fanout, 'clock')
74
+ # EM.add_periodic_timer(1) do
75
+ # puts "\npublishing #{time = Time.now}"
76
+ # clock.publish(Marshal.dump(time))
77
+ # end
78
+ #
79
+ # # one way of defining a queue
80
+ # amq = MQ::Queue.new(MQ.new, 'every second')
81
+ # amq.bind(MQ.fanout('clock')).subscribe do |time|
82
+ # puts "every second received #{Marshal.load(time)}"
83
+ # end
84
+ #
85
+ # # defining a queue using the convenience method
86
+ # # note the string passed to #bind
87
+ # MQ.queue('every 5 seconds').bind('clock').subscribe do |time|
88
+ # time = Marshal.load(time)
89
+ # puts "every 5 seconds received #{time}" if time.strftime('%S').to_i%5 == 0
90
+ # end
91
+ # end
92
+ #
93
+ # == Topic
94
+ # A topic exchange allows for messages to be published to an exchange
95
+ # tagged with a specific routing key. The Exchange uses the routing key
96
+ # to determine which queues to deliver the message. Wildcard matching
97
+ # is allowed. The topic must be declared using dot notation to separate
98
+ # each subtopic.
99
+ #
100
+ # This is the only exchange type to honor the :key parameter.
101
+ #
102
+ # As part of the AMQP standard, each server _should_ predeclare a topic
103
+ # exchange called 'amq.topic' (this is not required by the standard).
104
+ #
105
+ # The classic example is delivering market data. When publishing market
106
+ # data for stocks, we may subdivide the stream based on 2
107
+ # characteristics: nation code and trading symbol. The topic tree for
108
+ # Apple Computer would look like:
109
+ # 'stock.us.aapl'
110
+ # For a foreign stock, it may look like:
111
+ # 'stock.de.dax'
112
+ #
113
+ # When publishing data to the exchange, bound queues subscribing to the
114
+ # exchange indicate which data interests them by passing a routing key
115
+ # for matching against the published routing key.
116
+ #
117
+ # EM.run do
118
+ # exch = MQ::Exchange.new(MQ.new, :topic, "stocks")
119
+ # keys = ['stock.us.aapl', 'stock.de.dax']
120
+ #
121
+ # EM.add_periodic_timer(1) do # every second
122
+ # puts
123
+ # exch.publish(10+rand(10), :routing_key => keys[rand(2)])
124
+ # end
125
+ #
126
+ # # match against one dot-separated item
127
+ # MQ.queue('us stocks').bind(exch, :key => 'stock.us.*').subscribe do |price|
128
+ # puts "us stock price [#{price}]"
129
+ # end
130
+ #
131
+ # # match against multiple dot-separated items
132
+ # MQ.queue('all stocks').bind(exch, :key => 'stock.#').subscribe do |price|
133
+ # puts "all stocks: price [#{price}]"
134
+ # end
135
+ #
136
+ # # require exact match
137
+ # MQ.queue('only dax').bind(exch, :key => 'stock.de.dax').subscribe do |price|
138
+ # puts "dax price [#{price}]"
139
+ # end
140
+ # end
141
+ #
142
+ # For matching, the '*' (asterisk) wildcard matches against one
143
+ # dot-separated item only. The '#' wildcard (hash or pound symbol)
144
+ # matches against 0 or more dot-separated items. If none of these
145
+ # symbols are used, the exchange performs a comparison looking for an
146
+ # exact match.
147
+ #
148
+ # == Options
149
+ # * :passive => true | false (default false)
150
+ # If set, the server will not create the exchange if it does not
151
+ # already exist. The client can use this to check whether an exchange
152
+ # exists without modifying the server state.
153
+ #
154
+ # * :durable => true | false (default false)
155
+ # If set when creating a new exchange, the exchange will be marked as
156
+ # durable. Durable exchanges remain active when a server restarts.
157
+ # Non-durable exchanges (transient exchanges) are purged if/when a
158
+ # server restarts.
159
+ #
160
+ # A transient exchange (the default) is stored in memory-only
161
+ # therefore it is a good choice for high-performance and low-latency
162
+ # message publishing.
163
+ #
164
+ # Durable exchanges cause all messages to be written to non-volatile
165
+ # backing store (i.e. disk) prior to routing to any bound queues.
166
+ #
167
+ # * :auto_delete => true | false (default false)
168
+ # If set, the exchange is deleted when all queues have finished
169
+ # using it. The server waits for a short period of time before
170
+ # determining the exchange is unused to give time to the client code
171
+ # to bind a queue to it.
172
+ #
173
+ # If the exchange has been previously declared, this option is ignored
174
+ # on subsequent declarations.
175
+ #
176
+ # * :internal => true | false (default false)
177
+ # If set, the exchange may not be used directly by publishers, but
178
+ # only when bound to other exchanges. Internal exchanges are used to
179
+ # construct wiring that is not visible to applications.
180
+ #
181
+ # * :nowait => true | false (default true)
182
+ # If set, the server will not respond to the method. The client should
183
+ # not wait for a reply method. If the server could not complete the
184
+ # method it will raise a channel or connection exception.
185
+ #
186
+ # * :no_declare => true | false (default false)
187
+ # If set, the exchange will not be declared to the
188
+ # AMQP broker at instantiation-time. This allows the AMQP
189
+ # client to send messages to exchanges that were
190
+ # already declared by someone else, e.g. if the client
191
+ # does not have sufficient privilege to declare (create)
192
+ # an exchange. Use with caution, as binding to an exchange
193
+ # with the no-declare option causes your system to become
194
+ # sensitive to the ordering of clients' actions!
195
+ #
196
+ # == Exceptions
197
+ # Doing any of these activities are illegal and will raise MQ:Error.
198
+ # * redeclare an already-declared exchange to a different type
199
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
200
+ #
201
+ def initialize mq, type, name, opts = {}
202
+ @mq = mq
203
+ @type, @name, @opts = type, name, opts
204
+ @mq.exchanges[@name = name] ||= self
205
+ @key = opts[:key]
206
+
207
+ unless name == "amq.#{type}" or name == '' or opts[:no_declare]
208
+ @mq.callback {
209
+ @mq.send Protocol::Exchange::Declare.new({:exchange => name,
210
+ :type => type,
211
+ :nowait => true}.merge(opts))
212
+ }
213
+ end
214
+ end
215
+
216
+ attr_reader :name, :type, :key
217
+
218
+ # This method publishes a staged file message to a specific exchange.
219
+ # The file message will be routed to queues as defined by the exchange
220
+ # configuration and distributed to any active consumers when the
221
+ # transaction, if any, is committed.
222
+ #
223
+ # exchange = MQ.direct('name', :key => 'foo.bar')
224
+ # exchange.publish("some data")
225
+ #
226
+ # The method takes several hash key options which modify the behavior or
227
+ # lifecycle of the message.
228
+ #
229
+ # * :routing_key => 'string'
230
+ #
231
+ # Specifies the routing key for the message. The routing key is
232
+ # used for routing messages depending on the exchange configuration.
233
+ #
234
+ # * :mandatory => true | false (default false)
235
+ #
236
+ # This flag tells the server how to react if the message cannot be
237
+ # routed to a queue. If this flag is set, the server will return an
238
+ # unroutable message with a Return method. If this flag is zero, the
239
+ # server silently drops the message.
240
+ #
241
+ # * :immediate => true | false (default false)
242
+ #
243
+ # This flag tells the server how to react if the message cannot be
244
+ # routed to a queue consumer immediately. If this flag is set, the
245
+ # server will return an undeliverable message with a Return method.
246
+ # If this flag is zero, the server will queue the message, but with
247
+ # no guarantee that it will ever be consumed.
248
+ #
249
+ # * :persistent
250
+ # True or False. When true, this message will remain in the queue until
251
+ # it is consumed (if the queue is durable). When false, the message is
252
+ # lost if the server restarts and the queue is recreated.
253
+ #
254
+ # For high-performance and low-latency, set :persistent => false so the
255
+ # message stays in memory and is never persisted to non-volatile (slow)
256
+ # storage.
257
+ #
258
+ # The following standard AMQP header values can also be set as options:
259
+ # :content_type (default application/octet-stream)
260
+ # :delivery_mode (set using :persistent)
261
+ # :priority
262
+ # :reply_to
263
+ # :content_encoding
264
+ # :application_headers
265
+ # :priority (default 0)
266
+ # :correlation_id
267
+ # :reply_to
268
+ # :expiration
269
+ # :message_id
270
+ # :timestamp
271
+ # :type
272
+ # :user_id
273
+ # :app_id
274
+ # :cluster_id
275
+ #
276
+ # TODO: - breaks with header values that are ruby objects (convert to strings?)
277
+ #
278
+ def publish data, opts = {}
279
+ raise MQ::Error, "No connection to broker, unable to publish #{data} with #{opts}" unless @mq.connected?
280
+
281
+ @mq.callback {
282
+ out = []
283
+
284
+ out << Protocol::Basic::Publish.new({:exchange => name,
285
+ :routing_key => opts[:key] || @key}.merge(opts))
286
+ headers = {
287
+ :content_type => 'application/octet-stream',
288
+ :delivery_mode => (opts[:persistent] ? 2 : 1),
289
+ :priority => 0,
290
+ :reply_to => nil,
291
+ :content_encoding => nil,
292
+ :application_headers => nil,
293
+ :priority => nil,
294
+ :correlation_id => nil,
295
+ :reply_to => nil,
296
+ :expiration => nil,
297
+ :message_id => nil,
298
+ :timestamp => nil,
299
+ :type => nil,
300
+ :user_id => nil,
301
+ :app_id => nil,
302
+ :cluster_id => nil}.merge(opts)
303
+
304
+ headers.delete_if { |key, value| value == nil }
305
+
306
+ out << Protocol::Header.new(Protocol::Basic, data.bytesize, headers)
307
+
308
+ out << Frame::Body.new(data.to_s)
309
+
310
+ @mq.send *out
311
+ }
312
+ self
313
+ end
314
+
315
+ # This method deletes an exchange. When an exchange is deleted all queue
316
+ # bindings on the exchange are cancelled.
317
+ #
318
+ # Further attempts to publish messages to a deleted exchange will raise
319
+ # an MQ::Error due to a channel close exception.
320
+ #
321
+ # exchange = MQ.direct('name', :key => 'foo.bar')
322
+ # exchange.delete
323
+ #
324
+ # == Options
325
+ # * :nowait => true | false (default true)
326
+ # If set, the server will not respond to the method. The client should
327
+ # not wait for a reply method. If the server could not complete the
328
+ # method it will raise a channel or connection exception.
329
+ #
330
+ # exchange.delete(:nowait => false)
331
+ #
332
+ # * :if_unused => true | false (default false)
333
+ # If set, the server will only delete the exchange if it has no queue
334
+ # bindings. If the exchange has queue bindings the server does not
335
+ # delete it but raises a channel exception instead (MQ:Error).
336
+ #
337
+ def delete opts = {}
338
+ @mq.callback {
339
+ @mq.send Protocol::Exchange::Delete.new({:exchange => name,
340
+ :nowait => true}.merge(opts))
341
+ @mq.exchanges.delete name
342
+ }
343
+ nil
344
+ end
345
+
346
+ def reset
347
+ @deferred_status = nil
348
+ initialize @mq, @type, @name, @opts
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,33 @@
1
+ class MQ
2
+ class Header
3
+ include AMQP
4
+
5
+ def initialize(mq, header_obj)
6
+ @mq = mq
7
+ @header = header_obj
8
+ end
9
+
10
+ # Acknowledges the receipt of this message with the server.
11
+ def ack
12
+ @mq.callback{
13
+ @mq.send Protocol::Basic::Ack.new(:delivery_tag => properties[:delivery_tag])
14
+ }
15
+ end
16
+
17
+ # Reject this message (XXX currently unimplemented in rabbitmq)
18
+ # * :requeue => true | false (default false)
19
+ def reject opts = {}
20
+ @mq.callback{
21
+ @mq.send Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag]))
22
+ }
23
+ end
24
+
25
+ def method_missing meth, *args, &blk
26
+ @header.send meth, *args, &blk
27
+ end
28
+
29
+ def inspect
30
+ @header.inspect
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,89 @@
1
+ class MQ
2
+ class Logger
3
+ def initialize *args, &block
4
+ opts = args.pop if args.last.is_a? Hash
5
+ opts ||= {}
6
+
7
+ printer(block) if block
8
+
9
+ @prop = opts
10
+ @tags = ([:timestamp] + args).uniq
11
+ end
12
+
13
+ attr_reader :prop
14
+ alias :base :prop
15
+
16
+ def log severity, *args
17
+ opts = args.pop if args.last.is_a? Hash and args.size != 1
18
+ opts ||= {}
19
+ opts = @prop.clone.update(opts)
20
+
21
+ data = args.shift
22
+
23
+ data = {:type => :exception,
24
+ :name => data.class.to_s.intern,
25
+ :backtrace => data.backtrace,
26
+ :message => data.message} if data.is_a? Exception
27
+
28
+ (@tags + args).each do |tag|
29
+ tag = tag.to_sym
30
+ case tag
31
+ when :timestamp
32
+ opts.update :timestamp => Time.now
33
+ when :hostname
34
+ @hostname ||= { :hostname => `hostname`.strip }
35
+ opts.update @hostname
36
+ when :process
37
+ @process_id ||= { :process_id => Process.pid,
38
+ :process_name => $0,
39
+ :process_parent_id => Process.ppid,
40
+ :thread_id => Thread.current.object_id }
41
+ opts.update :process => @process_id
42
+ else
43
+ (opts[:tags] ||= []) << tag
44
+ end
45
+ end
46
+
47
+ opts.update(:severity => severity,
48
+ :msg => data)
49
+
50
+ print(opts)
51
+ unless Logger.disabled?
52
+ MQ.fanout('logging', :durable => true).publish Marshal.dump(opts)
53
+ end
54
+
55
+ opts
56
+ end
57
+ alias :method_missing :log
58
+
59
+ def print data = nil, &block
60
+ if block
61
+ @printer = block
62
+ elsif data.is_a? Proc
63
+ @printer = data
64
+ elsif data
65
+ (pr = @printer || self.class.printer) and pr.call(data)
66
+ else
67
+ @printer
68
+ end
69
+ end
70
+ alias :printer :print
71
+
72
+ def self.printer &block
73
+ @printer = block if block
74
+ @printer
75
+ end
76
+
77
+ def self.disabled?
78
+ !!@disabled
79
+ end
80
+
81
+ def self.enable
82
+ @disabled = false
83
+ end
84
+
85
+ def self.disable
86
+ @disabled = true
87
+ end
88
+ end
89
+ end