[DRE-commits] [ruby-stomp] 01/04: Imported Upstream version 1.2.14
Jonas Genannt
jonas at brachium-system.net
Tue Aug 27 12:12:00 UTC 2013
This is an automated email from the git hooks/post-receive script.
hggh-guest pushed a commit to branch master
in repository ruby-stomp.
commit 3e6d963dd249cb7efb40722d25d18e6b11e78dd4
Author: Jonas Genannt <jonas at brachium-system.net>
Date: Tue Aug 27 14:07:10 2013 +0200
Imported Upstream version 1.2.14
---
CHANGELOG.rdoc | 34 +++++++
README.rdoc | 29 ++++--
examples/slogger.rb | 80 ++++++++++++++--
examples/ssl_uc1_ciphers.rb | 6 +-
examples/ssl_uc2.rb | 9 +-
examples/ssl_uc2_ciphers.rb | 16 +++-
examples/ssl_uc3.rb | 10 +-
examples/ssl_uc3_ciphers.rb | 18 ++--
examples/ssl_uc4.rb | 13 ++-
examples/ssl_uc4_ciphers.rb | 21 ++--
lib/client/utils.rb | 98 +++++++++----------
lib/connection/heartbeats.rb | 169 +++++++++++++++++++++++++-------
lib/connection/netio.rb | 42 +++++---
lib/connection/utils.rb | 26 ++++-
lib/stomp/client.rb | 8 ++
lib/stomp/connection.rb | 65 +++++++++++--
lib/stomp/constants.rb | 7 ++
lib/stomp/errors.rb | 34 ++++++-
lib/stomp/version.rb | 2 +-
metadata.yml | 97 ++++++++-----------
notes/heartbeat_readme.txt | 169 ++++++++++++++++++++++++++++++++
spec/client_spec.rb | 6 +-
spec/connection_spec.rb | 43 ++++++---
stomp.gemspec | 7 +-
test/test_client.rb | 217 ++++++++++++++++++++++++++++++++----------
test/test_connection.rb | 16 +++-
test/test_connection1p.rb | 29 +++++-
test/test_helper.rb | 7 +-
test/test_urlogin.rb | 86 +++++++++++++++++
test/tlogger.rb | 10 +-
30 files changed, 1090 insertions(+), 284 deletions(-)
diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc
index 6f61f13..b445b97 100644
--- a/CHANGELOG.rdoc
+++ b/CHANGELOG.rdoc
@@ -1,3 +1,37 @@
+== 1.2.14 20130819
+
+* Version bump (1.2.13 release had Stomp::Version of 1.1.12.)
+* Prevent dup subscription header on re-receive
+
+== 1.2.13 20130817
+
+* Issue #68, Stomp::Client#unreceive max_redeliveries off-by-one error
+
+== 1.2.12 20130811
+
+* Fix infinite loop when max reconn attempts is reached
+* Enhance JRuby support in tests
+* Issue #63, nil message on rapid AMQ restarts
+* Issue #63, fast spurious failovers with JRuby and AMQ
+* Issue #67, SSL SNI support (thanks Hiram)
+* Proper cleanup when not reliable adn EOF from broker
+* Remove extraneous privte declarations
+* Issue #65, allow non-word characters in login and passcode using stomp://
+* Issue #66, allow a single broker in a failover URL
+
+== 1.2.11 20130728
+
+* Issue #60, timeout/hang under JRuby
+* More generally support JRuby use and testing
+* Issue #58, nil message in Client on AMQ shutdown
+* More robust RabbitMQ tests
+
+== 1.2.10 20130708
+
+* Issue #57, reconnect delays not honored if erroneous headers
+* Support fail overs when heartbeat send/receive fails
+* Update callback logger example
+
== 1.2.9 20130328
* Refactoring and documentation updates (glennr)
diff --git a/README.rdoc b/README.rdoc
index cfb946e..4270a60 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -10,18 +10,23 @@ An implementation of the Stomp protocol for Ruby. See:
===New
-* Gem version 1.2.9. Miscellaneous fixes and changes. See _CHANGELOG.rdoc_ for details.
-* Gem version 1.2.8. Stomp 1.1+ header codec inversion fix, test refactoring. See _CHANGELOG.rdoc_ for details.
-* Gem version 1.2.7. Stomp 1.2 support and miscellaneous fixes. See _CHANGELOG.rdoc_ for details.
-* Gem version 1.2.6. Miscellaneous fixes and changes. See _CHANGELOG.rdoc_ for details.
-* Gem version 1.2.5. Restructure. Forks with modifcations will be affected. See _CHANGELOG.rdoc_ for details.
+See _CHANGELOG.rdoc_ for details.
+
+* Gem version 1.2.14. Cleanup.
+* Gem version 1.2.13. Stomp::Client#unreceive max_redeliveries fix.
+* Gem version 1.2.12. Miscellaneous issue fixes and cleanup.
+* Gem version 1.2.11. JRuby and AMQ support fixes.
+* Gem version 1.2.10. Support failover from heartbeat threads.
+* Gem version 1.2.9. Miscellaneous fixes and changes.
+* Gem version 1.2.8. Stomp 1.1+ header codec inversion fix, test refactoring.
+* Gem version 1.2.7. Stomp 1.2 support and miscellaneous fixes.
+* Gem version 1.2.6. Miscellaneous fixes and changes.
+* Gem version 1.2.5. Restructure. Forks with modifcations will be affected.
* Gem version 1.2.4. Stomp 1.1 heartbeat fix, autoflush capability, miscellaneous fixes.
* Gem version 1.2.3. Miscellaneous fixes, see changelog for details.
* Gem version 1.2.2. Performance and more SSL enhancements.
-* Full support of SSL certificates is announced as of gem version 1.2.1.
-* Support of Stomp protocol level 1.1 is announced as of gem version 1.2.0.
-
-See _CHANGELOG.rdoc_ for details.
+* Gem version 1.2.1. Full support of SSL certificates.
+* Gem version 1.2.0. Support of Stomp protocol level 1.1.
===Hash Login Example Usage (this is the recommended login technique)
@@ -50,6 +55,11 @@ See _CHANGELOG.rdoc_ for details.
:hbser => false, # raise on heartbeat send exception
:stompconn => false, # Use STOMP instead of CONNECT
:usecrlf => false, # Use CRLF command and header line ends (1.2+)
+ :max_hbread_fails => 0, # Max HB read fails before retry. 0 => never retry
+ :max_hbrlck_fails => 0, # Max HB read lock obtain fails before retry. 0 => never retry
+ :fast_hbs_adjust => 0.0, # Fast heartbeat senders sleep adjustment, seconds, needed ...
+ # For fast heartbeat senders. 'fast' == YMMV. If not
+ # correct for your environment, expect unnecessary fail overs
}
# for client
@@ -132,4 +142,5 @@ The following people have contributed to Stomp:
* Jeremy Gailor
* JP Hastings-Spital
* Glenn Roberts
+* Ian Smith
diff --git a/examples/slogger.rb b/examples/slogger.rb
index b6e81ee..278a1e5 100644
--- a/examples/slogger.rb
+++ b/examples/slogger.rb
@@ -15,6 +15,14 @@ require 'logger' # use the standard Ruby logger .....
#
# * on_publish: publish called
# * on_subscribe: subscribe called
+# * on_unsubscribe: unsubscribe called
+#
+# * on_begin: begin called
+# * on_ack: ack called
+# * on_nack: nack called
+# * on_commit: commit called
+# * on_abort: abort called
+#
# * on_receive: receive called and successful
#
# * on_ssl_connecting: SSL connection starting
@@ -80,7 +88,7 @@ class Slogger
=begin
# An example LoggerConnectionError raise
@log.debug "Connect Fail, will raise"
- raise Stomp::Error::LoggerConnectionError.new("quit from connect")
+ raise Stomp::Error::LoggerConnectionError.new("quit from connect fail")
=end
end
@@ -113,6 +121,16 @@ class Slogger
end
end
+ # Log UnSubscribe
+ def on_unsubscribe(parms, headers)
+ begin
+ @log.debug "UnSubscribe Parms #{info(parms)}"
+ @log.debug "UnSubscribe Headers #{headers}"
+ rescue
+ @log.debug "UnSubscribe oops"
+ end
+ end
+
# Log Publish
def on_publish(parms, message, headers)
begin
@@ -134,21 +152,71 @@ class Slogger
end
end
+ # Log Begin
+ def on_begin(parms, headers)
+ begin
+ @log.debug "Begin Parms #{info(parms)}"
+ @log.debug "Begin Result #{headers}"
+ rescue
+ @log.debug "Begin oops"
+ end
+ end
+
+ # Log Ack
+ def on_ack(parms, headers)
+ begin
+ @log.debug "Ack Parms #{info(parms)}"
+ @log.debug "Ack Result #{headers}"
+ rescue
+ @log.debug "Ack oops"
+ end
+ end
+
+ # Log NAck
+ def on_nack(parms, headers)
+ begin
+ @log.debug "NAck Parms #{info(parms)}"
+ @log.debug "NAck Result #{headers}"
+ rescue
+ @log.debug "NAck oops"
+ end
+ end
+
+ # Log Commit
+ def on_commit(parms, headers)
+ begin
+ @log.debug "Commit Parms #{info(parms)}"
+ @log.debug "Commit Result #{headers}"
+ rescue
+ @log.debug "Commit oops"
+ end
+ end
+
+ # Log Abort
+ def on_abort(parms, headers)
+ begin
+ @log.debug "Abort Parms #{info(parms)}"
+ @log.debug "Abort Result #{headers}"
+ rescue
+ @log.debug "Abort oops"
+ end
+ end
+
# Stomp 1.1+ - heart beat read (receive) failed.
- def on_hbread_fail(parms, ticker_data)
+ def on_hbread_fail(parms, ticker_data = {})
begin
@log.debug "Hbreadf Parms #{info(parms)}"
- @log.debug "Hbreadf Result #{ticker_data}"
+ @log.debug "Hbreadf Result #{ticker_data.inspect}"
rescue
@log.debug "Hbreadf oops"
end
end
# Stomp 1.1+ - heart beat send (transmit) failed.
- def on_hbwrite_fail(parms, ticker_data)
+ def on_hbwrite_fail(parms, ticker_data = {})
begin
@log.debug "Hbwritef Parms #{info(parms)}"
- @log.debug "Hbwritef Result #{ticker_data}"
+ @log.debug "Hbwritef Result #{ticker_data.inspect}"
rescue
@log.debug "Hbwritef oops"
end
@@ -188,7 +256,7 @@ class Slogger
end
# Log heart beat fires
- def on_hbfire(parms, srind, curt)
+ def on_hbfire(parms, srind, firedata = {})
begin
@log.debug "HeartBeat Fire Parms #{info(parms)}"
@log.debug "HeartBeat Fire Send/Receive #{srind}"
diff --git a/examples/ssl_uc1_ciphers.rb b/examples/ssl_uc1_ciphers.rb
index dab4ded..3b0fdf3 100644
--- a/examples/ssl_uc1_ciphers.rb
+++ b/examples/ssl_uc1_ciphers.rb
@@ -19,8 +19,7 @@ class ExampleSSL1C
end
# Run example.
def run
- ciphers_list = [["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["DHE-DSS-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["AES256-SHA", "TLSv1/SSLv3", 256, 256], ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["EDH-DSS-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["DHE-DSS-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["AES128-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-MD5", [...]
- ["DES-CBC-SHA", "TLSv1/SSLv3", 56, 56], ["EXP-EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-EDH-DSS-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-RC2-CBC-MD5", "TLSv1/SSLv3", 40, 128], ["EXP-RC4-MD5", "TLSv1/SSLv3", 40, 128]]
+ ciphers_list = [["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["DHE-DSS-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["AES256-SHA", "TLSv1/SSLv3", 256, 256], ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["EDH-DSS-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["DHE-DSS-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["AES128-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-MD5", [...]
ssl_opts = Stomp::SSLParams.new(:ciphers => ciphers_list)
@@ -29,7 +28,8 @@ class ExampleSSL1C
#
hash = { :hosts => [
{:login => 'guest', :passcode => 'guest', :host => 'localhost', :port => 61612, :ssl => ssl_opts},
- ]
+ ],
+ :reliable => false, # YMMV, to test this in a sane manner
}
#
puts "Connect starts, SSL Use Case 1"
diff --git a/examples/ssl_uc2.rb b/examples/ssl_uc2.rb
index 6e4d049..23b0213 100644
--- a/examples/ssl_uc2.rb
+++ b/examples/ssl_uc2.rb
@@ -25,8 +25,13 @@ class ExampleSSL2
# Run example.
def run
ts_flist = []
- ts_flist << "/home/gmallard/sslwork/twocas_tj/serverCA/ServerTJCA.crt"
- ssl_opts = Stomp::SSLParams.new(:ts_files => ts_flist.join(","))
+
+ # Change the following to the location of the server's CA signed certificate.
+ ts_flist << "/home/gmallard/sslwork/2013/TestCA.crt"
+
+ ssl_opts = Stomp::SSLParams.new(:ts_files => ts_flist.join(","),
+ :fsck => true,
+ )
#
hash = { :hosts => [
{:login => 'guest', :passcode => 'guest', :host => 'localhost', :port => 61612, :ssl => ssl_opts},
diff --git a/examples/ssl_uc2_ciphers.rb b/examples/ssl_uc2_ciphers.rb
index 50a7a00..ec0e695 100644
--- a/examples/ssl_uc2_ciphers.rb
+++ b/examples/ssl_uc2_ciphers.rb
@@ -19,18 +19,24 @@ class ExampleSSL2C
end
# Run example.
def run
- ciphers_list = [["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["DHE-DSS-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["AES256-SHA", "TLSv1/SSLv3", 256, 256], ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["EDH-DSS-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["DHE-DSS-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["AES128-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-MD5", [...]
- ["DES-CBC-SHA", "TLSv1/SSLv3", 56, 56], ["EXP-EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-EDH-DSS-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-RC2-CBC-MD5", "TLSv1/SSLv3", 40, 128], ["EXP-RC4-MD5", "TLSv1/SSLv3", 40, 128]]
+ ciphers_list = [["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["DHE-DSS-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["AES256-SHA", "TLSv1/SSLv3", 256, 256], ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["EDH-DSS-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["DHE-DSS-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["AES128-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-MD5", [...]
#
# SSL Use Case 2
#
ts_flist = []
- ts_flist << "/home/gmallard/sslwork/twocas_tj/serverCA/ServerTJCA.crt"
- ssl_opts = Stomp::SSLParams.new(:ts_files => ts_flist.join(","), :ciphers => ciphers_list)
+
+ # Change the following to the location of your CA's signed certificate.
+ ts_flist << "/home/gmallard/sslwork/2013/TestCA.crt"
+
+ ssl_opts = Stomp::SSLParams.new(:ts_files => ts_flist.join(","),
+ :ciphers => ciphers_list,
+ :fsck => true
+ )
#
hash = { :hosts => [
{:login => 'guest', :passcode => 'guest', :host => 'localhost', :port => 61612, :ssl => ssl_opts},
- ]
+ ],
+ :reliable => false, # YMMV, to test this in a sane manner
}
#
puts "Connect starts, SSL Use Case 2"
diff --git a/examples/ssl_uc3.rb b/examples/ssl_uc3.rb
index 504107d..3f5a767 100644
--- a/examples/ssl_uc3.rb
+++ b/examples/ssl_uc3.rb
@@ -26,8 +26,14 @@ class ExampleSSL3
end
# Run example.
def run
- ssl_opts = Stomp::SSLParams.new(:key_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.key",
- :cert_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.crt")
+ # Change the following:
+ # * location of the client's signed certificate
+ # * location of the client's private key.
+ ssl_opts = Stomp::SSLParams.new(
+ :key_file => "/home/gmallard/sslwork/2013/client.key", # the client's private key
+ :cert_file => "/home/gmallard/sslwork/2013/client.crt", # the client's signed certificate
+ :fsck => true # Check that the files exist first
+ )
#
hash = { :hosts => [
diff --git a/examples/ssl_uc3_ciphers.rb b/examples/ssl_uc3_ciphers.rb
index 62d5b2d..9e25fd1 100644
--- a/examples/ssl_uc3_ciphers.rb
+++ b/examples/ssl_uc3_ciphers.rb
@@ -19,18 +19,24 @@ class ExampleSSL3C
end
# Run example.
def run
- ciphers_list = [["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["DHE-DSS-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["AES256-SHA", "TLSv1/SSLv3", 256, 256], ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["EDH-DSS-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["DHE-DSS-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["AES128-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-MD5", [...]
- ["DES-CBC-SHA", "TLSv1/SSLv3", 56, 56], ["EXP-EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-EDH-DSS-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-RC2-CBC-MD5", "TLSv1/SSLv3", 40, 128], ["EXP-RC4-MD5", "TLSv1/SSLv3", 40, 128]]
+ ciphers_list = [["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["DHE-DSS-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["AES256-SHA", "TLSv1/SSLv3", 256, 256], ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["EDH-DSS-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["DHE-DSS-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["AES128-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-MD5", [...]
#
# SSL Use Case 3
#
- ssl_opts = Stomp::SSLParams.new(:key_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.key",
- :cert_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.crt", :ciphers => ciphers_list)
-
+ # Change the following:
+ # * location of your client's signed certificate
+ # * location of tour client's private key.
+ ssl_opts = Stomp::SSLParams.new(
+ :key_file => "/home/gmallard/sslwork/2013/client.key", # the client's private key
+ :cert_file => "/home/gmallard/sslwork/2013/client.crt", # the client's signed certificate
+ :fsck => true, # Check that the files exist first
+ :ciphers => ciphers_list
+ )
#
hash = { :hosts => [
{:login => 'guest', :passcode => 'guest', :host => 'localhost', :port => 61612, :ssl => ssl_opts},
- ]
+ ],
+ :reliable => false, # YMMV, to test this in a sane manner
}
#
puts "Connect starts, SSL Use Case 3"
diff --git a/examples/ssl_uc4.rb b/examples/ssl_uc4.rb
index c861617..bc8deec 100644
--- a/examples/ssl_uc4.rb
+++ b/examples/ssl_uc4.rb
@@ -26,9 +26,16 @@ class ExampleSSL4
end
# Run example.
def run
- ssl_opts = Stomp::SSLParams.new(:key_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.key",
- :cert_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.crt",
- :ts_files => "/home/gmallard/sslwork/twocas_tj/serverCA/ServerTJCA.crt")
+ # Change the following:
+ # * location of the client's private key
+ # * location of the client's signed certificate
+ # * location of the server's CA signed certificate
+ ssl_opts = Stomp::SSLParams.new(
+ :key_file => "/home/gmallard/sslwork/2013/client.key", # The client's private key
+ :cert_file => "/home/gmallard/sslwork/2013/client.crt", # The client's signed certificate
+ :ts_files => "/home/gmallard/sslwork/2013/TestCA.crt", # The CA's signed sertificate
+ :fsck => true # Check that files exist first
+ )
#
hash = { :hosts => [
{:login => 'guest', :passcode => 'guest', :host => 'localhost', :port => 61612, :ssl => ssl_opts},
diff --git a/examples/ssl_uc4_ciphers.rb b/examples/ssl_uc4_ciphers.rb
index 9fd91be..015eab2 100644
--- a/examples/ssl_uc4_ciphers.rb
+++ b/examples/ssl_uc4_ciphers.rb
@@ -19,19 +19,26 @@ class ExampleSSL4C
end
# Run example.
def run
- ciphers_list = [["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["DHE-DSS-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["AES256-SHA", "TLSv1/SSLv3", 256, 256], ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["EDH-DSS-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["DHE-DSS-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["AES128-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-MD5", [...]
- ["DES-CBC-SHA", "TLSv1/SSLv3", 56, 56], ["EXP-EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-EDH-DSS-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56], ["EXP-RC2-CBC-MD5", "TLSv1/SSLv3", 40, 128], ["EXP-RC4-MD5", "TLSv1/SSLv3", 40, 128]]
+ ciphers_list = [["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["DHE-DSS-AES256-SHA", "TLSv1/SSLv3", 256, 256], ["AES256-SHA", "TLSv1/SSLv3", 256, 256], ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["EDH-DSS-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168], ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["DHE-DSS-AES128-SHA", "TLSv1/SSLv3", 128, 128], ["AES128-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-SHA", "TLSv1/SSLv3", 128, 128], ["RC4-MD5", [...]
#
# SSL Use Case 4
#
- ssl_opts = Stomp::SSLParams.new(:key_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.key",
- :cert_file => "/home/gmallard/sslwork/twocas_tj/clientCA/ClientTJ.crt",
- :ts_files => "/home/gmallard/sslwork/twocas_tj/serverCA/ServerTJCA.crt",
- :ciphers => ciphers_list)
+ # Change the following:
+ # * location of the client's private key
+ # * location of the client's signed certificate
+ # * location of the server's CA signed certificate
+ ssl_opts = Stomp::SSLParams.new(
+ :key_file => "/home/gmallard/sslwork/2013/client.key", # The client's private key
+ :cert_file => "/home/gmallard/sslwork/2013/client.crt", # The client's signed certificate
+ :ts_files => "/home/gmallard/sslwork/2013/TestCA.crt", # The CA's signed sertificate
+ :fsck => true, # Check that files exist first
+ :ciphers => ciphers_list
+ )
#
hash = { :hosts => [
{:login => 'guest', :passcode => 'guest', :host => 'localhost', :port => 61612, :ssl => ssl_opts},
- ]
+ ],
+ :reliable => false, # YMMV, to test this in a sane manner
}
#
puts "Connect starts, SSL Use Case 4"
diff --git a/lib/client/utils.rb b/lib/client/utils.rb
index 162f74b..9170344 100644
--- a/lib/client/utils.rb
+++ b/lib/client/utils.rb
@@ -21,43 +21,43 @@ module Stomp
@reliable = true
true
end
- private :parse_hash_params
def parse_stomp_url(login)
- regexp = /^stomp:\/\/#{url_regex}/ # e.g. stomp://login:passcode@host:port or stomp://host:port
+ regexp = /^stomp:\/\/#{URL_REPAT}/
return false unless login =~ regexp
- @login = $2 || ""
- @passcode = $3 || ""
- @host = $4
- @port = $5.to_i
+ @login = $3 || ""
+ @passcode = $4 || ""
+ @host = $5
+ @port = $6.to_i
+
@reliable = false
true
end
- private :parse_stomp_url
# e.g. failover://(stomp://login1:passcode1@localhost:61616,stomp://login2:passcode2@remotehost:61617)?option1=param
def parse_failover_url(login)
- regexp = /^failover:(\/\/)?\(stomp(\+ssl)?:\/\/#{url_regex}(,stomp(\+ssl)?:\/\/#{url_regex}\))+(\?(.*))?$/
- return false unless login =~ regexp
-
- first_host = {}
- first_host[:ssl] = !$2.nil?
- @login = first_host[:login] = $4 || ""
- @passcode = first_host[:passcode] = $5 || ""
- @host = first_host[:host] = $6
- @port = first_host[:port] = $7.to_i || Connection::default_port(first_host[:ssl])
- options = $16 || ""
- parts = options.split(/&|=/)
- options = Hash[*parts]
- hosts = [first_host] + parse_hosts(login)
- @parameters = {}
- @parameters[:hosts] = hosts
- @parameters.merge! filter_options(options)
- @reliable = true
- true
+ rval = nil
+ if md = FAILOVER_REGEX.match(login)
+ finhosts = parse_hosts(login)
+ #
+ @login = finhosts[0][:login] || ""
+ @passcode = finhosts[0][:passcode] || ""
+ @host = finhosts[0][:host] || ""
+ @port = finhosts[0][:port] || ""
+ #
+ options = {}
+ if md_last = md[md.size-1]
+ parts = md_last.split(/&|=/)
+ raise Stomp::Error::MalformedFailoverOptionsError unless (parts.size % 2 ) == 0
+ options = Hash[*parts]
+ end
+ @parameters = {:hosts => finhosts}.merge! filter_options(options)
+ @reliable = true
+ rval = true
+ end
+ rval
end
- private :parse_failover_url
def parse_positional_params(login, passcode, host, port, reliable)
@login = login
@@ -67,7 +67,6 @@ module Stomp
@reliable = reliable
true
end
- private :parse_positional_params
# Set a subscription id in the headers hash if one does not already exist.
# For simplicities sake, all subscriptions have a subscription ID.
@@ -91,29 +90,21 @@ module Stomp
id
end
- # url_regex defines a regex for e.g. login:passcode at host:port or host:port
- def url_regex
- '(([\w\.\-]*):(\w*)@)?([\w\.\-]+):(\d+)'
- end
-
- # Parse a stomp URL.
- def parse_hosts(url)
- hosts = []
-
- host_match = /stomp(\+ssl)?:\/\/(([\w\.]*):(\w*)@)?([\w\.]+):(\d+)\)/
- url.scan(host_match).each do |match|
- host = {}
- host[:ssl] = !match[0].nil?
- host[:login] = match[2] || ""
- host[:passcode] = match[3] || ""
- host[:host] = match[4]
- host[:port] = match[5].to_i
-
- hosts << host
- end
-
- hosts
- end
+# Parse a stomp URL.
+def parse_hosts(url)
+ hosts = []
+ host_match = /stomp(\+ssl)?:\/\/#{URL_REPAT}/
+ url.scan(host_match).each do |match|
+ host = {}
+ host[:ssl] = match[0] == "+ssl" ? true : false
+ host[:login] = match[3] || ""
+ host[:passcode] = match[4] || ""
+ host[:host] = match[5]
+ host[:port] = match[6].to_i
+ hosts << host
+ end
+ hosts
+end
# A very basic check of required arguments.
def check_arguments!()
@@ -157,7 +148,12 @@ module Stomp
@listener_thread = Thread.start do
while true
message = @connection.receive
- if message # AMQ specific?, nil message on multiple reconnects
+ # AMQ specific behavior
+ if message.nil? && (!@reliable)
+ raise Stomp::Error::NilMessageError
+ end
+ if message # message can be nil on rapid AMQ stop / start sequences
+ # OK, we have some real data
if message.command == Stomp::CMD_MESSAGE
if listener = find_listener(message)
listener.call(message)
diff --git a/lib/connection/heartbeats.rb b/lib/connection/heartbeats.rb
index dbd2c86..624cb98 100644
--- a/lib/connection/heartbeats.rb
+++ b/lib/connection/heartbeats.rb
@@ -90,37 +90,62 @@ module Stomp
# _start_send_ticker starts a thread to send heartbeats when required.
def _start_send_ticker()
sleeptime = @hbsend_interval / 1000000.0 # Sleep time secs
+ reconn = false
+ adjust = 0.0
@st = Thread.new {
+ first_time = true
while true do
- sleep sleeptime
+ #
+ slt = sleeptime - adjust - @fast_hbs_adjust
+ sleep(slt)
+ next unless @socket # nil under some circumstances ??
curt = Time.now.to_f
if @logger && @logger.respond_to?(:on_hbfire)
- @logger.on_hbfire(log_params, "send_fire", curt)
+ @logger.on_hbfire(log_params, "send_fire", :curt => curt, :last_sleep => slt)
end
delta = curt - @ls
- if delta > (@hbsend_interval - (@hbsend_interval/5.0)) / 1000000.0 # Be tolerant (minus)
+ # Be tolerant (minus), and always do this the first time through.
+ # Reintroduce logic removed in d922fa.
+ compval = (@hbsend_interval - (@hbsend_interval/5.0)) / 1000000.0
+ if delta > compval || first_time
+ first_time = false
if @logger && @logger.respond_to?(:on_hbfire)
- @logger.on_hbfire(log_params, "send_heartbeat", curt)
+ @logger.on_hbfire(log_params, "send_heartbeat", :last_sleep => slt,
+ :curt => curt, :last_send => @ls, :delta => delta,
+ :compval => compval)
end
# Send a heartbeat
@transmit_semaphore.synchronize do
begin
@socket.puts
- @ls = curt # Update last send
- @hb_sent = true # Reset if necessary
+ @socket.flush # Do not buffer heartbeats
+ @ls = Time.now.to_f # Update last send
+ @hb_sent = true # Reset if necessary
@hbsend_count += 1
rescue Exception => sendex
@hb_sent = false # Set the warning flag
if @logger && @logger.respond_to?(:on_hbwrite_fail)
- @logger.on_hbwrite_fail(log_params, {"ticker_interval" => @hbsend_interval,
+ @logger.on_hbwrite_fail(log_params, {"ticker_interval" => sleeptime,
"exception" => sendex})
end
if @hbser
raise # Re-raise if user requested this, otherwise ignore
end
+ if @reliable
+ reconn = true
+ break # exit the synchronize do
+ end
end
+ end # of the synchronize
+ if reconn
+ # Attempt a fail over reconnect. This is 'safe' here because
+ # this thread no longer holds the @transmit_semaphore lock.
+ @rt.kill if @rt # Kill the receiver thread if one exists
+ _reconn_prep_hb() # Drive reconnection logic
+ Thread.exit # This sender thread is done
end
end
+ adjust = Time.now.to_f - curt
Thread.pass
end
}
@@ -129,44 +154,118 @@ module Stomp
# _start_receive_ticker starts a thread that receives heartbeats when required.
def _start_receive_ticker()
sleeptime = @hbrecv_interval / 1000000.0 # Sleep time secs
+ read_fail_count = 0
+ lock_fail_count = 0
+ fail_hard = false
@rt = Thread.new {
+
+ #
while true do
sleep sleeptime
+ next unless @socket # nil under some circumstances ??
+ rdrdy = _is_ready?(@socket)
curt = Time.now.to_f
if @logger && @logger.respond_to?(:on_hbfire)
- @logger.on_hbfire(log_params, "receive_fire", curt)
+ @logger.on_hbfire(log_params, "receive_fire", :curt => curt)
end
- delta = curt - @lr
- if delta > ((@hbrecv_interval + (@hbrecv_interval/5.0)) / 1000000.0) # Be tolerant (plus)
- if @logger && @logger.respond_to?(:on_hbfire)
- @logger.on_hbfire(log_params, "receive_heartbeat", curt)
- end
- # Client code could be off doing something else (that is, no reading of
- # the socket has been requested by the caller). Try to handle that case.
- lock = @read_semaphore.try_lock
- if lock
- last_char = @socket.getc
- plc = parse_char(last_char)
- if plc == "\n" # Server Heartbeat
- @lr = Time.now.to_f
- else
- @socket.ungetc(last_char)
+ #
+ begin
+ delta = curt - @lr
+ if delta > sleeptime
+ if @logger && @logger.respond_to?(:on_hbfire)
+ @logger.on_hbfire(log_params, "receive_heartbeat", {})
end
- @read_semaphore.unlock
- @hbrecv_count += 1
- else
- # Shrug. Have not received one. Just set warning flag.
- @hb_received = false
- if @logger && @logger.respond_to?(:on_hbread_fail)
- @logger.on_hbread_fail(log_params, {"ticker_interval" => @hbrecv_interval})
+ # Client code could be off doing something else (that is, no reading of
+ # the socket has been requested by the caller). Try to handle that case.
+ lock = @read_semaphore.try_lock
+ if lock
+ lock_fail_count = 0 # clear
+ rdrdy = _is_ready?(@socket)
+ if rdrdy
+ read_fail_count = 0 # clear
+ last_char = @socket.getc
+ if last_char.nil? # EOF from broker?
+ fail_hard = true
+ else
+ @lr = Time.now.to_f
+ plc = parse_char(last_char)
+ if plc == "\n" # Server Heartbeat
+ @hbrecv_count += 1
+ @hb_received = true # Reset if necessary
+ else
+ @socket.ungetc(last_char)
+ end
+ end
+ @read_semaphore.unlock # Release read lock
+ else # Socket is not ready
+ @read_semaphore.unlock # Release read lock
+ @hb_received = false
+ read_fail_count += 1
+ if @logger && @logger.respond_to?(:on_hbread_fail)
+ @logger.on_hbread_fail(log_params, {"ticker_interval" => sleeptime,
+ "read_fail_count" => read_fail_count, "lock_fail" => false,
+ "lock_fail_count" => lock_fail_count})
+ end
+ end
+ else # try_lock failed
+ # Shrug. Could not get lock. Client must be actually be reading.
+ @hb_received = false
+ # But notify caller if possible
+ lock_fail_count += 1
+ if @logger && @logger.respond_to?(:on_hbread_fail)
+ @logger.on_hbread_fail(log_params, {"ticker_interval" => sleeptime,
+ "read_fail_count" => read_fail_count, "lock_fail" => true,
+ "lock_fail_count" => lock_fail_count})
+ end
+ end # of the try_lock
+
+ else # delta <= sleeptime
+ @hb_received = true # Reset if necessary
+ read_fail_count = 0 # reset
+ lock_fail_count = 0 # reset
+ end # of the if delta > sleeptime
+ rescue Exception => recvex
+ if @logger && @logger.respond_to?(:on_hbread_fail)
+ @logger.on_hbread_fail(log_params, {"ticker_interval" => sleeptime,
+ "exception" => recvex, "read_fail_count" => read_fail_count,
+ "lock_fail_count" => lock_fail_count})
+ end
+ fail_hard = true
+ end
+ # Do we want to attempt a retry?
+ if @reliable
+ # Retry on hard fail or max read fails
+ if fail_hard ||
+ (@max_hbread_fails > 0 && read_fail_count >= @max_hbread_fails)
+ # This is an attempt at a connection retry.
+ @st.kill if @st # Kill the sender thread if one exists
+ _reconn_prep_hb() # Drive reconnection logic
+ Thread.exit # This receiver thread is done
+ end
+ # Retry on max lock fails. Different logic in order to avoid a deadlock.
+ if (@max_hbrlck_fails > 0 && lock_fail_count >= @max_hbrlck_fails)
+ # This is an attempt at a connection retry.
+ begin
+ @socket.close # Attempt a forced close
+ rescue
end
+ @st.kill if @st # Kill the sender thread if one exists
+ Thread.exit # This receiver thread is done
end
- else
- @hb_received = true # Reset if necessary
end
- Thread.pass
- end
- }
+ Thread.pass # Prior to next receive loop
+ #
+ end # of the "while true"
+ } # end of the Thread.new
+ end
+
+ # _reconn_prep_hb prepares for a reconnect retry
+ def _reconn_prep_hb()
+ if @parameters
+ change_host()
+ end
+ @socket = nil
+ used_socket = socket()
end
end # class Connection
diff --git a/lib/connection/netio.rb b/lib/connection/netio.rb
index 320c610..dd08213 100644
--- a/lib/connection/netio.rb
+++ b/lib/connection/netio.rb
@@ -16,7 +16,16 @@ module Stomp
@read_semaphore.synchronize do
line = ''
if @protocol == Stomp::SPL_10 || (@protocol >= Stomp::SPL_11 && !@hbr)
- line = read_socket.gets # The old way
+ if @jruby
+ # Handle JRuby specific behavior.
+ while true
+ line = read_socket.gets # Data from wire
+ break unless line == "\n"
+ line = ''
+ end
+ else
+ line = read_socket.gets # The old way
+ end
else # We are >= 1.1 *AND* receiving heartbeats.
while true
line = read_socket.gets # Data from wire
@@ -28,7 +37,6 @@ module Stomp
return nil if line.nil?
# p [ "wiredatain_01", line ]
line = _normalize_line_end(line) if @protocol >= Stomp::SPL_12
-
# If the reading hangs for more than X seconds, abort the parsing process.
# X defaults to 5. Override allowed in connection hash parameters.
Timeout::timeout(@parse_timeout, Stomp::Error::PacketParsingTimeout) do
@@ -58,15 +66,16 @@ module Stomp
# If the buffer isn't empty, reads trailing new lines.
#
- # Note: experiments with JRuby seem to show that .ready? never
- # returns true. This means that this code to drain trailing new
- # lines never runs using JRuby.
+ # Note: experiments with JRuby seem to show that socket.ready? never
+ # returns true. It appears that in cases where Ruby returns true
+ # that JRuby returns a Fixnum. We attempt to adjust for this
+ # in the _is_ready? method.
#
# Note 2: the draining of new lines must be done _after_ a message
# is read. Do _not_ leave them on the wire and attempt to drain them
# at the start of the next read. Attempting to do that breaks the
# asynchronous nature of the 'poll' method.
- while read_socket.ready?
+ while _is_ready?(read_socket)
last_char = read_socket.getc
break unless last_char
if parse_char(last_char) != "\n"
@@ -74,9 +83,6 @@ module Stomp
break
end
end
- # And so, a JRuby hack. Remove any new lines at the start of the
- # next buffer.
- message_header.gsub!(/^\n?/, "")
if @protocol >= Stomp::SPL_11
@lr = Time.now.to_f if @hbr
@@ -92,6 +98,15 @@ module Stomp
end
end
+ # Check if the socket is ready, i.e. there is data to read.
+ def _is_ready?(s)
+ rdy = s.ready?
+ if @jruby
+ rdy = rdy.class == Fixnum ? true : false
+ end
+ rdy
+ end
+
# Normalize line ends because 1.2+ brokers can send 'mixed mode' headers, i.e.:
# - Some headers end with '\n'
# - Other headers end with '\r\n'
@@ -124,6 +139,8 @@ module Stomp
else
$stderr.print errstr
end
+ # !!! This loop initiates a re-connect !!!
+ _reconn_prep()
end
end
end
@@ -262,7 +279,6 @@ module Stomp
if @ssl.ciphers # User ciphers list?
ctx.ciphers = @ssl.ciphers # Accept user supplied ciphers
else
- ctx.ciphers = Stomp::DEFAULT_CIPHERS # Just use Stomp defaults
end
end
end
@@ -275,6 +291,8 @@ module Stomp
Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
ssl = OpenSSL::SSL::SSLSocket.new(open_tcp_socket, ctx)
+ ssl.hostname = @host if ssl.respond_to? :hostname=
+ ssl.sync_close = true # Sync ssl close with underlying TCP socket
ssl.connect
end
def ssl.ready?
@@ -351,7 +369,9 @@ module Stomp
@disconnect_receipt = nil
@session = @connection_frame.headers["session"] if @connection_frame
# replay any subscriptions.
- @subscriptions.each { |k,v| _transmit(used_socket, Stomp::CMD_SUBSCRIBE, v) }
+ @subscriptions.each {|k,v|
+ _transmit(used_socket, Stomp::CMD_SUBSCRIBE, v)
+ }
end
end # class Connection
diff --git a/lib/connection/utils.rb b/lib/connection/utils.rb
index 64fd8eb..548bf0b 100644
--- a/lib/connection/utils.rb
+++ b/lib/connection/utils.rb
@@ -53,6 +53,7 @@ module Stomp
lparms[:cur_recondelay] = @reconnect_delay
lparms[:cur_parseto] = @parse_timeout
lparms[:cur_conattempts] = @connection_attempts
+ lparms[:cur_failure] = @failure # To assist in debugging
lparms[:openstat] = open?
#
lparms
@@ -122,9 +123,15 @@ module Stomp
rescue
@failure = $!
used_socket = nil
+ @closed = true
+
raise unless @reliable
raise if @failure.is_a?(Stomp::Error::LoggerConnectionError)
- @closed = true
+ # Catch errors which are:
+ # a) emitted from corrupted 1.1+ 'connect' (caller programming error)
+ # b) should never be retried
+ raise if @failure.is_a?(Stomp::Error::ProtocolError11p)
+
if @logger && @logger.respond_to?(:on_connectfail)
# on_connectfail may raise
begin
@@ -172,6 +179,9 @@ module Stomp
:closed_check => true,
:hbser => false,
:stompconn => false,
+ :max_hbread_fails => 0,
+ :max_hbrlck_fails => 0,
+ :fast_hbs_adjust => 0.0,
}
res_params = default_params.merge(params)
@@ -220,6 +230,8 @@ module Stomp
begin
used_socket = socket()
return _receive(used_socket)
+ rescue Stomp::Error::MaxReconnectAttempts
+ raise
rescue
@failure = $!
raise unless @reliable
@@ -229,10 +241,22 @@ module Stomp
else
$stderr.print errstr
end
+ # !!! This initiates a re-connect !!!
+ _reconn_prep()
end
end
end
+ # _reconn_prep prepares for a reconnect retry
+ def _reconn_prep()
+ if @parameters
+ change_host()
+ end
+ @st.kill if @st # Kill ticker thread if any
+ @rt.kill if @rt # Kill ticker thread if any
+ @socket = nil
+ end
+
end # class Connection
end # module Stomp
diff --git a/lib/stomp/client.rb b/lib/stomp/client.rb
index 90ca069..28625c2 100644
--- a/lib/stomp/client.rb
+++ b/lib/stomp/client.rb
@@ -55,6 +55,9 @@ module Stomp
# :hbser => false,
# :stompconn => false,
# :usecrlf => false,
+ # :max_hbread_fails => 0,
+ # :max_hbrlck_fails => 0,
+ # :fast_hbs_adjust => 0.0,
# }
#
# e.g. c = Stomp::Client.new(hash)
@@ -231,6 +234,11 @@ module Stomp
@connection.closed?()
end
+ # jruby? tests if the connection has detcted a JRuby environment
+ def jruby?()
+ @connection.jruby
+ end
+
# close frees resources in use by this client. The listener thread is
# terminated, and disconnect on the connection is called.
def close(headers={})
diff --git a/lib/stomp/connection.rb b/lib/stomp/connection.rb
index 52d5cfd..e20344f 100644
--- a/lib/stomp/connection.rb
+++ b/lib/stomp/connection.rb
@@ -31,6 +31,9 @@ module Stomp
# Heartbeat send has been successful.
attr_reader :hb_sent # Heartbeat sent successfully
+ # JRuby detected
+ attr_reader :jruby
+
# Autoflush forces a flush on each transmit. This may be changed
# dynamically by calling code.
attr_accessor :autoflush
@@ -65,6 +68,9 @@ module Stomp
# :hbser => false,
# :stompconn => false,
# :usecrlf => false,
+ # :max_hbread_fails => 0,
+ # :max_hbrlck_fails => 0,
+ # :fast_hbs_adjust => 0.0,
# }
#
# e.g. c = Stomp::Connection.new(hash)
@@ -85,7 +91,10 @@ module Stomp
@hb_received = true # Assumed at first
@hb_sent = true # Assumed at first
@hbs = @hbr = false # Sending/Receiving heartbeats. Assume no for now.
-
+ @jruby = false # Assumed at first
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/
+ @jruby = true
+ end
if login.is_a?(Hash)
hashed_initialize(login)
else
@@ -106,6 +115,9 @@ module Stomp
@hbser = false # Raise if heartbeat send exception
@stompconn = false # If true, use STOMP rather than CONNECT
@usecrlf = false # If true, use \r\n as line ends (1.2 only)
+ @max_hbread_fails = 0 # 0 means never retry for HB read failures
+ @max_hbrlck_fails = 0 # 0 means never retry for HB read lock failures
+ @fast_hbs_adjust = 0.0 # Fast heartbeat senders sleep adjustment
warn "login looks like a URL, do you have the correct parameters?" if @login =~ /:\/\//
end
@@ -138,6 +150,9 @@ module Stomp
@hbser = @parameters[:hbser]
@stompconn = @parameters[:stompconn]
@usecrlf = @parameters[:usecrlf]
+ @max_hbread_fails = @parameters[:max_hbread_fails]
+ @max_hbrlck_fails = @parameters[:max_hbrlck_fails]
+ @fast_hbs_adjust = @parameters[:fast_hbs_adjust]
#sets the first host to connect
change_host
end
@@ -163,6 +178,9 @@ module Stomp
headers = headers.symbolize_keys
headers[:transaction] = name
_headerCheck(headers)
+ if @logger && @logger.respond_to?(:on_begin)
+ @logger.on_begin(log_params, headers)
+ end
transmit(Stomp::CMD_BEGIN, headers)
end
@@ -193,6 +211,9 @@ module Stomp
headers[:'message-id'] = message_id
end
_headerCheck(headers)
+ if @logger && @logger.respond_to?(:on_ack)
+ @logger.on_ack(log_params, headers)
+ end
transmit(Stomp::CMD_ACK, headers)
end
@@ -216,6 +237,9 @@ module Stomp
raise Stomp::Error::SubscriptionRequiredError unless headers[:subscription]
end
_headerCheck(headers)
+ if @logger && @logger.respond_to?(:on_nack)
+ @logger.on_nack(log_params, headers)
+ end
transmit(Stomp::CMD_NACK, headers)
end
@@ -225,6 +249,9 @@ module Stomp
headers = headers.symbolize_keys
headers[:transaction] = name
_headerCheck(headers)
+ if @logger && @logger.respond_to?(:on_commit)
+ @logger.on_commit(log_params, headers)
+ end
transmit(Stomp::CMD_COMMIT, headers)
end
@@ -234,6 +261,9 @@ module Stomp
headers = headers.symbolize_keys
headers[:transaction] = name
_headerCheck(headers)
+ if @logger && @logger.respond_to?(:on_abort)
+ @logger.on_abort(log_params, headers)
+ end
transmit(Stomp::CMD_ABORT, headers)
end
@@ -270,8 +300,12 @@ module Stomp
headers[:destination] = dest
if @protocol >= Stomp::SPL_11
raise Stomp::Error::SubscriptionRequiredError if (headers[:id].nil? && subId.nil?)
+ headers[:id] = subId unless headers[:id]
end
_headerCheck(headers)
+ if @logger && @logger.respond_to?(:on_unsubscribe)
+ @logger.on_unsubscribe(log_params, headers)
+ end
transmit(Stomp::CMD_UNSUBSCRIBE, headers)
if @reliable
subId = dest if subId.nil?
@@ -302,12 +336,14 @@ module Stomp
options = { :dead_letter_queue => "/queue/DLQ", :max_redeliveries => 6 }.merge(options)
# Lets make sure all keys are symbols
message.headers = message.headers.symbolize_keys
-
retry_count = message.headers[:retry_count].to_i || 0
message.headers[:retry_count] = retry_count + 1
transaction_id = "transaction-#{message.headers[:'message-id']}-#{retry_count}"
message_id = message.headers.delete(:'message-id')
+ # Prevent duplicate 'subscription' headers on subsequent receives
+ message.headers.delete(:subscription) if message.headers[:subscription]
+
begin
self.begin transaction_id
@@ -315,7 +351,7 @@ module Stomp
self.ack(message_id, :transaction => transaction_id)
end
- if retry_count <= options[:max_redeliveries]
+ if message.headers[:retry_count] <= options[:max_redeliveries]
self.publish(message.headers[:destination], message.body,
message.headers.merge(:transaction => transaction_id))
else
@@ -366,10 +402,13 @@ module Stomp
receive()
end
- # receive returns the next Message off of the wire.
+ # receive returns the next Message off of the wire. this can return nil
+ # in cases where:
+ # * the broker has closed the connection
+ # * the connection is not reliable
def receive()
raise Stomp::Error::NoCurrentConnection if @closed_check && closed?
- super_result = __old_receive
+ super_result = __old_receive()
if super_result.nil? && @reliable && !closed?
errstr = "connection.receive returning EOF as nil - resetting connection.\n"
if @logger && @logger.respond_to?(:on_miscerr)
@@ -377,10 +416,22 @@ module Stomp
else
$stderr.print errstr
end
- @socket = nil
- super_result = __old_receive
+ # !!! This initiates a re-connect !!!
+ # The call to __old_receive() will in turn call socket(). Before
+ # that we should change the target host, otherwise the host that
+ # just failed may be attempted first.
+ _reconn_prep()
+ #
+ super_result = __old_receive()
end
#
+ if super_result.nil? && !@reliable
+ @st.kill if @st # Kill ticker thread if any
+ @rt.kill if @rt # Kill ticker thread if any
+ close_socket()
+ @closed = true
+ warn 'warning: broker sent EOF, and connection not reliable' unless defined?(Test)
+ end
if @logger && @logger.respond_to?(:on_receive)
@logger.on_receive(log_params, super_result)
end
diff --git a/lib/stomp/constants.rb b/lib/stomp/constants.rb
index b43e3fb..e6efe55 100644
--- a/lib/stomp/constants.rb
+++ b/lib/stomp/constants.rb
@@ -111,4 +111,11 @@ module Stomp
["EXP-RC4-MD5", "TLSv1/SSLv3", 40, 128],
]
+ # stomp URL regex pattern, for e.g. login:passcode at host:port or host:port
+ URL_REPAT = '((([\w~!@#$%^&*()\-+=.?:<>,.]*\w):([\w~!@#$%^&*()\-+=.?:<>,.]*))?@)?([\w\.\-]+):(\d+)'
+
+ # Failover URL regex, for e.g.
+ #failover:(stomp+ssl://login1:passcode1@remotehost1:61612,stomp://login2:passcode2@remotehost2:61613)
+ FAILOVER_REGEX = /^failover:(\/\/)?\(stomp(\+ssl)?:\/\/#{URL_REPAT}(,stomp(\+ssl)?:\/\/#{URL_REPAT})*\)(\?(.*))?$/
+
end # Module Stomp
diff --git a/lib/stomp/errors.rb b/lib/stomp/errors.rb
index a6f09af..4ed7bb2 100644
--- a/lib/stomp/errors.rb
+++ b/lib/stomp/errors.rb
@@ -71,17 +71,24 @@ module Stomp
end
end
+ # ProtocolError11p - base class of 1.1 CONNECT errors
+ class ProtocolError11p < RuntimeError
+ def message
+ "STOMP 1.1+ CONNECT error"
+ end
+ end
+
# ProtocolErrorConnect is raised if:
- # * Incomplete Stomp 1.1 headers are detectd during a connect.
- class ProtocolErrorConnect < RuntimeError
+ # * Incomplete Stomp 1.1 headers are detected during a connect.
+ class ProtocolErrorConnect < ProtocolError11p
def message
- "protocol error on CONNECT"
+ "STOMP 1.1+ CONNECT error, missing/incorrect CONNECT headers"
end
end
# UnsupportedProtocolError is raised if:
# * No supported Stomp protocol levels are detected during a connect.
- class UnsupportedProtocolError < RuntimeError
+ class UnsupportedProtocolError < ProtocolError11p
def message
"unsupported protocol level(s)"
end
@@ -89,7 +96,7 @@ module Stomp
# InvalidHeartBeatHeaderError is raised if:
# * A "heart-beat" header is present, but the values are malformed.
- class InvalidHeartBeatHeaderError < RuntimeError
+ class InvalidHeartBeatHeaderError < ProtocolError11p
def message
"heart-beat header value is malformed"
end
@@ -189,6 +196,23 @@ module Stomp
class LoggerConnectionError < RuntimeError
end
+ # NilMessageError is raised if:
+ # * Invalid (nil) data is received from the Stomp server in a client's
+ # listener thread, and the connection is not reliable.
+ class NilMessageError < RuntimeError
+ def message
+ "Received message is nil, and connection not reliable"
+ end
+ end
+
+ # MalformedFailoverOptionsError is raised if failover URL
+ # options can not be parsed
+ class MalformedFailoverOptionsError < RuntimeError
+ def message
+ "failover options are malformed"
+ end
+ end
+
end # module Error
end # module Stomp
diff --git a/lib/stomp/version.rb b/lib/stomp/version.rb
index 57facdb..11b5a77 100644
--- a/lib/stomp/version.rb
+++ b/lib/stomp/version.rb
@@ -6,7 +6,7 @@ module Stomp
module Version #:nodoc: all
MAJOR = 1
MINOR = 2
- PATCH = 9
+ PATCH = 14
STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
end
end
diff --git a/metadata.yml b/metadata.yml
index d9c2144..1a50f48 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,15 +1,10 @@
---- !ruby/object:Gem::Specification
+--- !ruby/object:Gem::Specification
name: stomp
-version: !ruby/object:Gem::Version
- hash: 13
- prerelease: false
- segments:
- - 1
- - 2
- - 9
- version: 1.2.9
+version: !ruby/object:Gem::Version
+ version: 1.2.14
+ prerelease:
platform: ruby
-authors:
+authors:
- Brian McCallister
- Marius Mathiesen
- Thiago Morello
@@ -17,37 +12,36 @@ authors:
autorequire:
bindir: bin
cert_chain: []
-
-date: 2013-03-28 00:00:00 -04:00
-default_executable:
-dependencies:
-- !ruby/object:Gem::Dependency
+date: 2013-08-19 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
name: rspec
- prerelease: false
- requirement: &id001 !ruby/object:Gem::Requirement
+ requirement: !ruby/object:Gem::Requirement
none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 5
- segments:
- - 2
- - 3
- version: "2.3"
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: '2.3'
type: :development
- version_requirements: *id001
-description: Ruby client for the Stomp messaging protocol. Note that this gem is no longer supported on rubyforge.
-email:
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: '2.3'
+description: Ruby client for the Stomp messaging protocol. Note that this gem is
+ no longer supported on rubyforge.
+email:
- brianm at apache.org
- marius at stones.com
- morellon at gmail.com
- allard.guy.m at gmail.com
-executables:
+executables:
- catstomp
- stompcat
extensions: []
-
-extra_rdoc_files:
+extra_rdoc_files:
- CHANGELOG.rdoc
- LICENSE
- README.rdoc
@@ -99,8 +93,9 @@ extra_rdoc_files:
- test/test_helper.rb
- test/test_message.rb
- test/test_ssl.rb
+- test/test_urlogin.rb
- test/tlogger.rb
-files:
+files:
- CHANGELOG.rdoc
- LICENSE
- README.rdoc
@@ -148,6 +143,7 @@ files:
- lib/stomp/message.rb
- lib/stomp/sslparams.rb
- lib/stomp/version.rb
+- notes/heartbeat_readme.txt
- spec/client_shared_examples.rb
- spec/client_spec.rb
- spec/connection_spec.rb
@@ -161,40 +157,31 @@ files:
- test/test_helper.rb
- test/test_message.rb
- test/test_ssl.rb
+- test/test_urlogin.rb
- test/tlogger.rb
-has_rdoc: true
homepage: https://github.com/stompgem/stomp
licenses: []
-
post_install_message:
rdoc_options: []
-
-require_paths:
+require_paths:
- lib
-required_ruby_version: !ruby/object:Gem::Requirement
+required_ruby_version: !ruby/object:Gem::Requirement
none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
-required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: '0'
requirements: []
-
rubyforge_project:
-rubygems_version: 1.3.7
+rubygems_version: 1.8.25
signing_key:
specification_version: 3
summary: Ruby client for the Stomp messaging protocol
test_files: []
-
+has_rdoc:
diff --git a/notes/heartbeat_readme.txt b/notes/heartbeat_readme.txt
new file mode 100644
index 0000000..268d79f
--- /dev/null
+++ b/notes/heartbeat_readme.txt
@@ -0,0 +1,169 @@
+Usage notes and comments follow (sorry for the length here, please read carefully).
+
+Write failures: there is a single type of write failure, and it occurs when
+an exception of some sort is raised during Heartbeat write.
+This is the only type of write failure that can be detected.
+If :reliable is true, an exception on heartbeat write will always causes a fail
+over attempt.
+
+Read failures: this is actually more complex than originally envisioned.
+There are really three distinct types of read 'failures':
+
+1) An exception is thrown during Heartbeat read. If :reliable is true, this
+ always causes a fail over attempt.
+
+2) Heartbeat reads can not obtain the read_semaphore lock. This will occur
+ when the main connection thread has:
+ -- Called Connection#receive
+ -- Only heartbeats but no Stomp frames are on the inbound wire
+ -- Last Heartbeat read time is being maintained by the #receive attempt
+
+3) Heartbeat reads obtain the read_semaphore lock, but the socket shows no
+ data is available (by IO#ready?). This will occur when:
+ -- The main thread has not called Connection#receive (the thread is doing other work)
+ -- There is no heartbeat to receive (IO#ready? indicates no input data available)
+
+The requirement to handle cases 2) and 3) results in not one, but two different
+counters being taken in to consideration.
+
+To handle case 2) add to the connect hash:
+
+:max_hbrlck_fails => x # x is a number strictly greater than 0. Default is 0,
+which disables the functionality.
+
+A running count of this failure type is maintained in the HB read thread. The
+count is incremented when:
+
+-- Lock obtain fails, *and*
+-- A heartbeat is 'late'
+
+The count is reset to 0 whenever:
+
+-- A heart beat has been received on a timely basis
+
+When the running count *reaches* the value specified in :max_hbrlck_fails, a
+fail over attempt is initiated.
+
+Advice: do *not* set this to 1 (in order to avoid fail overs on a transient
+error).
+
+To handle case 3) add to the connect hash:
+
+:max_hbread_fails => y # y is a number strictly greater than 0. Default is 0,
+which disables the functionality.
+
+A running count of this failure type is maintained in the HB read thread.
+The count is incremented when:
+
+-- Lock obtain succeeds, *and*
+-- IO#ready? indicates no data available
+
+The count is reset to 0 under two conditions:
+
+Condition 1)
+ -- A heartbeat is late, *and*
+ -- Lock obtain succeeds, *and*
+ -- IO#ready? indicates data is available, *and*
+ -- A single character is read from the wire
+
+Condition 2)
+ -- A heartbeat has been received in a timely manner (perhaps by the main thread)
+
+When the running count *reaches* the value specified in :max_hbread_fails,
+a fail over attempt is initiated.
+
+Advice: do *not* set this to 1 (in order to avoid fail overs on a transient
+error).
+
+-----------------------------------------------------------
+
+General advice:
+
+Set your heartbeat intervals to the maximum possible to obtain your desired
+behavior. Do *not* set them at extremely low values even if the broker allows
+that. An absurd example:
+
+heart-beat:1,1
+
+which will likely not work well.
+
+-----------------------------------------------------------
+
+General notes:
+
+In your real world apps, think about whether one or both of these parameters
+are appropriate.
+
+Please add the:
+
+-- on_hbread_fail
+-- on_hbwrite_fail
+
+methods to a callback logger. In those methods show 'ticker_data.inspect'
+output. We would want that log output in future problem reports.
+
+We make the comment about not setting these values to 1 because every server
+we test with is prone to transient (single event) failures, particularly for
+client HB reads.
+
+We have done a variety of informal tests here, using both server kill and
+packet drop strategies as appropriate. We believe more real world testing is
+required.
+
+-----------------------------------------------------------
+
+08/07/2013
+
+Issue #63 related, specifically fast send heart beats are being used and
+spurious fail overs occur in rapid succession.
+
+Background:
+
+Fail over from heartbeat failures was introduced in gem version 1.2.10.
+
+Subsequently:
+
+This issue has been observed and documented in the following environment:
+
+-- JRuby engine 1.7.4 *and*
+-- ActiveMQ 5.8.0 *and*
+-- 'fast' client send heartbeats
+
+Heartbeat sends were at 2000ms.
+
+At this point in time, fast send heart beats and spurious fail overs have
+*not* been observed using:
+
+-- Any native RUBY_ENGINE and ActiveMQ
+-- Any native RUBY_ENGINE and Apollo (client send rates are limited by default)
+-- Any native RUBY_ENGINE and RabbitMQ
+-- JRuby and Apollo (client send rates are limited by default)
+-- JRuby and RabbitMQ
+
+Note that 'fast' will depend on your use case for heartbeats. Observations
+are that sending heartbeat times less than 5000ms might be considered 'fast'
+in the targeted environment.
+
+The solution / bypass being put in place as of the above date was developed
+through experimentation and is as follows:
+
+- Add 'adjustment' logic to the heartbeat sender (thanks to ppaul for this idea).
+- Re-introduce tolerance logic removed in d922fa.
+- Add a new connection hash parameter to adjust heartbeat sends.
+
+The newly introduced connection hash parameter is:
+
+:fast_hbs_adjust => 0.0 # The default, no adjustment to sender sleep times (sec)
+
+Recommendation for gem users that:
+
+- Use fast send heartbeats
+- Actually notice spurious fail overs
+
+is to provide a very sender sleep time adjustment when connecting. Examples:
+
+:fast_hbs_adjust => 0.05 # 50 milliseconds
+:fast_hbs_adjust => 0.10 # 100 milliseconds
+
+As usual, YMMV.
+
diff --git a/spec/client_spec.rb b/spec/client_spec.rb
index b8721f7..35c5c5b 100644
--- a/spec/client_spec.rb
+++ b/spec/client_spec.rb
@@ -254,7 +254,7 @@ describe Stomp::Client do
end
it "should properly parse a URL with failover:" do
- url = "failover:(stomp://login1:passcode1@localhost:61616,stomp://login2:passcode2@remotehost1:61617),stomp://login3:passcode3@remotehost2:61618)"
+ url = "failover:(stomp://login1:passcode1@localhost:61616,stomp://login2:passcode2@remotehost1:61617,stomp://login3:passcode3@remotehost2:61618)"
@parameters[:hosts] = [
{:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
@@ -283,7 +283,7 @@ describe Stomp::Client do
end
it "should properly parse a URL with user and/or password blank" do
- url = "failover:(stomp://:@localhost:61616,stomp://:@remotehost:61617)"
+ url = "failover:(stomp://@localhost:61616,stomp://@remotehost:61617)"
@parameters[:hosts] = [
{:login => "", :passcode => "", :host => "localhost", :port => 61616, :ssl => false},
@@ -302,7 +302,7 @@ describe Stomp::Client do
url = "failover:(stomp://login1:passcode1@localhost:61616,stomp://login2:passcode2@remotehost:61617)?#{query}"
- #backup and timeout are not implemented yet
+ #
@parameters = {
:initial_reconnect_delay => 5.0,
:max_reconnect_delay => 60.0,
diff --git a/spec/connection_spec.rb b/spec/connection_spec.rb
index 4cae83c..1289764 100644
--- a/spec/connection_spec.rb
+++ b/spec/connection_spec.rb
@@ -25,7 +25,10 @@ describe Stomp::Connection do
:hbser => false,
:stompconn => false,
:usecrlf => false,
- }
+ :max_hbread_fails => 0,
+ :max_hbrlck_fails => 0,
+ :fast_hbs_adjust => 0.0,
+ }
#POG:
class Stomp::Connection
@@ -87,9 +90,12 @@ describe Stomp::Connection do
"backOffMultiplier" => 2,
"maxReconnectAttempts" => 0,
"randomize" => false,
- "connect_timeout" => 0,
- "parse_timeout" => 5,
+ "connectTimeout" => 0,
+ "parseTimeout" => 5,
"usecrlf" => false,
+ :maxHbreadFails => 0,
+ :maxHbrlckFails => 0,
+ :fastHbsAdjust => 0.0,
}
@connection = Stomp::Connection.new(used_hash)
@@ -211,22 +217,23 @@ describe Stomp::Connection do
@message.headers[:retry_count].should == 5
end
- it "should not send the message to the dead letter queue as persistent if redeliveries equal max redeliveries" do
+ it "should not send the message to the dead letter queue as persistent if retry_count is less than max redeliveries" do
max_redeliveries = 5
dead_letter_queue = "/queue/Dead"
- @message.headers["retry_count"] = max_redeliveries
+ @message.headers["retry_count"] = max_redeliveries - 1
transaction_id = "transaction-#{@message.headers["message-id"]}-#{@message.headers["retry_count"]}"
@retry_headers = @retry_headers.merge :transaction => transaction_id, :retry_count => @message.headers["retry_count"] + 1
@connection.should_receive(:publish).with(@message.headers["destination"], @message.body, @retry_headers)
@connection.unreceive @message, :dead_letter_queue => dead_letter_queue, :max_redeliveries => max_redeliveries
end
+ # If the retry_count has reached max_redeliveries, then we're done.
it "should send the message to the dead letter queue as persistent if max redeliveries have been reached" do
max_redeliveries = 5
dead_letter_queue = "/queue/Dead"
- @message.headers["retry_count"] = max_redeliveries + 1
+ @message.headers["retry_count"] = max_redeliveries
transaction_id = "transaction-#{@message.headers["message-id"]}-#{@message.headers["retry_count"]}"
@retry_headers = @retry_headers.merge :persistent => true, :transaction => transaction_id, :retry_count => @message.headers["retry_count"] + 1, :original_destination=> @message.headers["destination"]
@connection.should_receive(:publish).with(dead_letter_queue, @message.body, @retry_headers)
@@ -268,7 +275,9 @@ describe Stomp::Connection do
before(:each) do
ssl_parameters = {:hosts => [{:login => "login2", :passcode => "passcode2", :host => "remotehost", :ssl => true}]}
- @ssl_socket = mock(:ssl_socket, :puts => nil, :write => nil, :setsockopt => nil, :flush => true)
+ @ssl_socket = mock(:ssl_socket, :puts => nil, :write => nil,
+ :setsockopt => nil, :flush => true)
+ @ssl_socket.stub!(:sync_close=)
TCPSocket.should_receive(:open).and_return @tcp_socket
OpenSSL::SSL::SSLSocket.should_receive(:new).and_return(@ssl_socket)
@@ -338,6 +347,9 @@ describe Stomp::Connection do
:closed_check => true,
:hbser => false,
:stompconn => false,
+ :max_hbread_fails => 0,
+ :max_hbrlck_fails => 0,
+ :fast_hbs_adjust => 0.0,
}
used_hash = {
@@ -363,7 +375,6 @@ describe Stomp::Connection do
:back_off_multiplier => 3,
:max_reconnect_attempts => 10,
:randomize => true,
- :backup => false,
:reliable => false,
:connect_timeout => 0,
:parse_timeout => 20,
@@ -372,9 +383,12 @@ describe Stomp::Connection do
:max_redeliveries => 10,
:dmh => false,
:closed_check => true,
- :hbser => false,
- :stompconn => false,
- :usecrlf => false,
+ :hbser => true,
+ :stompconn => true,
+ :usecrlf => true,
+ :max_hbread_fails => 123,
+ :max_hbrlck_fails => 456,
+ :fast_hbs_adjust => 0.2,
}
@connection = Stomp::Connection.new(used_hash)
@@ -423,7 +437,14 @@ describe Stomp::Connection do
@connection.instance_variable_set(:@connection_attempts, limit)
@connection.send(:max_reconnect_attempts?).should be_true
+ end
+
+ # These should be raised for the user to deal with
+ it "should not rescue MaxReconnectAttempts" do
+ @connection = Stomp::Connection.new(@parameters)
+ @connection.stub(:socket).and_raise(Stomp::Error::MaxReconnectAttempts)
+ expect { @connection.receive() }.to raise_error
end
end
diff --git a/stomp.gemspec b/stomp.gemspec
index 494b90f..17111fd 100644
--- a/stomp.gemspec
+++ b/stomp.gemspec
@@ -5,11 +5,11 @@
Gem::Specification.new do |s|
s.name = %q{stomp}
- s.version = "1.2.9"
+ s.version = "1.2.14"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Brian McCallister", "Marius Mathiesen", "Thiago Morello", "Guy M. Allard"]
- s.date = %q{2013-03-28}
+ s.date = %q{2013-08-19}
s.description = %q{Ruby client for the Stomp messaging protocol. Note that this gem is no longer supported on rubyforge.}
s.email = ["brianm at apache.org", "marius at stones.com", "morellon at gmail.com", "allard.guy.m at gmail.com"]
s.executables = ["catstomp", "stompcat"]
@@ -65,6 +65,7 @@ Gem::Specification.new do |s|
"test/test_helper.rb",
"test/test_message.rb",
"test/test_ssl.rb",
+ "test/test_urlogin.rb",
"test/tlogger.rb"
]
s.files = [
@@ -115,6 +116,7 @@ Gem::Specification.new do |s|
"lib/stomp/message.rb",
"lib/stomp/sslparams.rb",
"lib/stomp/version.rb",
+ "notes/heartbeat_readme.txt",
"spec/client_shared_examples.rb",
"spec/client_spec.rb",
"spec/connection_spec.rb",
@@ -128,6 +130,7 @@ Gem::Specification.new do |s|
"test/test_helper.rb",
"test/test_message.rb",
"test/test_ssl.rb",
+ "test/test_urlogin.rb",
"test/tlogger.rb"
]
s.homepage = %q{https://github.com/stompgem/stomp}
diff --git a/test/test_client.rb b/test/test_client.rb
index 7a258f6..756fbdf 100644
--- a/test/test_client.rb
+++ b/test/test_client.rb
@@ -23,7 +23,7 @@ class TestClient < Test::Unit::TestCase
end
def teardown
- @client.close if @client.open? # allow tests to close
+ @client.close if @client && @client.open? # allow tests to close
end
# Test poll works.
@@ -50,7 +50,7 @@ class TestClient < Test::Unit::TestCase
sleep 0.01 until receipt
assert_not_nil receipt.headers['receipt-id']
checkEmsg(@client)
- end unless ENV['STOMP_RABBIT'] # TODO: why does Rabbit 1.1 fail ?
+ end
# Test Client subscribe
def test_asynch_subscribe
@@ -160,61 +160,133 @@ class TestClient < Test::Unit::TestCase
end unless RUBY_ENGINE =~ /jruby/
# Test transaction publish and abort, receive with new client.
- def test_transaction_ack_rollback_with_new_client
- @client.publish make_destination, message_text
+ # New client uses ack => client.
+ def test_tran_ack_abrt_newcli_cli
+ @client.close if @client && @client.open? # allow tests to close
+ @client = get_client()
+ q = make_destination
+ data = message_text
+ @client.publish q, data
@client.begin 'tx1'
message = nil
sid = nil
if @client.protocol() == Stomp::SPL_10
- @client.subscribe(make_destination, :ack => 'client') {|m| message = m}
- else
+ @client.subscribe(q, :ack => 'client') {|m| message = m}
+ else # 1.1 and 1.2 are the same for this
sid = @client.uuid()
- @client.subscribe(make_destination, :ack => 'client', :id => sid) {|m| message = m}
+ @client.subscribe(q, :ack => 'client', :id => sid) {|m| message = m}
end
sleep 0.01 until message
- assert_equal message_text, message.body
+ assert_equal data, message.body
assert_nothing_raised {
- if @client.protocol() == Stomp::SPL_10
- @client.acknowledge message, :transaction => 'tx1'
- else
- @client.acknowledge message, :transaction => 'tx1', :subscription => sid
+ case @client.protocol()
+ when Stomp::SPL_10
+ @client.acknowledge message, :transaction => 'tx1'
+ checkEmsg(@client)
+ when Stomp::SPL_11
+ @client.acknowledge message, :transaction => 'tx1', :subscription => message.headers['subscription']
+ checkEmsg(@client)
+ else # 1.2+
+ @client.acknowledge message, :transaction => 'tx1', :id => message.headers['ack']
+ checkEmsg(@client)
end
- message = nil
- @client.abort 'tx1'
+ message = nil # reset
+ @client.abort 'tx1' # now abort
}
checkEmsg(@client)
# lets recreate the connection
- teardown
- setup
+ @client.close
+ @client = get_client()
sid = nil
+ message2 = nil
+ @client.begin 'tx2'
assert_nothing_raised {
if @client.protocol() == Stomp::SPL_10
- @client.subscribe(make_destination, :ack => 'client') {|m| message = m}
- else
+ @client.subscribe(q, :ack => 'client') {|m| message2 = m}
+ else # 1.1 and 1.2 are the same for this
sid = @client.uuid()
- @client.subscribe(make_destination, :ack => 'client', :id => sid) {|m| message = m}
+ @client.subscribe(q, :ack => 'client', :id => sid) {|m| message2 = m}
end
}
- Timeout::timeout(4) do
- sleep 0.01 until message
- end
+ sleep 0.01 until message2
assert_not_nil message
- assert_equal message_text, message.body
+ assert_equal data, message2.body
assert_nothing_raised {
- @client.begin 'tx2'
case @client.protocol()
when Stomp::SPL_10
- @client.acknowledge message, :transaction => 'tx2'
+ @client.acknowledge message2, :transaction => 'tx2'
+ checkEmsg(@client)
when Stomp::SPL_11
- @client.acknowledge message, :transaction => 'tx2', :subscription => sid
- else
- # Skip 1.2+ for now. Current 1.2 broker appears to think this is
- # already ACK'd.
+ @client.acknowledge message2, :transaction => 'tx2', :subscription => message2.headers['subscription']
+ checkEmsg(@client)
+ else # 1.2+
+ @client.acknowledge message2, :transaction => 'tx2', :id => message2.headers['ack']
+ checkEmsg(@client)
end
@client.commit 'tx2'
}
checkEmsg(@client)
+ @client.close
+ end
+
+ # Test transaction publish and abort, receive with new client.
+ # New client uses ack => auto.
+ def test_tran_ack_abrt_newcli_auto
+ @client.close if @client && @client.open? # allow tests to close
+ @client = get_client()
+ q = make_destination
+ data = message_text
+ @client.publish q, data
+
+ @client.begin 'tx1'
+ message = nil
+ sid = nil
+ if @client.protocol() == Stomp::SPL_10
+ @client.subscribe(q, :ack => 'client') {|m| message = m}
+ else # 1.1 and 1.2 are the same for this
+ sid = @client.uuid()
+ @client.subscribe(q, :ack => 'client', :id => sid) {|m| message = m}
+ end
+ sleep 0.01 until message
+ assert_equal data, message.body
+ assert_nothing_raised {
+ case @client.protocol()
+ when Stomp::SPL_10
+ @client.acknowledge message, :transaction => 'tx1'
+ checkEmsg(@client)
+ when Stomp::SPL_11
+ @client.acknowledge message, :transaction => 'tx1', :subscription => message.headers['subscription']
+ checkEmsg(@client)
+ else # 1.2+
+ @client.acknowledge message, :transaction => 'tx1', :id => message.headers['ack']
+ checkEmsg(@client)
+ end
+ message = nil # reset
+ @client.abort 'tx1' # now abort
+ }
+ checkEmsg(@client)
+ # lets recreate the connection
+ @client.close
+
+ @client = get_client()
+ sid = nil
+ message2 = nil
+ @client.begin 'tx2'
+ assert_nothing_raised {
+ if @client.protocol() == Stomp::SPL_10
+ @client.subscribe(q, :ack => 'auto') {|m| message2 = m}
+ else # 1.1 and 1.2 are the same for this
+ sid = @client.uuid()
+ @client.subscribe(q, :ack => 'auto', :id => sid) {|m| message2 = m}
+ end
+ }
+ sleep 0.01 until message2
+ assert_not_nil message2
+ assert_equal data, message2.body
+ @client.commit 'tx2'
+ checkEmsg(@client)
+ @client.close
end
# Test that subscription destinations must be unique for a Client.
@@ -313,55 +385,65 @@ class TestClient < Test::Unit::TestCase
checkEmsg(@client)
end unless ENV['STOMP_NOWILD'] || ENV['STOMP_DOTQUEUE']
- # Test transaction with client side redilivery.
- def test_transaction_with_client_side_redelivery
- @client.publish make_destination, message_text
+ # Test transaction with client side reacknowledge.
+ def test_transaction_with_client_side_reack
+ @client.close if @client && @client.open? # allow tests to close
+ @client = get_client()
+ q = make_destination
+ data = message_text
+ @client.publish q, data
@client.begin 'tx1'
message = nil
sid = nil
if @client.protocol() == Stomp::SPL_10
- @client.subscribe(make_destination, :ack => 'client') { |m| message = m }
+ @client.subscribe(q, :ack => 'client') { |m| message = m }
else
sid = @client.uuid()
- @client.subscribe(make_destination, :ack => 'client', :id => sid) { |m| message = m }
+ @client.subscribe(q, :ack => 'client', :id => sid) { |m| message = m }
end
-
sleep 0.1 while message.nil?
-
- assert_equal message_text, message.body
- if @client.protocol() == Stomp::SPL_10
- @client.acknowledge message, :transaction => 'tx1'
- else
- @client.acknowledge message, :transaction => 'tx1', :subscription => sid
+ assert_equal data, message.body
+ case @client.protocol()
+ when Stomp::SPL_10
+ @client.acknowledge message, :transaction => 'tx1'
+ checkEmsg(@client)
+ when Stomp::SPL_11
+ @client.acknowledge message, :transaction => 'tx1', :subscription => message.headers['subscription']
+ checkEmsg(@client)
+ else # 1.2+
+ @client.acknowledge message, :transaction => 'tx1', :id => message.headers['ack']
+ checkEmsg(@client)
end
message = nil
@client.abort 'tx1'
-
+ # Wait for redlivery (Client logic)
sleep 0.1 while message.nil?
-
assert_not_nil message
- assert_equal message_text, message.body
-
+ assert_equal data, message.body
assert_nothing_raised {
@client.begin 'tx2'
case @client.protocol()
when Stomp::SPL_10
@client.acknowledge message, :transaction => 'tx2'
+ checkEmsg(@client)
when Stomp::SPL_11
- @client.acknowledge message, :transaction => 'tx2', :subscription => sid
- else
- # Skip 1.2+ for now. Current 1.2 broker appears to think this is
- # already ACK'd.
+ @client.acknowledge message, :transaction => 'tx2', :subscription => message.headers['subscription']
+ checkEmsg(@client)
+ else # 1.2+
+ @client.acknowledge message, :transaction => 'tx2', :id => message.headers['ack']
+ checkEmsg(@client)
end
@client.commit 'tx2'
}
checkEmsg(@client)
+ @client.close
+ @client = nil
end
# Test that a connection frame is received.
def test_connection_frame
- assert_not_nil @client.connection_frame
+ assert_not_nil @client.connection_frame
checkEmsg(@client)
end unless RUBY_ENGINE =~ /jruby/
@@ -523,6 +605,41 @@ class TestClient < Test::Unit::TestCase
end
end
+ # test JRuby detection
+ def test_jruby_presence
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/
+ assert @client.jruby?
+ else
+ assert !@client.jruby?
+ end
+ end
+
+ # test max redeliveries is not broken (6c2c1c1)
+ def test_max_redeliveries
+ @client.close
+ rdmsg = "To Be Redelivered"
+ dest = make_destination
+ [1, 2, 3].each do |max_re|
+ @client = get_client()
+ sid = @client.uuid()
+ received = nil
+ rm_actual = 0
+ sh = @client.protocol() == Stomp::SPL_10 ? {} : {:id => sid}
+ @client.subscribe(dest, sh) {|msg|
+ rm_actual += 1
+ @client.unreceive(msg, :max_redeliveries => max_re)
+ received = msg if rm_actual - 1 == max_re
+ }
+ @client.publish(dest, rdmsg)
+ sleep 0.01 until received
+ assert_equal rdmsg, received.body
+ sleep 0.5
+ @client.unsubscribe dest, sh
+ assert_equal max_re, rm_actual - 1
+ @client.close
+ end
+ end
+
private
def message_text
name = caller_method_name unless name
diff --git a/test/test_connection.rb b/test/test_connection.rb
index 3a08b62..5659186 100644
--- a/test/test_connection.rb
+++ b/test/test_connection.rb
@@ -115,7 +115,7 @@ class TestConnection < Test::Unit::TestCase
# matching the message-id for the MESSAGE being acknowledged and
# subscription, which MUST be set to match the value of the subscription's
# id header.
- @conn.ack msg.headers['message-id'], :subscription => sid
+ @conn.ack msg.headers['message-id'], :subscription => msg.headers['subscription']
}
checkEmsg(@conn)
end
@@ -484,10 +484,11 @@ class TestConnection < Test::Unit::TestCase
def test_conn10_simple
@conn.disconnect
#
+ vhost = ENV['STOMP_RABBIT'] ? "/" : host
hash = { :hosts => [
{:login => user, :passcode => passcode, :host => host, :port => port, :ssl => false},
],
- :connect_headers => {"accept-version" => "1.0", "host" => host},
+ :connect_headers => {"accept-version" => "1.0", "host" => vhost},
:reliable => false,
}
c = nil
@@ -499,7 +500,7 @@ class TestConnection < Test::Unit::TestCase
hash = { :hosts => [
{:login => user, :passcode => passcode, :host => host, :port => port, :ssl => false},
],
- :connect_headers => {"accept-version" => "3.14159,1.0,12.0", "host" => host},
+ :connect_headers => {"accept-version" => "3.14159,1.0,12.0", "host" => vhost},
:reliable => false,
}
c = nil
@@ -509,5 +510,14 @@ class TestConnection < Test::Unit::TestCase
c.disconnect if c
end
+ # test JRuby detection
+ def test_jruby_presence
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/
+ assert @conn.jruby
+ else
+ assert !@conn.jruby
+ end
+ end
+
end
diff --git a/test/test_connection1p.rb b/test/test_connection1p.rb
index a8c22ed..d149cb6 100644
--- a/test/test_connection1p.rb
+++ b/test/test_connection1p.rb
@@ -28,7 +28,7 @@ class TestConnection1P < Test::Unit::TestCase
assert @conn.open?
end
- # Test missing connect headers.
+ # Test missing connect headers - part 1.
def test_conn_1p_0010
@conn.disconnect
#
@@ -43,6 +43,33 @@ class TestConnection1P < Test::Unit::TestCase
end
end
+ # Test missing connect headers - part 2.
+ def test_conn_1p_0015
+ @conn.disconnect
+ #
+ cha = {:host => "localhost"}
+ hash = { :hosts => [
+ {:login => user, :passcode => passcode, :host => host, :port => port, :ssl => nil},
+ ],
+ :reliable => true, # Note, issue #57 discussion
+ :connect_headers => cha,
+ :stompconn => get_stomp_conn(),
+ :usecrlf => get_crlf(),
+ :initial_reconnect_delay => 0.1,
+ :max_reconnect_delay => 30,
+ :use_exponential_back_off => true,
+ :back_off_multiplier => 2,
+ :max_reconnect_attempts => 10,
+ }
+ assert_raise Stomp::Error::ProtocolErrorConnect do
+ conn = Stomp::Connection.open(hash)
+ end
+ hash[:connect_headers] = {"accept-version" => "1.1"}
+ assert_raise Stomp::Error::ProtocolErrorConnect do
+ conn = Stomp::Connection.open(hash)
+ end
+ end
+
# Test requesting only a 1.0 connection.
def test_conn_1p_0020
@conn.disconnect
diff --git a/test/test_helper.rb b/test/test_helper.rb
index b422519..f1ee9df 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -84,7 +84,7 @@ module TestBase
# Get a Stomp SSL Connection.
def get_ssl_connection()
ch = get_conn_headers()
- ssl_params = Stomp::SSLParams.new # S/B safe for all Ruby versions tested
+ ssl_params = Stomp::SSLParams.new(:use_ruby_ciphers => jruby?())
hash = { :hosts => [
{:login => user, :passcode => passcode, :host => host, :port => ssl_port, :ssl => ssl_params},
],
@@ -164,5 +164,10 @@ module TestBase
end
end
+ # Check for JRuby before a connection exists
+ def jruby?()
+ jr = defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ ? true : false
+ end
+
end
diff --git a/test/test_urlogin.rb b/test/test_urlogin.rb
new file mode 100644
index 0000000..1f50c15
--- /dev/null
+++ b/test/test_urlogin.rb
@@ -0,0 +1,86 @@
+# -*- encoding: utf-8 -*-
+
+if Kernel.respond_to?(:require_relative)
+ require_relative("test_helper")
+else
+ $:.unshift(File.dirname(__FILE__))
+ require 'test_helper'
+end
+
+=begin
+
+ Main class for testing Stomp::Client URL based Logins.
+
+=end
+class TestURLLogins < Test::Unit::TestCase
+ include TestBase
+
+ def setup
+ hostname = host()
+ portnum = port()
+ sslpn = ssl_port()
+ @tdstomp = [
+ "stomp://guestl:guestp@#{hostname}:#{portnum}",
+ "stomp://#{hostname}:#{portnum}",
+ "stomp://@#{hostname}:#{portnum}",
+ "stomp://f@#$$%^&*()_+=o.o:@#{hostname}:#{portnum}",
+ 'stomp://f@#$$%^&*()_+=o.o::b~!@#$%^&*()+-_=?:<>,.@@' + hostname + ":#{portnum}",
+ ]
+ @tdfailover = [
+ "failover://(stomp://#{hostname}:#{portnum})",
+ "failover://(stomp+ssl://#{hostname}:#{sslpn})",
+ "failover://(stomp://#{hostname}:#{portnum})",
+ "failover://(stomp://#{hostname}:#{portnum})?whatup=doc&coyote=kaboom",
+ "failover://(stomp://#{hostname}:#{portnum})?whatup=doc",
+ "failover://(stomp://#{hostname}:#{portnum})?whatup=doc&coyote=kaboom&randomize=true",
+ 'failover://(stomp://f@#$$%^&*()_+=o.o::b~!@#$%^&*()+-_=?:<>,.@@' + "localhost" + ":#{portnum}" + ")",
+ 'failover://(stomp://f@#$$%^&*()_+=o.o::b~!@#$%^&*()+-_=:<>,.@@' + "localhost" + ":#{portnum}" + ")",
+ 'failover://(stomp://f@#$$%^&*()_+=o.o::b~!@#$%^&*()+-_=?:<>,.@@' + "localhost" + ":#{portnum}" + ")?a=b",
+ 'failover://(stomp://f@#$$%^&*()_+=o.o::b~!@#$%^&*()+-_=:<>,.@@' + "localhost" + ":#{portnum}" + ")?c=d&e=f",
+ "failover://(stomp://usera:passa@#{hostname}:#{portnum})",
+ "failover://(stomp+ssl://usera:passa@#{hostname}:#{sslpn})",
+ "failover://(stomp://usera:@#{hostname}:#{portnum})",
+ "failover://(stomp://#{hostname}:#{portnum},stomp://#{hostname}:#{portnum})",
+ "failover://(stomp://usera:passa@#{hostname}:#{portnum},stomp://#{hostname}:#{portnum})",
+ "failover://(stomp://usera:@#{hostname}:#{portnum},stomp://#{hostname}:#{portnum})",
+ "failover://(stomp://usera:@#{hostname}:#{portnum},stomp+ssl://#{hostname}:#{sslpn})",
+ "failover://(stomp://#{hostname}:#{portnum},stomp://#{hostname}:#{portnum})?a=b&c=d",
+ "failover://(stomp://#{hostname}:#{portnum},stomp://#{hostname}:#{portnum})?a=b&c=d&connect_timeout=2020",
+ ]
+
+ @badparms = "failover://(stomp://#{hostname}:#{portnum})?a=b&noequal"
+ end
+
+ def teardown
+ @client.close if @client && @client.open? # allow tests to close
+ end
+
+ # test stomp:// URLs
+ def test_0010_stomp_urls()
+ @tdstomp.each_with_index do |url, ndx|
+ c = Stomp::Client.new(url)
+ assert !c.nil?, url
+ assert c.open?, url
+ c.close
+ end
+ end
+
+ # test failover:// urls
+ def test_0020_failover_urls()
+ @tdfailover.each_with_index do |url, ndx|
+ c = Stomp::Client.new(url)
+ assert !c.nil?, url
+ assert c.open?, url
+ c.close
+ end
+ end
+
+ # test failover:// with bad parameters
+ def test_0020_failover_badparms()
+ assert_raise(Stomp::Error::MalformedFailoverOptionsError) {
+ c = Stomp::Client.new(@badparms)
+ }
+ end
+
+end unless ENV['STOMP_RABBIT']
+
diff --git a/test/tlogger.rb b/test/tlogger.rb
index c6ea91f..99231a4 100644
--- a/test/tlogger.rb
+++ b/test/tlogger.rb
@@ -6,6 +6,8 @@ require 'logger' # use the standard Ruby logger .....
Callback logger for Stomp 1.1+ heartbeat tests.
+See the examples directory for a more robust logger example.
+
=end
class Tlogger
@@ -31,7 +33,7 @@ class Tlogger
def on_hbwrite_fail(parms, ticker_data)
begin
@log.debug "Hbwritef Parms #{info(parms)}"
- @log.debug "Hbwritef Result #{ticker_data}"
+ @log.debug "Hbwritef Result #{ticker_data.inspect}"
rescue
@log.debug "Hbwritef oops"
end
@@ -42,18 +44,18 @@ class Tlogger
def on_hbread_fail(parms, ticker_data)
begin
@log.debug "Hbreadf Parms #{info(parms)}"
- @log.debug "Hbreadf Result #{ticker_data}"
+ @log.debug "Hbreadf Result #{ticker_data.inspect}"
rescue
@log.debug "Hbreadf oops"
end
end
# Stomp 1.1+ - heart beat thread fires
- def on_hbfire(parms, type, time)
+ def on_hbfire(parms, type, firedata)
begin
@log.debug "HBfire #{type} " + "=" * 30
@log.debug "HBfire #{type} Parms #{info(parms)}"
- @log.debug "HBfire #{type} Time #{time}"
+ @log.debug "HBfire #{type} Firedata #{firedata.inspect}"
rescue
@log.debug "HBfire #{type} oops"
end
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-stomp.git
More information about the Pkg-ruby-extras-commits
mailing list