onyx-stomp 1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ == 1.1 2009-27-02
2
+
3
+ * Ruby 1.9 Support
4
+ * Add support for connect_headers, to control the CONNECT command.
5
+ * Refactored lib dir to separate concerns.
6
+ * Better test coverage
7
+ * General code cleanup.
8
+
9
+ == 1.0.6 2008-05-08
10
+
11
+ * Whitespace cleanup
12
+ * Refactored Rakefile and added stomp.gemspec for GitHub friendliness.
13
+ * Added .gitignore file
14
+ * Refactored layout of lib dir to separate concerns
15
+ * Cleanup of initializers, and provide Client accessors for reading values (for testing)
16
+ * Removed test/test_url_* files as they only differed from the test_client.rb in their
17
+ setup. Super UnDry. Added URL tests to cover stomp URL as param.
18
+ * Created initial RSpec specs which stub/mock objects and should not require a running
19
+ Stomp server instance.
20
+
21
+ == v1.0.5
22
+
23
+ SVN rev 86 clone from http://svn.codehaus.org/stomp/ruby/trunk
24
+
25
+ git-svn-id: http://svn.codehaus.org/stomp/ruby/trunk@86 fd4e7336-3dff-0310-b68a-b6615a75f13b
data/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,36 @@
1
+ ==README
2
+
3
+ http://stomp.rubyforge.org/
4
+
5
+ ===Overview
6
+
7
+ An implementation of the Stomp protocol (http://stomp.codehaus.org/Protocol) for Ruby.
8
+
9
+
10
+ ===Example Usage
11
+
12
+ client = Stomp::Client.new("test", "user", "localhost", 61613)
13
+ client.send("/my/queue", "hello world!")
14
+ client.subscribe("/my/queue") do |msg|
15
+ p msg
16
+ end
17
+
18
+ ===Contact info
19
+
20
+ Up until March 2009 the project was maintained and primarily developed by Brian McCallister.
21
+
22
+ The project is now maintained by Johan Sørensen <johan@johansorensen.com>
23
+
24
+ ===Source Code
25
+
26
+ http://gitorious.org/projects/stomp/
27
+ http://github.com/js/stomp/
28
+
29
+ ===Project urls
30
+
31
+ Project Home :
32
+ http://gitorious.org/projects/stomp/
33
+ http://rubyforge.org/projects/stomp/
34
+
35
+ Stomp Protocol Info :
36
+ http://stomp.codehaus.org/Protocol
@@ -0,0 +1,52 @@
1
+ # Copyright 2005-2006 Brian McCallister
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'rubygems'
16
+ require 'rake/gempackagetask'
17
+ require 'rake/testtask'
18
+ require 'rake/rdoctask'
19
+ require 'spec/rake/spectask'
20
+
21
+ # read the contents of the gemspec, eval it, and assign it to 'spec'
22
+ # this lets us maintain all gemspec info in one place. Nice and DRY.
23
+ spec = eval(IO.read("stomp.gemspec"))
24
+
25
+ Rake::GemPackageTask.new(spec) do |pkg|
26
+ pkg.gem_spec = spec
27
+ pkg.need_tar = true
28
+ end
29
+
30
+ task :install => [:package] do
31
+ sh %{sudo gem install pkg/#{GEM}-#{VERSION}}
32
+ end
33
+
34
+ Rake::TestTask.new do |t|
35
+ t.libs << "test"
36
+ t.test_files = FileList['test/test*.rb']
37
+ t.verbose = true
38
+ end
39
+
40
+ Rake::RDocTask.new do |rd|
41
+ rd.main = "README.rdoc"
42
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
43
+ rd.rdoc_dir = 'doc'
44
+ rd.options = spec.rdoc_options
45
+ end
46
+
47
+ desc "RSpec : run all"
48
+ Spec::Rake::SpecTask.new('spec') do |t|
49
+ t.spec_files = FileList['spec/**/*.rb']
50
+ t.spec_opts = ["--color", "--format", "specdoc"]
51
+ end
52
+
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright 2006 LogicBlaze Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ begin; require 'rubygems'; rescue; end
18
+ require 'stomp'
19
+
20
+ #
21
+ # This simple script is inspired by the netcat utility. It allows you to send
22
+ # input into this process to stomp destination.
23
+ #
24
+ # Usage: catstomp (destination-name)
25
+ #
26
+ # Example: ls | catstomp /topic/foo
27
+ # Would send the output of the ls command to the stomp destination /topic/foo
28
+ #
29
+ begin
30
+
31
+ @port = 61613
32
+ @host = "localhost"
33
+ @user = ENV["STOMP_USER"];
34
+ @password = ENV["STOMP_PASSWORD"]
35
+
36
+ @host = ENV["STOMP_HOST"] if ENV["STOMP_HOST"] != nil
37
+ @port = ENV["STOMP_PORT"] if ENV["STOMP_PORT"] != nil
38
+
39
+ @destination = "/topic/default"
40
+ @destination = $*[0] if $*[0] != nil
41
+
42
+ $stderr.print "Connecting to stomp://#{@host}:#{@port} as #{@user}\n"
43
+ @conn = Stomp::Connection.open(@user, @password, @host, @port, true)
44
+ $stderr.print "Sending input to #{@destination}\n"
45
+
46
+ @headers = {'persistent'=>'false'}
47
+ @headers['reply-to'] = $*[1] if $*[1] != nil
48
+
49
+ STDIN.each_line { |line|
50
+ @conn.send @destination, line, @headers
51
+ }
52
+
53
+ rescue
54
+ end
55
+
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright 2006 LogicBlaze Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ begin; require 'rubygems'; rescue; end
18
+ require 'stomp'
19
+
20
+ #
21
+ # This simple script is inspired by the netcat utility. It allows you to receive
22
+ # data from a stomp destination and output it.
23
+ #
24
+ # Usage: stompcat (destination-name)
25
+ #
26
+ # Example: stompcat /topic/foo
27
+ # Would display output that arrives at the /topic/foo stomp destination
28
+ #
29
+ begin
30
+
31
+ @port = 61613
32
+ @host = "localhost"
33
+ @user = ENV["STOMP_USER"];
34
+ @password = ENV["STOMP_PASSWORD"]
35
+
36
+ @host = ENV["STOMP_HOST"] if ENV["STOMP_HOST"] != nil
37
+ @port = ENV["STOMP_PORT"] if ENV["STOMP_PORT"] != nil
38
+
39
+ @destination = "/topic/default"
40
+ @destination = $*[0] if $*[0] != nil
41
+
42
+ $stderr.print "Connecting to stomp://#{@host}:#{@port} as #{@user}\n"
43
+ @conn = Stomp::Connection.open(@user, @password, @host, @port, true)
44
+ $stderr.print "Getting output from #{@destination}\n"
45
+
46
+ @conn.subscribe(@destination, { :ack =>"client" })
47
+ while true
48
+ @msg = @conn.receive
49
+ $stdout.print @msg.body
50
+ $stdout.flush
51
+ @conn.ack @msg.headers["message-id"]
52
+ end
53
+
54
+ rescue
55
+ end
56
+
@@ -0,0 +1,25 @@
1
+ # Copyright 2005-2006 Brian McCallister
2
+ # Copyright 2006 LogicBlaze Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'io/wait'
17
+ require 'socket'
18
+ require 'thread'
19
+ require 'stomp/connection'
20
+ require 'stomp/client'
21
+ require 'stomp/message'
22
+
23
+ module Stomp
24
+ end
25
+
@@ -0,0 +1,207 @@
1
+ module Stomp
2
+
3
+ # Typical Stomp client class. Uses a listener thread to receive frames
4
+ # from the server, any thread can send.
5
+ #
6
+ # Receives all happen in one thread, so consider not doing much processing
7
+ # in that thread if you have much message volume.
8
+ class Client
9
+
10
+ attr_reader :login, :passcode, :host, :port, :reliable, :running
11
+
12
+ # A new Client object can be initialized using two forms:
13
+ #
14
+ # Standard positional parameters:
15
+ # login (String, default : '')
16
+ # passcode (String, default : '')
17
+ # host (String, default : 'localhost')
18
+ # port (Integer, default : 61613)
19
+ # reliable (Boolean, default : false)
20
+ #
21
+ # e.g. c = Client.new('login', 'passcode', 'localhost', 61613, true)
22
+ #
23
+ # Stomp URL :
24
+ # A Stomp URL must begin with 'stomp://' and can be in one of the following forms:
25
+ #
26
+ # stomp://host:port
27
+ # stomp://host.domain.tld:port
28
+ # stomp://login:passcode@host:port
29
+ # stomp://login:passcode@host.domain.tld:port
30
+ #
31
+ def initialize(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
32
+
33
+ # Parse stomp:// URL's or set positional params
34
+ case login
35
+ when /stomp:\/\/([\w\.]+):(\d+)/ # e.g. stomp://host:port
36
+ # grabs the matching positions out of the regex which are stored as
37
+ # $1 (host), $2 (port), etc
38
+ @login = ''
39
+ @passcode = ''
40
+ @host = $1
41
+ @port = $2.to_i
42
+ @reliable = false
43
+ when /stomp:\/\/([\w\.]+):(\w+)@([\w\.]+):(\d+)/ # e.g. stomp://login:passcode@host:port
44
+ @login = $1
45
+ @passcode = $2
46
+ @host = $3
47
+ @port = $4.to_i
48
+ @reliable = false
49
+ else
50
+ @login = login
51
+ @passcode = passcode
52
+ @host = host
53
+ @port = port.to_i
54
+ @reliable = reliable
55
+ end
56
+
57
+ raise ArgumentError if @host.nil? || @host.empty?
58
+ raise ArgumentError if @port.nil? || @port == '' || @port < 1 || @port > 65535
59
+ raise ArgumentError unless @reliable.is_a?(TrueClass) || @reliable.is_a?(FalseClass)
60
+
61
+ @id_mutex = Mutex.new
62
+ @ids = 1
63
+ @connection = Connection.new(@login, @passcode, @host, @port, @reliable, reconnect_delay, connect_headers)
64
+ @listeners = {}
65
+ @receipt_listeners = {}
66
+ @running = true
67
+ @replay_messages_by_txn = {}
68
+
69
+ @listener_thread = Thread.start do
70
+ while @running
71
+ message = @connection.receive
72
+ case
73
+ when message.nil?
74
+ break
75
+ when message.command == 'MESSAGE'
76
+ if listener = @listeners[message.headers['destination']]
77
+ listener.call(message)
78
+ end
79
+ when message.command == 'RECEIPT'
80
+ if listener = @receipt_listeners[message.headers['receipt-id']]
81
+ listener.call(message)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ end
88
+
89
+ # Syntactic sugar for 'Client.new' See 'initialize' for usage.
90
+ def self.open(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false)
91
+ Client.new(login, passcode, host, port, reliable)
92
+ end
93
+
94
+ # Join the listener thread for this client,
95
+ # generally used to wait for a quit signal
96
+ def join
97
+ @listener_thread.join
98
+ end
99
+
100
+ # Begin a transaction by name
101
+ def begin(name, headers = {})
102
+ @connection.begin(name, headers)
103
+ end
104
+
105
+ # Abort a transaction by name
106
+ def abort(name, headers = {})
107
+ @connection.abort(name, headers)
108
+
109
+ # lets replay any ack'd messages in this transaction
110
+ replay_list = @replay_messages_by_txn[name]
111
+ if replay_list
112
+ replay_list.each do |message|
113
+ if listener = @listeners[message.headers['destination']]
114
+ listener.call(message)
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ # Commit a transaction by name
121
+ def commit(name, headers = {})
122
+ txn_id = headers[:transaction]
123
+ @replay_messages_by_txn.delete(txn_id)
124
+ @connection.commit(name, headers)
125
+ end
126
+
127
+ # Subscribe to a destination, must be passed a block
128
+ # which will be used as a callback listener
129
+ #
130
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
131
+ def subscribe(destination, headers = {})
132
+ raise "No listener given" unless block_given?
133
+ @listeners[destination] = lambda {|msg| yield msg}
134
+ @connection.subscribe(destination, headers)
135
+ end
136
+
137
+ # Unsubecribe from a channel
138
+ def unsubscribe(name, headers = {})
139
+ @connection.unsubscribe(name, headers)
140
+ @listeners[name] = nil
141
+ end
142
+
143
+ # Acknowledge a message, used when a subscription has specified
144
+ # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
145
+ #
146
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
147
+ def acknowledge(message, headers = {})
148
+ txn_id = headers[:transaction]
149
+ if txn_id
150
+ # lets keep around messages ack'd in this transaction in case we rollback
151
+ replay_list = @replay_messages_by_txn[txn_id]
152
+ if replay_list.nil?
153
+ replay_list = []
154
+ @replay_messages_by_txn[txn_id] = replay_list
155
+ end
156
+ replay_list << message
157
+ end
158
+ if block_given?
159
+ headers['receipt'] = register_receipt_listener lambda {|r| yield r}
160
+ end
161
+ @connection.ack message.headers['message-id'], headers
162
+ end
163
+
164
+ # Send message to destination
165
+ #
166
+ # If a block is given a receipt will be requested and passed to the
167
+ # block on receipt
168
+ #
169
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
170
+ def send(destination, message, headers = {})
171
+ if block_given?
172
+ headers['receipt'] = register_receipt_listener lambda {|r| yield r}
173
+ end
174
+ @connection.send(destination, message, headers)
175
+ end
176
+
177
+ # Is this client open?
178
+ def open?
179
+ @connection.open?
180
+ end
181
+
182
+ # Is this client closed?
183
+ def closed?
184
+ @connection.closed?
185
+ end
186
+
187
+ # Close out resources in use by this client
188
+ def close
189
+ @connection.disconnect
190
+ @running = false
191
+ end
192
+
193
+ private
194
+
195
+ def register_receipt_listener(listener)
196
+ id = -1
197
+ @id_mutex.synchronize do
198
+ id = @ids.to_s
199
+ @ids = @ids.succ
200
+ end
201
+ @receipt_listeners[id] = listener
202
+ id
203
+ end
204
+
205
+ end
206
+ end
207
+
@@ -0,0 +1,256 @@
1
+ module Stomp
2
+
3
+ # Low level connection which maps commands and supports
4
+ # synchronous receives
5
+ class Connection
6
+
7
+
8
+ # A new Connection object accepts the following parameters:
9
+ #
10
+ # login (String, default : '')
11
+ # passcode (String, default : '')
12
+ # host (String, default : 'localhost')
13
+ # port (Integer, default : 61613)
14
+ # reliable (Boolean, default : false)
15
+ # reconnect_delay (Integer, default : 5)
16
+ #
17
+ # e.g. c = Client.new("username", "password", "localhost", 61613, true)
18
+ #
19
+ # TODO
20
+ # Stomp URL :
21
+ # A Stomp URL must begin with 'stomp://' and can be in one of the following forms:
22
+ #
23
+ # stomp://host:port
24
+ # stomp://host.domain.tld:port
25
+ # stomp://user:pass@host:port
26
+ # stomp://user:pass@host.domain.tld:port
27
+ #
28
+ def initialize(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
29
+ @host = host
30
+ @port = port
31
+ @login = login
32
+ @passcode = passcode
33
+ @transmit_semaphore = Mutex.new
34
+ @read_semaphore = Mutex.new
35
+ @socket_semaphore = Mutex.new
36
+ @reliable = reliable
37
+ @reconnect_delay = reconnect_delay
38
+ @connect_headers = connect_headers
39
+ @closed = false
40
+ @subscriptions = {}
41
+ @failure = nil
42
+ socket
43
+ end
44
+
45
+ # Syntactic sugar for 'Connection.new' See 'initialize' for usage.
46
+ def Connection.open(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
47
+ Connection.new(login, passcode, host, port, reliable, reconnect_delay, connect_headers)
48
+ end
49
+
50
+ def socket
51
+ # Need to look into why the following synchronize does not work.
52
+ #@read_semaphore.synchronize do
53
+ s = @socket;
54
+ while s.nil? || !@failure.nil?
55
+ @failure = nil
56
+ begin
57
+ s = TCPSocket.open @host, @port
58
+ headers = @connect_headers.clone
59
+ headers[:login] = @login
60
+ headers[:passcode] = @passcode
61
+ _transmit(s, "CONNECT", headers)
62
+ @connect = _receive(s)
63
+ # replay any subscriptions.
64
+ @subscriptions.each { |k,v| _transmit(s, "SUBSCRIBE", v) }
65
+ rescue
66
+ @failure = $!;
67
+ s=nil;
68
+ raise unless @reliable
69
+ $stderr.print "connect failed: " + $! +" will retry in #{@reconnect_delay}\n";
70
+ sleep(@reconnect_delay);
71
+ end
72
+ end
73
+ @socket = s
74
+ return s;
75
+ #end
76
+ end
77
+
78
+ # Is this connection open?
79
+ def open?
80
+ !@closed
81
+ end
82
+
83
+ # Is this connection closed?
84
+ def closed?
85
+ @closed
86
+ end
87
+
88
+ # Begin a transaction, requires a name for the transaction
89
+ def begin(name, headers = {})
90
+ headers[:transaction] = name
91
+ transmit("BEGIN", headers)
92
+ end
93
+
94
+ # Acknowledge a message, used when a subscription has specified
95
+ # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
96
+ #
97
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
98
+ def ack(message_id, headers = {})
99
+ headers['message-id'] = message_id
100
+ transmit("ACK", headers)
101
+ end
102
+
103
+ # Commit a transaction by name
104
+ def commit(name, headers = {})
105
+ headers[:transaction] = name
106
+ transmit("COMMIT", headers)
107
+ end
108
+
109
+ # Abort a transaction by name
110
+ def abort(name, headers = {})
111
+ headers[:transaction] = name
112
+ transmit("ABORT", headers)
113
+ end
114
+
115
+ # Subscribe to a destination, must specify a name
116
+ def subscribe(name, headers = {}, subId = nil)
117
+ headers[:destination] = name
118
+ transmit("SUBSCRIBE", headers)
119
+
120
+ # Store the sub so that we can replay if we reconnect.
121
+ if @reliable
122
+ subId = name if subId.nil?
123
+ @subscriptions[subId] = headers
124
+ end
125
+ end
126
+
127
+ # Unsubscribe from a destination, must specify a name
128
+ def unsubscribe(name, headers = {}, subId = nil)
129
+ headers[:destination] = name
130
+ transmit("UNSUBSCRIBE", headers)
131
+ if @reliable
132
+ subId = name if subId.nil?
133
+ @subscriptions.delete(subId)
134
+ end
135
+ end
136
+
137
+ # Send message to destination
138
+ #
139
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
140
+ def send(destination, message, headers = {})
141
+ headers[:destination] = destination
142
+ transmit("SEND", headers, message)
143
+ end
144
+
145
+ # Close this connection
146
+ def disconnect(headers = {})
147
+ transmit("DISCONNECT", headers)
148
+ @closed = true
149
+ end
150
+
151
+ # Return a pending message if one is available, otherwise
152
+ # return nil
153
+ def poll
154
+ @read_semaphore.synchronize do
155
+ return nil if @socket.nil? || !@socket.ready?
156
+ return receive
157
+ end
158
+ end
159
+
160
+ # Receive a frame, block until the frame is received
161
+ def __old_receive
162
+ # The recive my fail so we may need to retry.
163
+ while TRUE
164
+ begin
165
+ s = socket
166
+ return _receive(s)
167
+ rescue
168
+ @failure = $!;
169
+ raise unless @reliable
170
+ $stderr.print "receive failed: " + $!;
171
+ end
172
+ end
173
+ end
174
+
175
+ def receive
176
+ super_result = __old_receive()
177
+ if super_result.nil? && @reliable
178
+ $stderr.print "connection.receive returning EOF as nil - resetting connection.\n"
179
+ @socket = nil
180
+ super_result = __old_receive()
181
+ end
182
+ return super_result
183
+ end
184
+
185
+ private
186
+
187
+ def _receive( s )
188
+ line = ' '
189
+ @read_semaphore.synchronize do
190
+ line = s.gets while line =~ /^\s*$/
191
+ return nil if line.nil?
192
+
193
+ message = Message.new do |m|
194
+ m.command = line.chomp
195
+ m.headers = {}
196
+ until (line = s.gets.chomp) == ''
197
+ k = (line.strip[0, line.strip.index(':')]).strip
198
+ v = (line.strip[line.strip.index(':') + 1, line.strip.length]).strip
199
+ m.headers[k] = v
200
+ end
201
+
202
+ if (m.headers['content-length'])
203
+ m.body = s.read m.headers['content-length'].to_i
204
+ c = RUBY_VERSION > '1.9' ? s.getc.ord : s.getc
205
+ raise "Invalid content length received" unless c == 0
206
+ else
207
+ m.body = ''
208
+ if RUBY_VERSION > '1.9'
209
+ until (c = s.getc.ord) == 0
210
+ m.body << c.chr
211
+ end
212
+ else
213
+ until (c = s.getc) == 0
214
+ m.body << c.chr
215
+ end
216
+ end
217
+ end
218
+ #c = s.getc
219
+ #raise "Invalid frame termination received" unless c == 10
220
+ end # message
221
+ return message
222
+
223
+ end
224
+ end
225
+
226
+ def transmit(command, headers = {}, body = '')
227
+ # The transmit may fail so we may need to retry.
228
+ while TRUE
229
+ begin
230
+ s = socket
231
+ _transmit(s, command, headers, body)
232
+ return
233
+ rescue
234
+ @failure = $!;
235
+ raise unless @reliable
236
+ $stderr.print "transmit failed: " + $!+"\n";
237
+ end
238
+ end
239
+ end
240
+
241
+ def _transmit(s, command, headers = {}, body = '')
242
+ @transmit_semaphore.synchronize do
243
+ s.puts command
244
+ headers.each {|k,v| s.puts "#{k}:#{v}" }
245
+ s.puts "content-length: #{body.length}"
246
+ s.puts "content-type: text/plain; charset=UTF-8"
247
+ s.puts
248
+ s.write body
249
+ s.write "\0"
250
+ end
251
+ end
252
+
253
+ end
254
+
255
+ end
256
+
@@ -0,0 +1,17 @@
1
+ module Stomp
2
+
3
+ # Container class for frames, misnamed technically
4
+ class Message
5
+ attr_accessor :headers, :body, :command
6
+
7
+ def initialize
8
+ yield(self) if block_given?
9
+ end
10
+
11
+ def to_s
12
+ "<Stomp::Message headers=#{headers.inspect} body='#{body}' command='#{command}' >"
13
+ end
14
+ end
15
+
16
+ end
17
+
@@ -0,0 +1,182 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper.rb')
2
+
3
+ class TestClient < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @client = Stomp::Client.new("test", "user", "localhost", 61613)
7
+ end
8
+
9
+ def teardown
10
+ @client.close
11
+ end
12
+
13
+ def message_text
14
+ "test_client#" + name()
15
+ end
16
+
17
+ def destination
18
+ "/queue/test/ruby/client/" + name()
19
+ end
20
+
21
+ def test_subscribe_requires_block
22
+ assert_raise(RuntimeError) do
23
+ @client.subscribe destination
24
+ end
25
+ end
26
+
27
+ def test_asynch_subscribe
28
+ received = false
29
+ @client.subscribe(destination) {|msg| received = msg}
30
+ @client.send destination, message_text
31
+ sleep 0.01 until received
32
+
33
+ assert_equal message_text, received.body
34
+ end
35
+
36
+ def test_ack_api_works
37
+ @client.send destination, message_text
38
+
39
+ received = nil
40
+ @client.subscribe(destination, :ack => 'client') {|msg| received = msg}
41
+ sleep 0.01 until received
42
+ assert_equal message_text, received.body
43
+
44
+ receipt = nil
45
+ @client.acknowledge(received) {|r| receipt = r}
46
+ sleep 0.01 until receipt
47
+ assert_not_nil receipt.headers['receipt-id']
48
+ end
49
+
50
+ # BROKEN
51
+ def test_noack
52
+ @client.send destination, message_text
53
+
54
+ received = nil
55
+ @client.subscribe(destination, :ack => :client) {|msg| received = msg}
56
+ sleep 0.01 until received
57
+ assert_equal message_text, received.body
58
+ @client.close
59
+
60
+ # was never acked so should be resent to next client
61
+
62
+ @client = Stomp::Client.new("test", "user", "localhost", 61613)
63
+ received = nil
64
+ @client.subscribe(destination) {|msg| received = msg}
65
+ sleep 0.01 until received
66
+
67
+ assert_equal message_text, received.body
68
+ end
69
+
70
+ def test_receipts
71
+ receipt = false
72
+ @client.send(destination, message_text) {|r| receipt = r}
73
+ sleep 0.1 until receipt
74
+
75
+ message = nil
76
+ @client.subscribe(destination) {|m| message = m}
77
+ sleep 0.1 until message
78
+ assert_equal message_text, message.body
79
+ end
80
+
81
+ def test_send_then_sub
82
+ @client.send destination, message_text
83
+ message = nil
84
+ @client.subscribe(destination) {|m| message = m}
85
+ sleep 0.01 until message
86
+
87
+ assert_equal message_text, message.body
88
+ end
89
+
90
+ def test_transactional_send
91
+ @client.begin 'tx1'
92
+ @client.send destination, message_text, :transaction => 'tx1'
93
+ @client.commit 'tx1'
94
+
95
+ message = nil
96
+ @client.subscribe(destination) {|m| message = m}
97
+ sleep 0.01 until message
98
+
99
+ assert_equal message_text, message.body
100
+ end
101
+
102
+ def test_transaction_send_then_rollback
103
+ @client.begin 'tx1'
104
+ @client.send destination, "first_message", :transaction => 'tx1'
105
+ @client.abort 'tx1'
106
+
107
+ @client.begin 'tx1'
108
+ @client.send destination, "second_message", :transaction => 'tx1'
109
+ @client.commit 'tx1'
110
+
111
+ message = nil
112
+ @client.subscribe(destination) {|m| message = m}
113
+ sleep 0.01 until message
114
+ assert_equal "second_message", message.body
115
+ end
116
+
117
+ def test_transaction_ack_rollback_with_new_client
118
+ @client.send destination, message_text
119
+
120
+ @client.begin 'tx1'
121
+ message = nil
122
+ @client.subscribe(destination, :ack => 'client') {|m| message = m}
123
+ sleep 0.01 until message
124
+ assert_equal message_text, message.body
125
+ @client.acknowledge message, :transaction => 'tx1'
126
+ message = nil
127
+ @client.abort 'tx1'
128
+
129
+ # lets recreate the connection
130
+ teardown
131
+ setup
132
+ @client.subscribe(destination, :ack => 'client') {|m| message = m}
133
+
134
+ Timeout::timeout(4) do
135
+ sleep 0.01 until message
136
+ end
137
+ assert_not_nil message
138
+ assert_equal message_text, message.body
139
+
140
+ @client.begin 'tx2'
141
+ @client.acknowledge message, :transaction => 'tx2'
142
+ @client.commit 'tx2'
143
+ end
144
+
145
+ def test_unsubscribe
146
+ message = nil
147
+ client = Stomp::Client.new("test", "user", "localhost", 61613, true)
148
+ client.subscribe(destination, :ack => 'client') { |m| message = m }
149
+ @client.send destination, message_text
150
+ Timeout::timeout(4) do
151
+ sleep 0.01 until message
152
+ end
153
+ client.unsubscribe destination # was throwing exception on unsub at one point
154
+
155
+ end
156
+
157
+ def test_transaction_with_client_side_redelivery
158
+ @client.send destination, message_text
159
+
160
+ @client.begin 'tx1'
161
+ message = nil
162
+ @client.subscribe(destination, :ack => 'client') { |m| message = m }
163
+
164
+ sleep 0.1 while message.nil?
165
+
166
+ assert_equal message_text, message.body
167
+ @client.acknowledge message, :transaction => 'tx1'
168
+ message = nil
169
+ @client.abort 'tx1'
170
+
171
+ sleep 0.1 while message.nil?
172
+
173
+ assert_not_nil message
174
+ assert_equal message_text, message.body
175
+
176
+ @client.begin 'tx2'
177
+ @client.acknowledge message, :transaction => 'tx2'
178
+ @client.commit 'tx2'
179
+ end
180
+
181
+
182
+ end
@@ -0,0 +1,95 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper.rb')
2
+
3
+ class TestStomp < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @conn = Stomp::Connection.open("test", "user", "localhost", 61613)
7
+ end
8
+
9
+ def teardown
10
+ @conn.disconnect
11
+ end
12
+
13
+ def make_destination
14
+ "/queue/test/ruby/stomp/" + name()
15
+ end
16
+
17
+ def _test_transaction
18
+ @conn.subscribe make_destination
19
+
20
+ # Drain the destination.
21
+ sleep 0.01 while
22
+ sleep 0.01 while @conn.poll!=nil
23
+
24
+ @conn.begin "tx1"
25
+ @conn.send make_destination, "txn message", 'transaction' => "tx1"
26
+
27
+ @conn.send make_destination, "first message"
28
+
29
+ sleep 0.01
30
+ msg = @conn.receive
31
+ assert_equal "first message", msg.body
32
+
33
+ @conn.commit "tx1"
34
+ msg = @conn.receive
35
+ assert_equal "txn message", msg.body
36
+ end
37
+
38
+ def test_connection_exists
39
+ assert_not_nil @conn
40
+ end
41
+
42
+ def test_explicit_receive
43
+ @conn.subscribe make_destination
44
+ @conn.send make_destination, "test_stomp#test_explicit_receive"
45
+ msg = @conn.receive
46
+ assert_equal "test_stomp#test_explicit_receive", msg.body
47
+ end
48
+
49
+ def test_receipt
50
+ @conn.subscribe make_destination, :receipt => "abc"
51
+ msg = @conn.receive
52
+ assert_equal "abc", msg.headers['receipt-id']
53
+ end
54
+
55
+ def test_client_ack_with_symbol
56
+ @conn.subscribe make_destination, :ack => :client
57
+ @conn.send make_destination, "test_stomp#test_client_ack_with_symbol"
58
+ msg = @conn.receive
59
+ @conn.ack msg.headers['message-id']
60
+ end
61
+
62
+ def test_embedded_null
63
+ @conn.subscribe make_destination
64
+ @conn.send make_destination, "a\0"
65
+ msg = @conn.receive
66
+ assert_equal "a\0" , msg.body
67
+ end
68
+
69
+ def test_connection_open?
70
+ assert_equal true , @conn.open?
71
+ @conn.disconnect
72
+ assert_equal false, @conn.open?
73
+ end
74
+
75
+ def test_connection_closed?
76
+ assert_equal false, @conn.closed?
77
+ @conn.disconnect
78
+ assert_equal true, @conn.closed?
79
+ end
80
+
81
+ def test_response_is_instance_of_message_class
82
+ @conn.subscribe make_destination
83
+ @conn.send make_destination, "a\0"
84
+ msg = @conn.receive
85
+ assert_instance_of Stomp::Message , msg
86
+ end
87
+
88
+ def test_message_to_s
89
+ @conn.subscribe make_destination
90
+ @conn.send make_destination, "a\0"
91
+ msg = @conn.receive
92
+ assert_match /^<Stomp::Message headers=/ , msg.to_s
93
+ end
94
+
95
+ end
@@ -0,0 +1,5 @@
1
+ require 'test/unit'
2
+ require 'timeout'
3
+ require 'stomp'
4
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
5
+
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: onyx-stomp
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: "1.1"
6
+ platform: ruby
7
+ authors:
8
+ - Brian McCallister
9
+ - Marius Mathiesen
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2011-06-16 00:00:00 -04:00
15
+ default_executable:
16
+ dependencies: []
17
+
18
+ description: Ruby client for the Stomp messaging protocol
19
+ email:
20
+ - brianm@apache.org
21
+ - marius@stones.com
22
+ executables:
23
+ - catstomp
24
+ - stompcat
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.rdoc
29
+ - CHANGELOG
30
+ - LICENSE
31
+ files:
32
+ - README.rdoc
33
+ - LICENSE
34
+ - CHANGELOG
35
+ - Rakefile
36
+ - lib/stomp.rb
37
+ - lib/stomp/client.rb
38
+ - lib/stomp/connection.rb
39
+ - lib/stomp/message.rb
40
+ - test/test_client.rb
41
+ - test/test_connection.rb
42
+ - test/test_helper.rb
43
+ - bin/catstomp
44
+ - bin/stompcat
45
+ has_rdoc: true
46
+ homepage: http://stomp.codehaus.org/
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --quiet
52
+ - --title
53
+ - stomp documentation
54
+ - --opname
55
+ - index.html
56
+ - --line-numbers
57
+ - --main
58
+ - README.rdoc
59
+ - --inline-source
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.5.1
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Ruby client for the Stomp messaging protocol
81
+ test_files:
82
+ - test/test_client.rb
83
+ - test/test_connection.rb
84
+ - test/test_helper.rb