stomper 0.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|