stomper 0.4 → 1.0.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.
- data/CHANGELOG +7 -0
- data/README.rdoc +63 -11
- data/lib/stomper.rb +13 -1
- data/lib/stomper/client.rb +3 -284
- data/lib/stomper/connection.rb +67 -114
- data/lib/stomper/frame_reader.rb +73 -0
- data/lib/stomper/frame_writer.rb +21 -0
- data/lib/stomper/frames.rb +24 -9
- data/lib/stomper/frames/abort.rb +2 -6
- data/lib/stomper/frames/ack.rb +2 -6
- data/lib/stomper/frames/begin.rb +2 -5
- data/lib/stomper/frames/client_frame.rb +30 -27
- data/lib/stomper/frames/commit.rb +1 -5
- data/lib/stomper/frames/connect.rb +2 -7
- data/lib/stomper/frames/connected.rb +11 -8
- data/lib/stomper/frames/disconnect.rb +1 -4
- data/lib/stomper/frames/error.rb +3 -8
- data/lib/stomper/frames/message.rb +15 -11
- data/lib/stomper/frames/receipt.rb +1 -6
- data/lib/stomper/frames/send.rb +1 -5
- data/lib/stomper/frames/server_frame.rb +13 -23
- data/lib/stomper/frames/subscribe.rb +9 -14
- data/lib/stomper/frames/unsubscribe.rb +3 -7
- data/lib/stomper/open_uri_interface.rb +41 -0
- data/lib/stomper/receipt_handlers.rb +23 -0
- data/lib/stomper/receiptor.rb +38 -0
- data/lib/stomper/sockets.rb +37 -0
- data/lib/stomper/subscriber.rb +76 -0
- data/lib/stomper/subscription.rb +14 -14
- data/lib/stomper/threaded_receiver.rb +59 -0
- data/lib/stomper/transaction.rb +13 -8
- data/lib/stomper/transactor.rb +50 -0
- data/lib/stomper/uri.rb +55 -0
- data/spec/client_spec.rb +7 -158
- data/spec/connection_spec.rb +13 -3
- data/spec/frame_reader_spec.rb +37 -0
- data/spec/frame_writer_spec.rb +27 -0
- data/spec/frames/client_frame_spec.rb +22 -98
- data/spec/frames/indirect_frame_spec.rb +45 -0
- data/spec/frames/server_frame_spec.rb +15 -16
- data/spec/open_uri_interface_spec.rb +132 -0
- data/spec/receiptor_spec.rb +35 -0
- data/spec/shared_connection_examples.rb +12 -17
- data/spec/spec_helper.rb +6 -0
- data/spec/subscriber_spec.rb +77 -0
- data/spec/subscription_spec.rb +11 -11
- data/spec/subscriptions_spec.rb +3 -6
- data/spec/threaded_receiver_spec.rb +33 -0
- data/spec/transaction_spec.rb +5 -5
- data/spec/transactor_spec.rb +46 -0
- metadata +30 -6
- data/lib/stomper/frames/headers.rb +0 -68
- data/spec/frames/headers_spec.rb +0 -54
@@ -1,16 +1,11 @@
|
|
1
1
|
module Stomper
|
2
2
|
module Frames
|
3
3
|
# Encapsulates a "RECEIPT" server side frame for the Stomp Protocol.
|
4
|
-
#
|
5
|
-
# See the {Stomp Protocol Specification}[http://stomp.codehaus.org/Protocol]
|
6
|
-
# for more details.
|
7
4
|
class Receipt < Stomper::Frames::ServerFrame
|
8
|
-
# This class is a factory for all RECEIPT commands received.
|
9
|
-
factory_for :receipt
|
10
5
|
|
11
6
|
# Creates a new Receipt frame with the supplied +headers+ and +body+
|
12
7
|
def initialize(headers, body)
|
13
|
-
super(
|
8
|
+
super(headers, body)
|
14
9
|
end
|
15
10
|
|
16
11
|
# Returns the 'receipt-id' header of the frame, which
|
data/lib/stomper/frames/send.rb
CHANGED
@@ -1,13 +1,9 @@
|
|
1
1
|
module Stomper
|
2
2
|
module Frames
|
3
3
|
# Encapsulates a "SEND" frame from the Stomp Protocol.
|
4
|
-
#
|
5
|
-
# See the {Stomp Protocol Specification}[http://stomp.codehaus.org/Protocol]
|
6
|
-
# for more details.
|
7
4
|
class Send < Stomper::Frames::ClientFrame
|
8
5
|
def initialize(destination, body, headers={})
|
9
|
-
super(
|
10
|
-
@headers.destination = destination
|
6
|
+
super(headers.merge({ :destination => destination }), body)
|
11
7
|
end
|
12
8
|
end
|
13
9
|
end
|
@@ -1,31 +1,21 @@
|
|
1
1
|
module Stomper
|
2
2
|
module Frames
|
3
3
|
# Encapsulates a server side frame for the Stomp Protocol.
|
4
|
-
|
5
|
-
# See the {Stomp Protocol Specification}[http://stomp.codehaus.org/Protocol]
|
6
|
-
# for more details.
|
7
|
-
class ServerFrame
|
8
|
-
attr_reader :command, :headers, :body
|
4
|
+
class ServerFrame < IndirectFrame
|
9
5
|
|
10
6
|
# Creates a new server frame corresponding to the
|
11
7
|
# supplied +command+ with the given +headers+ and +body+.
|
12
|
-
def initialize(
|
13
|
-
|
14
|
-
@headers = Headers.new(headers)
|
15
|
-
@body = body
|
8
|
+
def initialize(headers={}, body=nil, command = nil)
|
9
|
+
super
|
16
10
|
end
|
17
11
|
|
18
12
|
class << self
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@@registered_commands ||= {}
|
26
|
-
args.each do |command|
|
27
|
-
@@registered_commands[command.to_s.upcase] = self
|
28
|
-
end
|
13
|
+
def inherited(server_frame) #:nodoc:
|
14
|
+
declared_frames << { :class => server_frame, :command => server_frame.name.split("::").last.downcase.to_sym }
|
15
|
+
end
|
16
|
+
|
17
|
+
def declared_frames
|
18
|
+
@declared_frames ||= []
|
29
19
|
end
|
30
20
|
|
31
21
|
# Builds a new ServerFrame instance by first checking to
|
@@ -35,11 +25,11 @@ module Stomper
|
|
35
25
|
# ServerFrame instance is created with its +command+ attribute
|
36
26
|
# set appropriately.
|
37
27
|
def build(command, headers, body)
|
38
|
-
|
39
|
-
if
|
40
|
-
|
28
|
+
com_sym = command.downcase.to_sym
|
29
|
+
if klass = declared_frames.detect { |frame| com_sym == frame[:command] }
|
30
|
+
klass[:class].new(headers, body)
|
41
31
|
else
|
42
|
-
ServerFrame.new(
|
32
|
+
ServerFrame.new(headers, body, command)
|
43
33
|
end
|
44
34
|
end
|
45
35
|
end
|
@@ -1,46 +1,41 @@
|
|
1
1
|
module Stomper
|
2
2
|
module Frames
|
3
3
|
# Encapsulates a "SUBSCRIBE" frame from the Stomp Protocol.
|
4
|
-
#
|
5
|
-
# See the {Stomp Protocol Specification}[http://stomp.codehaus.org/Protocol]
|
6
|
-
# for more details.
|
7
4
|
class Subscribe < Stomper::Frames::ClientFrame
|
8
5
|
def initialize(destination, headers={})
|
9
|
-
super('
|
10
|
-
@headers['destination'] = destination
|
11
|
-
@headers['ack'] ||= 'auto'
|
6
|
+
super({ :ack => 'auto' }.merge(headers).merge({ :destination => destination }))
|
12
7
|
end
|
13
8
|
|
14
9
|
# Returns the ack mode of this subscription. (defaults to 'auto')
|
15
10
|
#
|
16
11
|
# This is a convenience method, and may also be accessed through
|
17
|
-
# frame.headers
|
12
|
+
# frame.headers[:ack]
|
18
13
|
def ack
|
19
|
-
@headers[
|
14
|
+
@headers[:ack]
|
20
15
|
end
|
21
16
|
|
22
17
|
# Returns the destination to which we are subscribing.
|
23
18
|
#
|
24
19
|
# This is a convenience method, and may also be accessed through
|
25
|
-
# frame.headers
|
20
|
+
# frame.headers[:destination]
|
26
21
|
def destination
|
27
|
-
@headers[
|
22
|
+
@headers[:destination]
|
28
23
|
end
|
29
24
|
|
30
25
|
# Returns the id of this subscription, if it has been set.
|
31
26
|
#
|
32
27
|
# This is a convenience method, and may also be accessed through
|
33
|
-
# frame.headers
|
28
|
+
# frame.headers[:id]
|
34
29
|
def id
|
35
|
-
@headers[
|
30
|
+
@headers[:id]
|
36
31
|
end
|
37
32
|
|
38
33
|
# Returns the selector header of this subscription, if it has been set.
|
39
34
|
#
|
40
35
|
# This is a convenience method, and may also be accessed through
|
41
|
-
# frame.headers
|
36
|
+
# frame.headers[:selector]
|
42
37
|
def selector
|
43
|
-
@headers[
|
38
|
+
@headers[:selector]
|
44
39
|
end
|
45
40
|
end
|
46
41
|
end
|
@@ -1,22 +1,18 @@
|
|
1
1
|
module Stomper
|
2
2
|
module Frames
|
3
3
|
# Encapsulates an "UNSUBSCRIBE" frame from the Stomp Protocol.
|
4
|
-
#
|
5
|
-
# See the {Stomp Protocol Specification}[http://stomp.codehaus.org/Protocol]
|
6
|
-
# for more details.
|
7
4
|
class Unsubscribe < Stomper::Frames::ClientFrame
|
8
5
|
def initialize(destination, headers={})
|
9
|
-
super(
|
10
|
-
@headers['destination'] = destination
|
6
|
+
super(headers.merge({ :destination => destination }))
|
11
7
|
end
|
12
8
|
|
13
9
|
# Returns the id of the subscription being unsubscribed from, if it
|
14
10
|
# exists.
|
15
11
|
#
|
16
12
|
# This is a convenience method, and may also be accessed through
|
17
|
-
# frame.headers
|
13
|
+
# frame.headers[:id]
|
18
14
|
def id
|
19
|
-
@headers[
|
15
|
+
@headers[:id]
|
20
16
|
end
|
21
17
|
end
|
22
18
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Stomper
|
2
|
+
module OpenUriInterface
|
3
|
+
def put(msg, headers={})
|
4
|
+
send(default_destination, msg, headers.merge(:generate_content_length => false))
|
5
|
+
end
|
6
|
+
alias_method :puts, :put
|
7
|
+
|
8
|
+
def write(msg, headers={})
|
9
|
+
send(default_destination, msg, headers.merge(:generate_content_length => true))
|
10
|
+
end
|
11
|
+
|
12
|
+
def first(n=1)
|
13
|
+
received = []
|
14
|
+
each do |m|
|
15
|
+
received << m
|
16
|
+
break if received.size == n
|
17
|
+
end
|
18
|
+
n == 1 ? received.first : received
|
19
|
+
end
|
20
|
+
alias_method :get, :first
|
21
|
+
alias_method :gets, :first
|
22
|
+
alias_method :read, :first
|
23
|
+
|
24
|
+
# This is the tricky one.
|
25
|
+
# The subscriber interface is not going to work here, because it is built
|
26
|
+
# for an entirely different use case (threaded receiving)
|
27
|
+
# This interface, by contrast, is blocking... fudge.
|
28
|
+
def each(&block)
|
29
|
+
subscription = subscribe(default_destination) { |m| m }
|
30
|
+
loop do
|
31
|
+
m = receive
|
32
|
+
yield m if m.is_a?(Stomper::Frames::Message) && subscription.accepts?(m)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def default_destination
|
38
|
+
uri.path
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Stomper
|
2
|
+
class ReceiptHandlers
|
3
|
+
def initialize
|
4
|
+
@recps = {}
|
5
|
+
@recp_lock = Mutex.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def add(receipt_id, callback)
|
9
|
+
@recp_lock.synchronize { @recps[receipt_id] = callback }
|
10
|
+
end
|
11
|
+
|
12
|
+
def size
|
13
|
+
@recps.size
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform(receipt)
|
17
|
+
@recp_lock.synchronize do
|
18
|
+
callback = @recps.delete(receipt.for)
|
19
|
+
callback.call(receipt) if callback
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Stomper
|
2
|
+
module Receiptor
|
3
|
+
def self.included(base)
|
4
|
+
if base.method_defined?(:receive)
|
5
|
+
base.instance_eval do
|
6
|
+
alias_method :receive_without_receipt_dispatch, :receive
|
7
|
+
alias_method :receive, :receive_with_receipt_dispatch
|
8
|
+
end
|
9
|
+
end
|
10
|
+
if base.method_defined?(:send)
|
11
|
+
base.instance_eval do
|
12
|
+
alias_method :send_without_receipt_handler, :send
|
13
|
+
alias_method :send, :send_with_receipt_handler
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Receives a frame and dispatches it to the known receipt handlers, if the
|
19
|
+
# received frame is a RECEIPT frame.
|
20
|
+
def receive_with_receipt_dispatch
|
21
|
+
frame = receive_without_receipt_dispatch
|
22
|
+
receipt_handlers.perform(frame) if frame.is_a?(::Stomper::Frames::Receipt)
|
23
|
+
frame
|
24
|
+
end
|
25
|
+
|
26
|
+
def send_with_receipt_handler(destination, body, headers={}, &block)
|
27
|
+
if block_given?
|
28
|
+
headers[:receipt] ||= "rcpt-#{Time.now.to_f}"
|
29
|
+
receipt_handlers.add(headers[:receipt], block)
|
30
|
+
end
|
31
|
+
send_without_receipt_handler(destination, body, headers)
|
32
|
+
end
|
33
|
+
|
34
|
+
def receipt_handlers
|
35
|
+
@receipt_handlers ||= ::Stomper::ReceiptHandlers.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Stomper
|
2
|
+
module Sockets
|
3
|
+
class SSL < DelegateClass(OpenSSL::SSL::SSLSocket)
|
4
|
+
include FrameReader
|
5
|
+
include FrameWriter
|
6
|
+
|
7
|
+
def initialize(host, port, *args)
|
8
|
+
@context = OpenSSL::SSL::SSLContext.new
|
9
|
+
@context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
10
|
+
|
11
|
+
tcp_sock = TCPSocket.new(host, port)
|
12
|
+
@socket = OpenSSL::SSL::SSLSocket.new(tcp_sock, @context)
|
13
|
+
@socket.sync_close = true
|
14
|
+
@socket.connect
|
15
|
+
super(@socket)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ready?
|
19
|
+
@socket.io.ready?
|
20
|
+
end
|
21
|
+
|
22
|
+
def shutdown(mode=2)
|
23
|
+
@socket.io.shutdown(mode)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class TCP < DelegateClass(TCPSocket)
|
28
|
+
include FrameReader
|
29
|
+
include FrameWriter
|
30
|
+
|
31
|
+
def initialize(host, port, *args)
|
32
|
+
@socket = TCPSocket.new(host, port)
|
33
|
+
super(@socket)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Stomper
|
2
|
+
module Subscriber
|
3
|
+
def self.included(base)
|
4
|
+
if base.method_defined?(:receive)
|
5
|
+
base.instance_eval do
|
6
|
+
alias_method :receive_without_message_dispatch, :receive
|
7
|
+
alias_method :receive, :receive_with_message_dispatch
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Receives a frame and dispatches it to the known subscriptions, if the
|
13
|
+
# received frame is a MESSAGE frame.
|
14
|
+
def receive_with_message_dispatch
|
15
|
+
frame = receive_without_message_dispatch
|
16
|
+
subscriptions.perform(frame) if frame.is_a?(::Stomper::Frames::Message)
|
17
|
+
frame
|
18
|
+
end
|
19
|
+
|
20
|
+
# Subscribes to the specified +destination+, passing along
|
21
|
+
# the optional +headers+ inside the subscription frame. When a message
|
22
|
+
# is received for this subscription, the supplied +block+ is
|
23
|
+
# called with the received message as its argument.
|
24
|
+
#
|
25
|
+
# Examples:
|
26
|
+
#
|
27
|
+
# client.subscribe("/queue/test") { |msg| puts "Got message: #{msg.body}" }
|
28
|
+
#
|
29
|
+
# client.subscribe("/queue/test", :ack => 'client', 'id' => 'subscription-001') do |msg|
|
30
|
+
# puts "Got message: #{msg.body}"
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# client.subscribe("/queue/test", :selector => 'cost > 5') do |msg|
|
34
|
+
# puts "Got message: #{msg.body}"
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# See also: unsubscribe, Stomper::Subscription
|
38
|
+
def subscribe(destination, headers={}, &block)
|
39
|
+
unless destination.is_a?(Subscription)
|
40
|
+
destination = Subscription.new(headers.merge(:destination => destination), &block)
|
41
|
+
end
|
42
|
+
subscriptions << destination
|
43
|
+
transmit(destination.to_subscribe)
|
44
|
+
destination
|
45
|
+
end
|
46
|
+
|
47
|
+
# Unsubscribes from the specified +destination+. The +destination+
|
48
|
+
# parameter may be either a string, such as "/queue/test", or Stomper::Subscription
|
49
|
+
# object. If the optional +sub_id+ is supplied, the client will unsubscribe
|
50
|
+
# from the subscription with an id matching +sub_id+, regardless if the
|
51
|
+
# +destination+ parameter matches that of the registered subscription. For
|
52
|
+
# this reason, it is vital that subscription ids, if manually specified, be
|
53
|
+
# unique.
|
54
|
+
#
|
55
|
+
# Examples:
|
56
|
+
#
|
57
|
+
# client.unsubscribe("/queue/test")
|
58
|
+
# # unsubscribes from all "naive" subscriptions for "/queue/test"
|
59
|
+
#
|
60
|
+
# client.unsubscribe("/queue/does/not/matter", "sub-0013012031")
|
61
|
+
# # unsubscribes from all subscriptions with id of "sub-0013012031"
|
62
|
+
#
|
63
|
+
# client.unsubscribe(some_subscription)
|
64
|
+
#
|
65
|
+
# See also: subscribe, Stomper::Subscription
|
66
|
+
def unsubscribe(destination, sub_id=nil)
|
67
|
+
subscriptions.remove(destination, sub_id).each do |unsub|
|
68
|
+
transmit(unsub.to_unsubscribe)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def subscriptions
|
73
|
+
@subscriptions ||= ::Stomper::Subscriptions.new
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/stomper/subscription.rb
CHANGED
@@ -36,23 +36,23 @@ module Stomper
|
|
36
36
|
#
|
37
37
|
# If no +subscription_id+ is specified, either explicitly or through a
|
38
38
|
# hash key of 'id' in +destination_or_options+, one may be automatically
|
39
|
-
# generated of the form
|
39
|
+
# generated of the form +sub-<Time.now.to_f>+. The automatic generation
|
40
40
|
# of a subscription id occurs if and only if naive? returns false.
|
41
41
|
#
|
42
42
|
# While direct creation of Subscription instances is possible, the preferred
|
43
43
|
# method is for them to be constructed by a Stomper::Client through the use
|
44
44
|
# of the Stomper::Client#subscribe method.
|
45
45
|
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
46
|
+
# @see naive?
|
47
|
+
# @see Subscriber#subscribe
|
48
|
+
# @see Subscriber#unsubscribe
|
49
|
+
# @see Client#ack
|
49
50
|
def initialize(destination_or_options, subscription_id=nil, ack=nil, selector=nil, &block)
|
50
51
|
if destination_or_options.is_a?(Hash)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
selector ||= options.selector
|
52
|
+
destination = destination_or_options[:destination]
|
53
|
+
subscription_id ||= destination_or_options[:id]
|
54
|
+
ack ||= destination_or_options[:ack]
|
55
|
+
selector ||= destination_or_options[:selector]
|
56
56
|
else
|
57
57
|
destination = destination_or_options.to_s
|
58
58
|
end
|
@@ -110,9 +110,9 @@ module Stomper
|
|
110
110
|
# Stomper::Frames::Subscribe client frame that can be transmitted
|
111
111
|
# to a stomp broker through a Stomper::Connection instance.
|
112
112
|
def to_subscribe
|
113
|
-
headers = {
|
114
|
-
headers[
|
115
|
-
headers[
|
113
|
+
headers = { :destination => @destination, :ack => @ack.to_s }
|
114
|
+
headers[:id] = @id unless @id.nil?
|
115
|
+
headers[:selector] = @selector unless @selector.nil?
|
116
116
|
Stomper::Frames::Subscribe.new(@destination, headers)
|
117
117
|
end
|
118
118
|
|
@@ -120,8 +120,8 @@ module Stomper
|
|
120
120
|
# Stomper::Frames::Unsubscribe client frame that can be transmitted
|
121
121
|
# to a stomp broker through a Stomper::Connection instance.
|
122
122
|
def to_unsubscribe
|
123
|
-
headers = {
|
124
|
-
headers[
|
123
|
+
headers = { :destination => @destination }
|
124
|
+
headers[:id] = @id unless @id.nil?
|
125
125
|
Stomper::Frames::Unsubscribe.new(@destination, headers)
|
126
126
|
end
|
127
127
|
end
|