stomp.py-4.1.19/0000755000175000017500000000000013171175615012746 5ustar sophiesophiestomp.py-4.1.19/Makefile0000644000175000017500000000417313171175615014413 0ustar sophiesophiePYTHON:=`which python` DESTDIR=/ PROJECT=stomp.py PYTHON_VERSION_MAJOR:=$(shell $(PYTHON) -c "import sys;print(sys.version_info[0])") PLATFORM := $(shell uname) ifeq ($(PYTHON_VERSION_MAJOR), 3) travistests: travistests-py3 else travistests: travistests-py2 endif all: @echo "make source - Create source package" @echo "make install - Install on local system" @echo "make buildrpm - Generate a rpm package" @echo "make builddeb - Generate a deb package" @echo "make clean - Get rid of scratch and byte files" .PHONY: docs docs: cd docs && make html source: $(PYTHON) setup.py sdist $(COMPILE) install: $(PYTHON) setup.py install --root $(DESTDIR) $(COMPILE) test: travistests $(PYTHON) setup.py test --test=cli_ssl_test,multicast_test,ssl_test,local_test travistests: $(PYTHON) setup.py test --test=basic_test,ss_test,cli_test,s10_test,s11_test,s12_test,rabbitmq_test,stompserver_test,misc_test,transport_test,utils_test $(PYTHON) setup.py piptest travistests-py2: $(PYTHON) setup.py test --test=p2_nonascii_test,p2_backward_test travistests-py3: $(PYTHON) setup.py test --test=p3_nonascii_test,p3_backward_test buildrpm: $(PYTHON) setup.py bdist_rpm --post-install=rpm/postinstall --pre-uninstall=rpm/preuninstall builddeb: # build the source package in the parent directory # then rename it to project_version.orig.tar.gz $(PYTHON) setup.py sdist $(COMPILE) --dist-dir=../ --prune rename -f 's/$(PROJECT)-(.*)\.tar\.gz/$(PROJECT)_$$1\.orig\.tar\.gz/' ../* # build the package dpkg-buildpackage -i -I -rfakeroot haproxy: openssl req -x509 -newkey rsa:2048 -keyout tmp/key1.pem -out tmp/cert1.pem -days 365 -nodes -subj "/CN=my.example.org" openssl req -x509 -newkey rsa:2048 -keyout tmp/key2.pem -out tmp/cert2.pem -days 365 -nodes -subj "/CN=my.example.com" cat tmp/cert1.pem tmp/key1.pem > tmp/myorg.pem cat tmp/cert2.pem tmp/key2.pem > tmp/mycom.pem /usr/sbin/haproxy -f stomp/test/haproxy.cfg clean: ifeq ($(PLATFORM),Linux) $(MAKE) -f $(CURDIR)/debian/rules clean endif $(PYTHON) setup.py clean rm -rf build/ MANIFEST dist/ find . -name '*.pyc' -delete release: $(PYTHON) setup.py clean install sdist bdist_wheel uploadstomp.py-4.1.19/LICENSE0000644000175000017500000002613613171175615013763 0ustar sophiesophie Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. stomp.py-4.1.19/README0000644000175000017500000000201213171175615013621 0ustar sophiesophie"stomp.py" is a Python client library for accessing messaging servers (such as ActiveMQ, Apollo or RabbitMQ) using the `STOMP protocol `_ (versions `1.0 `_, `1.1 `_ and `1.2 `_). It can also be run as a standalone, command-line client for testing. A basic example of using stomp.py can be found `here `_. More info can be found on `GitHub `_. Select: - `Version 4.0+ `_ for both Python2.x and Python3.x, with support for STOMP 1.2 (note this version separates the transport mechanism from the protocol) - `Version 3.0+ `_ for both Python2.x and Python3.x (STOMP 1.0 and 1.1 only) - `Version 2.0.x `_ for Python2.xstomp.py-4.1.19/setup.py0000755000175000017500000000620513171175615014466 0ustar sophiesophie#!/usr/bin/env python from distutils.core import Command from setuptools import setup import platform import logging.config import os import shutil import sys import unittest try: logging.config.fileConfig('stomp.log.conf') except: pass # Import this after configuring logging import stomp class TestCommand(Command): user_options = [('test=', 't', 'specific test to run')] def initialize_options(self): self.test = '*' def finalize_options(self): pass def run(self): try: import coverage cov = coverage.coverage() cov.start() except ImportError: cov = None suite = unittest.TestSuite() import stomp.test if self.test == '*': print('Running all tests') tests = stomp.test.__all__ else: tests = self.test.split(',') for tst in tests: suite.addTests(unittest.TestLoader().loadTestsFromName('stomp.test.%s' % tst)) runner = unittest.TextTestRunner(verbosity=2) res = runner.run(suite) if len(res.errors) > 0 or len(res.failures) > 0: sys.exit(1) if cov: cov.stop() cov.save() cov.html_report(directory='../stomppy-docs/htmlcov') class TestPipInstallCommand(Command): user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): if sys.hexversion <= 33950192 or \ (platform.python_implementation() == 'PyPy' and sys.version_info.major == 3 and sys.version_info.minor < 3): # spurious failure on py2.6, so drop out here # also failing on pypy3 <3.2 return if os.path.exists('tmp'): shutil.rmtree('tmp') os.mkdir('tmp') from virtualenvapi.manage import VirtualEnvironment env = VirtualEnvironment('tmp/scratch') env.install('stomp.py') class DoxygenCommand(Command): user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): os.system('doxygen config.dox') def version(): s = [] for num in stomp.__version__: s.append(str(num)) return '.'.join(s) setup( name='stomp.py', version=version(), description='Python STOMP client, supporting versions 1.0, 1.1 and 1.2 of the protocol', license='Apache', url='https://github.com/jasonrbriggs/stomp.py', author='Jason R Briggs', author_email='jasonrbriggs@gmail.com', platforms=['any'], install_requires = ['docopt>=0.6.2'], packages=['stomp', 'stomp.adapter'], cmdclass={'test': TestCommand, 'docs': DoxygenCommand, 'piptest': TestPipInstallCommand}, entry_points={ 'console_scripts': [ 'stomp = stomp.__main__:main', ], }, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3' ] ) stomp.py-4.1.19/README.md0000644000175000017500000000444013171175615014227 0ustar sophiesophieREADME ====== "stomp.py" is a Python client library for accessing messaging servers (such as ActiveMQ, Apollo or RabbitMQ) using the [STOMP protocol](http://stomp.github.io) (versions [1.0](http://stomp.github.io/stomp-specification-1.0.html), [1.1](http://stomp.github.io/stomp-specification-1.1.html) and [1.2](http://stomp.github.io/stomp-specification-1.2.html)). It can also be run as a standalone, command-line client for testing. Quick Start ----------- A basic example of using stomp.py can be found [here](https://github.com/jasonrbriggs/stomp.py/wiki/Simple-Example). Testing via the command-line interface is described [here](https://github.com/jasonrbriggs/stomp.py/wiki/Command-Line-Access). Downloads can be found on [PyPi](https://pypi.python.org/pypi/stomp.py). API documentation can be found [here](http://jasonrbriggs.github.io/stomp.py/index.html). Current test coverage can be found [here](http://jasonrbriggs.github.io/stomp.py/htmlcov/). [Version 4.0+](https://pypi.python.org/pypi/stomp.py) is for both Python2.x and Python3.x - with support for versions 1.0, 1.1 and 1.2 of the protocol. Legacy clients using the old 3-series code, can find the download for 3.1.7 [here](https://pypi.python.org/pypi/stomp.py/3.1.7) (github branch [here](https://github.com/jasonrbriggs/stomp.py/tree/stomppy-3series)) stomp.py has been perfunctorily tested on: [ActiveMQ](http://activemq.apache.org/), [Apollo](http://activemq.apache.org/apollo/), [RabbitMQ](http://www.rabbitmq.com), [stompserver](http://stompserver.rubyforge.org), and has been reported to work with [JBossMessaging](http://www.jboss.org/jbossmessaging) in the distant past. For more info on setting up a test server (using virtualbox), contact the developer. Contributors (pre-github) ------------------------- Julian Scheid ([Rising Sun Pictures](http://open.rsp.com.au/)) Andreas Schobel Fernando Ciciliati Eugene Strulyov Gavin M. Roy Martin Pieuchot Joe Gdaniec Jayson Vantuyl Tatiana Al-Chueyr Martins Rafael Durán Casteñada Chaskiel Grundman Ville Skyttä Pavel Savchenko Project Status -------------- [![PyPI version](https://badge.fury.io/py/stomp.py.svg)](https://badge.fury.io/py/stomp.py) [![Build Status](https://travis-ci.org/jasonrbriggs/stomp.py.svg)](https://travis-ci.org/jasonrbriggs/stomp.py) stomp.py-4.1.19/.travis.yml0000644000175000017500000000126513171175615015063 0ustar sophiesophiesudo: required language: python python: - "2.6" - "2.7" - "3.3" - "3.4" - "3.5" - "pypy-5.4" services: - rabbitmq addons: apt: packages: - stompserver env: - RABBITMQ_HOST=localhost RABBITMQ_PORT=61613 RABBITMQ_USER=guest RABBITMQ_PASSWORD=guest STOMPSERVER_HOST=localhost STOMPSERVER_PORT=63613 STD_HOST=localhost STD_PORT=61613 STD_USER=guest STD_PASSWORD=guest STD_VHOST=/ install: - sudo service stompserver stop - sudo rabbitmq-plugins enable rabbitmq_stomp - sudo sed -i -e s/61613/$STOMPSERVER_PORT/ /etc/stompserver/stompserver.conf - sudo service stompserver start - pip install virtualenv-api - pip install docopt script: - make travistests stomp.py-4.1.19/setup.cfg0000644000175000017500000000020313171175615014562 0ustar sophiesophie[bdist_wheel] universal = 1 [flake8] ignore = E265,E501,F401,F403,F405,H301,H404,H405 import-order-style = google exclude = build stomp.py-4.1.19/docs/0000755000175000017500000000000013171175615013676 5ustar sophiesophiestomp.py-4.1.19/docs/Makefile0000644000175000017500000001642713171175615015350 0ustar sophiesophie# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build HTMLBUILDDIR = ../../stomppy-docs # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(HTMLBUILDDIR) @echo @echo "Build finished. The HTML pages are in $(HTMLBUILDDIR)." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Stomp.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Stomp.qhc" applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Stomp" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Stomp" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." stomp.py-4.1.19/docs/source/0000755000175000017500000000000013171175615015176 5ustar sophiesophiestomp.py-4.1.19/docs/source/conf.py0000755000175000017500000002216213171175615016503 0ustar sophiesophie#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Stomp documentation build configuration file, created by # sphinx-quickstart on Sun Sep 20 16:35:36 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os import shlex # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.join(os.path.abspath('.'), '..', '..')) import stomp import stomp.connect # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Stomp' copyright = '2015, Jason R Briggs' author = 'Jason R Briggs' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '.'.join(map(str, stomp.__version__)) # The full version, including alpha/beta/rc tags. release = version + '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['**test**'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. #html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'Stompdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'Stomp.tex', 'Stomp Documentation', 'Jason R Briggs', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'stomp', 'Stomp Documentation', [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'Stomp', 'Stomp Documentation', author, 'Stomp', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False stomp.py-4.1.19/docs/source/intro.rst0000644000175000017500000000413713171175615017070 0ustar sophiesophie======================== Introduction to Stomp.py ======================== About Stomp.py -------------- Stomp.py started as an `"itch-scratching" project `_, after discovering that the message broker we were using for inter-application communications in a telecommunications platform, had a text-based protocol called `STOMP `_ you could use for access. We wanted something which could randomly send a variation of messages, easily scriptable - and there was only one other Python-based client library available at the time (which didn't work, and it looked as if the project had stalled). So after a number of evenings spent coding (ah! those were the days - when one could get away with endless coding in the evenings), the first version of stomp.py was created (supporting the basics of the 1.0 protocol, a smidgen of a CLI, and little else). In the 8 or 9 years since its inception, support for the subsequent versions of STOMP have been added, and the command line client has been significantly enhanced. * Stomp.py currently supports all versions of the stomp protocol (1.0, 1.1 and 1.2) * Both Python 2 and Python 3 are supported * The command-line client is installed via pip and has a number of useful features for testing * The code is perfunctorily tested on: ActiveMQ, RabbitMQ, stompserver, and has been reported to work with JBossMessaging in the distant past. Full test suite runs against Apache Apollo (for info on setting up VirtualBox for testing, contact the developer). Getting Help ------------ View outstanding issues on the GitHub `issues list `_, or raise a request for help (note that stomp.py is 'intermittently' supported at times). Contributors ------------ Julian Scheid (`Rising Sun Pictures `_) Andreas Schobel Fernando Ciciliati Eugene Strulyov Gavin M. Roy Martin Pieuchot Joe Gdaniec Jayson Vantuyl Tatiana Al-Chueyr Martins Rafael Durán Casteñada Chaskiel Grundman Ville Skyttästomp.py-4.1.19/docs/source/stomp.adapter.rst0000644000175000017500000000053613171175615020515 0ustar sophiesophiestomp.adapter package ===================== Submodules ---------- stomp.adapter.multicast module ------------------------------ .. automodule:: stomp.adapter.multicast :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: stomp.adapter :members: :undoc-members: :show-inheritance: stomp.py-4.1.19/docs/source/quickstart.rst0000644000175000017500000000363013171175615020124 0ustar sophiesophie=========== Quick start =========== Stomp.py is a Python library providing access to a message broker using the `STOMP protocol `_ - either programmatically or using a command line client. Stomp.py API ============ :: >>> import time >>> import sys >>> >>> import stomp >>> >>> class MyListener(stomp.ConnectionListener): >>> def on_error(self, headers, message): >>> print('received an error "%s"' % message) >>> def on_message(self, headers, message): >>> print('received a message "%s"' % message) >>> conn = stomp.Connection() >>> conn.set_listener('', MyListener()) >>> conn.start() >>> conn.connect('admin', 'password', wait=True) >>> >>> conn.subscribe(destination='/queue/test', id=1, ack='auto') >>> >>> conn.send(body=' '.join(sys.argv[1:]), destination='/queue/test') >>> >>> time.sleep(2) >>> conn.disconnect() Command-line Client =================== Assuming stomp.py is installed (using pip) in the site-packages directory (e.g. lib/python3.3/site-packages), hereby referred to as ${SITEPACKAGES}, then you can run the command line client as follows:: python ${SITEPACKAGES}/stomp -H localhost -P 61613 As of version 4.0.3, a stomp.py is also installed into the bin dir (at least on unix), so you can also run:: stomp -H localhost -P 61613 After a successful connection, you can type commands such as:: subscribe /queue/test send /queue/test hello world If you need to pass a username and password to the client:: python ${SITEPACKAGES}/stomp -H localhost -P 61613 -U admin -W password stomp -H localhost -P 61613 -U admin -W password Type help for more information once you're running the command-line interface, or run the following to see the list of startup arguments:: python ${SITEPACKAGES}/stomp --help stomp --helpstomp.py-4.1.19/docs/source/api.rst0000644000175000017500000002405013171175615016502 0ustar sophiesophie============= Using the API ============= Establishing a connection ------------------------- The simplest way to establish a connection, assuming the message broker is running on the local machine, is:: >>> import stomp >>> c = stomp.Connection([('127.0.0.1', 62613)]) >>> c.start() >>> c.connect('admin', 'password', wait=True) By default this represents a STOMP 1.1 connection. You can request a specific version of the connection using one of the following:: >>> c = stomp.Connection10([('127.0.0.1', 62613)]) >>> c = stomp.Connection11([('127.0.0.1', 62613)]) >>> c = stomp.Connection12([('127.0.0.1', 62613)]) The first parameter to a ``Connection`` is ``host_and_ports``. This is a list of tuples, each containing ip address (which could be an ipv6 address) and the port where the message broker is listening for stomp connections. The general idea with the list is to try each address until a successful socket connection is established (giving the ability to provide multiple brokers for failover). An example of setting up a connection with failover addresses might be:: >>> import stomp >>> c = stomp.Connection([('192.168.1.100', 61613), ('192.168.1.101', 62613)]) And here's an example of an ipv6 connection:: >>> import stomp >>> c = stomp.Connection(['fe80::a00:27ff:fe90:3f1a%en1', 62613]) There are a number of other parameters for initialising the connection (looking at the StompConnection12 class): .. autoclass:: stomp.connect.StompConnection12 The ``start`` method is next - which performs the actual socket connection to the remote server and starts a separate receiver thread. The final step is to call the ``connect`` method (corresponding to the `CONNECT frame `_): .. automethod:: stomp.protocol.Protocol12.connect Note that connect also allows for a map (dict) of headers to be provided, and will merge these with any additional named parameters to build the headers for the `STOMP frame `_, allowing for non-standard headers to be transmitted to the broker. Sending and receiving messages ------------------------------ Once the connection is established, you can send messages using the ``send`` method: .. automethod:: stomp.protocol.Protocol12.send To receive messages back from the messaging system, you need to setup some sort of listener on your connection, and then subscribe to the destination (see `STOMP subscribe `_). Listeners are simply a subclass which implements the methods in the ConnectionListener class (see `this page `_ for more detail). Stomp provides a few implementations of listeners, but the simplest is ``PrintingListener`` which just prints all interactions between the client and server. A simple example of this in action is:: >>> from stomp import * >>> c = Connection([('127.0.0.1', 62613)]) >>> c.set_listener('', PrintingListener()) >>> c.start() >>> c.connect('admin', 'password', wait=True) on_connecting 127.0.0.1 62613 on_send STOMP {'passcode': 'password', 'login': 'admin', 'accept-version': '1.2', 'host': '127.0.0.1'} on_connected {'server': 'apache-apollo/1.7.1', 'host-id': 'mybroker', 'session': 'mybroker-13e0', 'heart-beat': '100,10000', 'version': '1.2', 'user-id': 'admin'} >>> c.subscribe('/queue/test', 123) on_send SUBSCRIBE {'id': 123, 'ack': 'auto', 'destination': '/queue/test'} >>> c.send('/queue/test', 'a test message') on_send SEND {'content-length': 5, 'destination': '/queue/test'} b'a test message' >>> on_before_message {'destination': '/queue/test', 'message-id': 'mybroker-13e01', 'subscription': '123', 'ack': '2', 'content-length': '5'} a test message on_message {'destination': '/queue/test', 'message-id': 'mybroker-13e01', 'subscription': '123', 'ack': '2', 'content-length': '5'} a test message You can see the responses from the message system in the ``on_connected``, and ``on_message`` output. The stomp frames sent to the server can be seen in each ``on_send`` output (an initial STOMP connect frame, SUBSCRIBE and then SEND). In the case of the subscribe method, as of STOMP 1.1, the ``id`` parameter is required (if connecting with STOMP 1.0, only the destination is required): .. automethod:: stomp.protocol.Protocol12.subscribe Note that listeners can be named so you can use more that one type of listener at the same time:: >>> c.set_listener('stats', StatsListener()) >>> c.set_listener('print', PrintingListener()) You unsubscribe from a topic or queue using the unique ``id`` for the subscription:: >>> c.subscribe('/queue/test', 123) >>> c.unsubscribe(123) Acks and Nacks -------------- Acknowledgements are a way to tell the message server that a message was either consumed, or not. Assume a collection of clients on a server listening on a queue, and a message which requires significant processing. One of the clients receives the message, checks resource usage on the server and decides to send a nack as a consequence. The message server could, at that point, decide to send to a failover server for processing (that's a possible use, anyway). Use the client or client-individual acknowledgement parameter (see `here `_ for a description) with the subscription, in order to use acks and nacks. Afterwards, you use the message and subscription ids to ack or nack the message:: >>> conn.subscribe('/queue/test', id=4, ack='client') on_before_message {'message-id': 'mybroker-14aa2', 'destination': '/queue/test', 'subscription': '4', 'content-length': '14'} test message 1 on_message {'message-id': 'mybroker-14aa2', 'destination': '/queue/test', 'subscription': '4', 'content-length': '14'} test message 1 >>> conn.ack('mybroker-14aa2', 2) on_before_message {'message-id': 'mybroker-14ab2', 'destination': '/queue/test', 'subscription': '4', 'content-length': '14'} test message 2 on_message {'message-id': 'mybroker-14ab2', 'destination': '/queue/test', 'subscription': '4', 'content-length': '14'} test message 2 >>> conn.nack('mybroker-14ab2', 2) Transactions ------------ The STOMP protocol provides a way to transmit messages to a broker inside a transaction, which are held on the server until the transaction is either committed - at which point they're sent - or aborted - where the messages are discarded. Begin a transaction using the ``begin`` method, which returns the transaction id you then use when sending messages (you can also generate your own transaction id and pass that as a parameter to ``begin``):: >>> conn.subscribe('/queue/test', id=5) >>> txid = conn.begin() >>> conn.send('/queue/test', 'test1', transaction=txid) >>> conn.send('/queue/test', 'test2', transaction=txid) >>> conn.send('/queue/test', 'test3', transaction=txid) >>> conn.commit(txid) on_message {'subscription': '5', 'content-length': '5', 'destination': '/queue/test', 'message-id': 'mybroker-14b03', 'transaction': 'b39f3136-46a3-4e11-8ba8-845e36d48412'} test1 on_message {'subscription': '5', 'content-length': '5', 'destination': '/queue/test', 'message-id': 'mybroker-14b04', 'transaction': 'b39f3136-46a3-4e11-8ba8-845e36d48412'} test2 on_message {'subscription': '5', 'content-length': '5', 'destination': '/queue/test', 'message-id': 'mybroker-14b05', 'transaction': 'b39f3136-46a3-4e11-8ba8-845e36d48412'} test3 Abort a transaction (and discard the sent messages using ``abort``): >>> conn.subscribe('/queue/test', id=6) >>> txid = conn.begin() >>> conn.send('/queue/test', 'test4', transaction=txid) >>> conn.send('/queue/test', 'test5', transaction=txid) >>> conn.abort(txid) Disconnect ---------- Stomp.py supports graceful shutdown/disconnections through a receipt parameter (automatically generated if you don't provide it). The connection is only dropped when the server sends back a response to that receipt:: >>> conn.disconnect() on_send DISCONNECT {'receipt': '825a5cd6-9e3c-4a72-8051-72348a94f5ce'} on_receipt {'receipt-id': '825a5cd6-9e3c-4a72-8051-72348a94f5ce'} on_disconnected Dealing with disconnects ------------------------ You can use a listener to deal with connection failures, and gracefully reconnect. Consider the below 'server' code: .. code-block:: python import os import time import stomp def connect_and_subscribe(conn): conn.start() conn.connect('guest', 'guest', wait=True) conn.subscribe(destination='/queue/test', id=1, ack='auto') class MyListener(stomp.ConnectionListener): def __init__(self, conn): self.conn = conn def on_error(self, headers, message): print('received an error "%s"' % message) def on_message(self, headers, message): print('received a message "%s"' % message) for x in range(10): print(x) time.sleep(1) print('processed message') def on_disconnected(self): print('disconnected') connect_and_subscribe(self.conn) conn = stomp.Connection([('localhost', 62613)], heartbeats=(4000, 4000)) conn.set_listener('', MyListener(conn)) connect_and_subscribe(conn) time.sleep(60) conn.disconnect() The listener in this code has an, arguably broken, message handler (on_message) which takes longer to process than the heartbeat time of 4 seconds (4000); resulting in a heartbeat timeout when a message is received, and a subsequent disconnect. The on_disconnected method then reconnects and continues processing. You can test the results of this by running the above code, and sending a message using the following 'client': .. code-block:: python import stomp conn = stomp.Connection([('localhost', 62613)]) conn.start() conn.connect('guest', 'guest', wait=True) conn.send('/queue/test', 'test message') stomp.py-4.1.19/docs/source/stomp.rst0000644000175000017500000000415713171175615017101 0ustar sophiesophiestomp package ============= Subpackages ----------- .. toctree:: stomp.adapter stomp.test Submodules ---------- stomp.backward module --------------------- .. automodule:: stomp.backward :members: :undoc-members: :show-inheritance: stomp.backward2 module ---------------------- .. automodule:: stomp.backward2 :members: :undoc-members: :show-inheritance: stomp.backward3 module ---------------------- .. automodule:: stomp.backward3 :members: :undoc-members: :show-inheritance: stomp.backwardsock module ------------------------- .. automodule:: stomp.backwardsock :members: :undoc-members: :show-inheritance: stomp.backwardsock25 module --------------------------- .. automodule:: stomp.backwardsock25 :members: :undoc-members: :show-inheritance: stomp.backwardsock26 module --------------------------- .. automodule:: stomp.backwardsock26 :members: :undoc-members: :show-inheritance: stomp.colors module ------------------- .. automodule:: stomp.colors :members: :undoc-members: :show-inheritance: stomp.connect module -------------------- .. automodule:: stomp.connect :members: :undoc-members: :show-inheritance: stomp.constants module ---------------------- .. automodule:: stomp.constants :members: :undoc-members: :show-inheritance: stomp.exception module ---------------------- .. automodule:: stomp.exception :members: :undoc-members: :show-inheritance: stomp.listener module --------------------- .. automodule:: stomp.listener :members: :undoc-members: :show-inheritance: stomp.protocol module --------------------- .. automodule:: stomp.protocol :members: :undoc-members: :show-inheritance: stomp.transport module ---------------------- .. automodule:: stomp.transport :members: :undoc-members: :show-inheritance: stomp.utils module ------------------ .. automodule:: stomp.utils :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: stomp :members: :undoc-members: :show-inheritance: stomp.py-4.1.19/docs/source/commandline.rst0000644000175000017500000000534313171175615020223 0ustar sophiesophie========================================= Using the Command-line client application ========================================= Once stomp.py is installed, access the command-line client as follows:: stomp -H 127.0.0.1 -P 61613 -U admin -W password Find more info using --help:: $ stomp --help Usage: stomp [options] Options: --version show program's version number and exit -h, --help show this help message and exit -H HOST, --host=HOST Hostname or IP to connect to. Defaults to localhost if not specified. -P PORT, --port=PORT Port providing stomp protocol connections. Defaults to 61613 if not specified. -U USER, --user=USER Username for the connection -W PASSWORD, --password=PASSWORD Password for the connection -F FILENAME, --file=FILENAME File containing commands to be executed, instead of prompting from the command prompt. -S STOMP, --stomp=STOMP Set the STOMP protocol version. -L LISTEN, --listen=LISTEN Listen for messages on a queue/destination -V VERBOSE, --verbose=VERBOSE Verbose logging "on" or "off" (if on, full headers from stomp server responses are printed) --ssl Enable SSL connection And you can also get more help within the application using the help command:: > help Documented commands (type help ): ======================================== EOF begin help rollback sendfile stats ver abort commit nack run sendrec subscribe version ack exit quit send sendreply unsubscribe Some of the differences to the programmatic API are the ability to run a script, and to send files (``run``, ``sendfile``, ``stats``):: > help run Usage: run Description: Execute commands in a specified file > help sendfile Usage: sendfile Required Parameters: destination - where to send the message filename - the file to send Description: Sends a file to a destination in the messaging system. > help stats Usage: stats [on|off] Description: Record statistics on messages sent, received, errors, etc. If no argument (on|off) is specified, dump the current statistics. Apart from that, the commands are largely inline with what you can do programmatically. Note that you can run it as a normal CLI, as a standalone listener and use it to run a script of commands. stomp.py-4.1.19/docs/source/stomp.test.rst0000644000175000017500000000621713171175615020056 0ustar sophiesophiestomp.test package ================== Submodules ---------- stomp.test.basic_test module ---------------------------- .. automodule:: stomp.test.basic_test :members: :undoc-members: :show-inheritance: stomp.test.cli_test module -------------------------- .. automodule:: stomp.test.cli_test :members: :undoc-members: :show-inheritance: stomp.test.misc_test module --------------------------- .. automodule:: stomp.test.misc_test :members: :undoc-members: :show-inheritance: stomp.test.multicast_test module -------------------------------- .. automodule:: stomp.test.multicast_test :members: :undoc-members: :show-inheritance: stomp.test.p2_backward_test module ---------------------------------- .. automodule:: stomp.test.p2_backward_test :members: :undoc-members: :show-inheritance: stomp.test.p2_nonascii_test module ---------------------------------- .. automodule:: stomp.test.p2_nonascii_test :members: :undoc-members: :show-inheritance: stomp.test.p3_backward_test module ---------------------------------- .. automodule:: stomp.test.p3_backward_test :members: :undoc-members: :show-inheritance: stomp.test.p3_nonascii_test module ---------------------------------- .. automodule:: stomp.test.p3_nonascii_test :members: :undoc-members: :show-inheritance: stomp.test.rabbitmq_test module ------------------------------- .. automodule:: stomp.test.rabbitmq_test :members: :undoc-members: :show-inheritance: stomp.test.s10_test module -------------------------- .. automodule:: stomp.test.s10_test :members: :undoc-members: :show-inheritance: stomp.test.s11_test module -------------------------- .. automodule:: stomp.test.s11_test :members: :undoc-members: :show-inheritance: stomp.test.s12_test module -------------------------- .. automodule:: stomp.test.s12_test :members: :undoc-members: :show-inheritance: stomp.test.ss_test module ------------------------- .. automodule:: stomp.test.ss_test :members: :undoc-members: :show-inheritance: stomp.test.ssl_test module -------------------------- .. automodule:: stomp.test.ssl_test :members: :undoc-members: :show-inheritance: stomp.test.stompserver_test module ---------------------------------- .. automodule:: stomp.test.stompserver_test :members: :undoc-members: :show-inheritance: stomp.test.testutils module --------------------------- .. automodule:: stomp.test.testutils :members: :undoc-members: :show-inheritance: stomp.test.threading_test module -------------------------------- .. automodule:: stomp.test.threading_test :members: :undoc-members: :show-inheritance: stomp.test.transport_test module -------------------------------- .. automodule:: stomp.test.transport_test :members: :undoc-members: :show-inheritance: stomp.test.utils_test module ---------------------------- .. automodule:: stomp.test.utils_test :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: stomp.test :members: :undoc-members: :show-inheritance: stomp.py-4.1.19/docs/source/index.rst0000644000175000017500000000123313171175615017036 0ustar sophiesophie.. Stomp documentation master file, created by sphinx-quickstart on Sun Sep 20 16:35:36 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Stomp.py |release| documentation ================================ Stomp.py was created by `Jason R Briggs `_. You can view outstanding issues, and find further info, on the `Github project page `_. Contents: .. toctree:: :maxdepth: 2 quickstart intro api commandline Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` stomp.py-4.1.19/docs/source/modules.rst0000644000175000017500000000006413171175615017400 0ustar sophiesophiestomp ===== .. toctree:: :maxdepth: 4 stomp stomp.py-4.1.19/stomp/0000755000175000017500000000000013171175615014110 5ustar sophiesophiestomp.py-4.1.19/stomp/utils.py0000644000175000017500000001431613171175615015627 0ustar sophiesophie"""General utility functions. """ import copy import re import socket import threading from stomp.backward import decode, NULL # List of all host names (unqualified, fully-qualified, and IP # addresses) that refer to the local host (both loopback interface # and external interfaces). This is used for determining # preferred targets. LOCALHOST_NAMES = ["localhost", "127.0.0.1"] try: LOCALHOST_NAMES.append(socket.gethostbyname(socket.gethostname())) except: pass try: LOCALHOST_NAMES.append(socket.gethostname()) except: pass try: LOCALHOST_NAMES.append(socket.getfqdn(socket.gethostname())) except: pass ## # Used to parse STOMP header lines in the format "key:value", # HEADER_LINE_RE = re.compile('(?P[^:]+)[:](?P.*)') ## # As of STOMP 1.2, lines can end with either line feed, or carriage return plus line feed. # PREAMBLE_END_RE = re.compile(b'\n\n|\r\n\r\n') ## # As of STOMP 1.2, lines can end with either line feed, or carriage return plus line feed. # LINE_END_RE = re.compile('\n|\r\n') def default_create_thread(callback): """ Default thread creation - used to create threads when the client doesn't want to provide their own thread creation. :param function callback: the callback function provided to threading.Thread """ thread = threading.Thread(None, callback) thread.daemon = True # Don't let thread prevent termination thread.start() return thread def is_localhost(host_and_port): """ Return 1 if the specified host+port is a member of the 'localhost' list of hosts, 2 if not (predominately used as a sort key. :param (str,int) host_and_port: tuple containing host and port :rtype: int """ (host, _) = host_and_port if host in LOCALHOST_NAMES: return 1 else: return 2 _HEADER_ESCAPES = { '\r': '\\r', '\n': '\\n', ':': '\\c', '\\': '\\\\', } _HEADER_UNESCAPES = dict((value, key) for (key, value) in _HEADER_ESCAPES.items()) def _unescape_header(matchobj): escaped = matchobj.group(0) unescaped = _HEADER_UNESCAPES.get(escaped) if not unescaped: # TODO: unknown escapes MUST be treated as fatal protocol error per spec unescaped = escaped return unescaped def parse_headers(lines, offset=0): """ Parse the headers in a STOMP response :param list(str) lines: the lines received in the message response :param int offset: the starting line number :rtype: dict(str,str) """ headers = {} for header_line in lines[offset:]: header_match = HEADER_LINE_RE.match(header_line) if header_match: key = header_match.group('key') key = re.sub(r'\\.', _unescape_header, key) if key not in headers: value = header_match.group('value') value = re.sub(r'\\.', _unescape_header, value) headers[key] = value return headers def parse_frame(frame): """ Parse a STOMP frame into a Frame object. :param bytes frame: the frame received from the server (as a byte string) :rtype: Frame """ f = Frame() if frame == b'\x0a': f.cmd = 'heartbeat' return f mat = PREAMBLE_END_RE.search(frame) if mat: preamble_end = mat.start() body_start = mat.end() else: preamble_end = len(frame) body_start = preamble_end preamble = decode(frame[0:preamble_end]) preamble_lines = LINE_END_RE.split(preamble) preamble_len = len(preamble_lines) f.body = frame[body_start:] # Skip any leading newlines first_line = 0 while first_line < preamble_len and len(preamble_lines[first_line]) == 0: first_line += 1 if first_line >= preamble_len: return None # Extract frame type/command f.cmd = preamble_lines[first_line] # Put headers into a key/value map f.headers = parse_headers(preamble_lines, first_line + 1) return f def merge_headers(header_map_list): """ Helper function for combining multiple header maps into one. :param list(dict) header_map_list: list of maps :rtype: dict """ headers = {} for header_map in header_map_list: if header_map: headers.update(header_map) return headers def clean_headers(headers): rtn = headers if 'passcode' in headers: rtn = copy.copy(headers) rtn['passcode'] = '********' return rtn def calculate_heartbeats(shb, chb): """ Given a heartbeat string from the server, and a heartbeat tuple from the client, calculate what the actual heartbeat settings should be. :param (str,str) shb: server heartbeat numbers :param (int,int) chb: client heartbeat numbers :rtype: (int,int) """ (sx, sy) = shb (cx, cy) = chb x = 0 y = 0 if cx != 0 and sy != '0': x = max(cx, int(sy)) if cy != 0 and sx != '0': y = max(cy, int(sx)) return x, y def convert_frame_to_lines(frame): """ Convert a frame to a list of lines separated by newlines. :param Frame frame: the Frame object to convert :rtype: list(str) """ lines = [] if frame.cmd: lines.append(frame.cmd) lines.append("\n") for key, vals in sorted(frame.headers.items()): if vals is None: continue if type(vals) != tuple: vals = (vals,) for val in vals: lines.append("%s:%s\n" % (key, val)) lines.append("\n") if frame.body: lines.append(frame.body) if frame.cmd: lines.append(NULL) return lines def length(s): """ Null (none) safe length function. :param str s: the string to return length of (None allowed) :rtype: int """ if s is not None: return len(s) return 0 class Frame(object): """ A STOMP frame (or message). :param str cmd: the protocol command :param dict headers: a map of headers for the frame :param body: the content of the frame. """ def __init__(self, cmd=None, headers=None, body=None): self.cmd = cmd self.headers = headers if headers is not None else {} self.body = body def __str__(self): return '{cmd=%s,headers=[%s],body=%s}' % (self.cmd, self.headers, self.body) stomp.py-4.1.19/stomp/constants.py0000644000175000017500000000125513171175615016501 0ustar sophiesophie"""The STOMP command and header name strings. """ HDR_ACCEPT_VERSION = 'accept-version' HDR_ACK = 'ack' HDR_CONTENT_LENGTH = 'content-length' HDR_CONTENT_TYPE = 'content-type' HDR_DESTINATION = 'destination' HDR_HEARTBEAT = 'heart-beat' HDR_HOST = 'host' HDR_ID = 'id' HDR_MESSAGE_ID = 'message-id' HDR_LOGIN = 'login' HDR_PASSCODE = 'passcode' HDR_RECEIPT = 'receipt' HDR_SUBSCRIPTION = 'subscription' HDR_TRANSACTION = 'transaction' CMD_ABORT = 'ABORT' CMD_ACK = 'ACK' CMD_BEGIN = 'BEGIN' CMD_COMMIT = 'COMMIT' CMD_CONNECT = 'CONNECT' CMD_DISCONNECT = 'DISCONNECT' CMD_NACK = 'NACK' CMD_STOMP = 'STOMP' CMD_SEND = 'SEND' CMD_SUBSCRIBE = 'SUBSCRIBE' CMD_UNSUBSCRIBE = 'UNSUBSCRIBE' stomp.py-4.1.19/stomp/backwardsock25.py0000644000175000017500000000141513171175615017270 0ustar sophiesophie""" Python2.5 (and lower) specific versions of various networking (ipv6) functions used by stomp.py """ from socket import * def get_socket(host, port, timeout=None): """ Return a socket. :param str host: the hostname to connect to :param int port: the port number to connect to :param timeout: if specified, set the socket timeout """ for res in getaddrinfo(host, port, 0, SOCK_STREAM): af, socktype, proto, canonname, sa = res sock = None try: sock = socket(af, socktype, proto) if timeout is not None: sock.settimeout(timeout) sock.connect(sa) return sock except error: if sock is not None: sock.close() raise error stomp.py-4.1.19/stomp/colors.py0000644000175000017500000000042213171175615015761 0ustar sophiesophie"""Color 'constants' used by the command line client. """ import platform if platform.system().lower() != 'windows': GREEN = "\33[32m" RED = "\33[31m" NO_COLOR = "\33[0m" BOLD = "\33[1m" else: GREEN = "" NO_COLOR = "" RED = "" BOLD = "" stomp.py-4.1.19/stomp/__main__.py0000644000175000017500000004742413171175615016215 0ustar sophiesophie""" Stomp.py command-line client Usage: stomp [options] Options: --version Show the version number and exit -h, --help Show this help message and exit -H , --host= Hostname or IP address to connect to. [default: localhost] -P , --port= Port providing stomp protocol connections. [default: 61613] -U , --user= Username for the connection -W , --password= Password for the connection -F , --file= File containing commands to be executed, instead of prompting from the command prompt. -S , --protocol= Set the STOMP protocol version (1.0, 1.1, 1.2) [default: 1.1] -L , --listen= Listen for messages on a queue/destination -V, --verbose Verbose logging "on" or "off" (if on, full headers from stomp server responses are printed) --ssl Enable SSL connection --heartbeats= Heartbeats to request when connecting with protocol >= 1.1 (two comma separated integers required) [default: 0,0] """ import base64 from cmd import Cmd import json import os import re import sys import time from docopt import docopt from stomp.adapter.multicast import MulticastConnection import stomp.colors from stomp.connect import StompConnection10, StompConnection11, StompConnection12 from stomp.listener import ConnectionListener, StatsListener sys.path.append('.') import stomp version_string = '%s.%s.%s' % stomp.__version__ heartbeat_pattern = re.compile(r'[0-9]+,[0-9]+') try: import uuid except ImportError: from backward import uuid class SubscriptionInfo(object): """ Used to store info about a subscription. """ def __init__(self, id, ack): self.id = id self.ack = ack class StompCLI(Cmd, ConnectionListener): """ A command line interface to the stomp.py client. See :py:class:`stomp.connect.StompConnection11` for more information on establishing a connection to a stomp server. """ def __init__(self, host='localhost', port=61613, user='', passcode='', ver='1.1', prompt='> ', verbose=True, use_ssl=False, heartbeats=(0, 0), stdin=sys.stdin, stdout=sys.stdout): Cmd.__init__(self, 'Tab', stdin, stdout) ConnectionListener.__init__(self) self.prompt = prompt self.verbose = verbose self.user = user self.passcode = passcode self.__quit = False if ver == '1.0': self.conn = StompConnection10([(host, port)]) elif ver == '1.1': self.conn = StompConnection11([(host, port)], heartbeats=heartbeats) elif ver == '1.2': self.conn = StompConnection12([(host, port)], heartbeats=heartbeats) elif ver == 'multicast': self.conn = MulticastConnection() else: raise RuntimeError('Unknown version') if use_ssl: self.conn.set_ssl([(host, port)]) self.conn.set_listener('', self) self.conn.start() self.conn.connect(self.user, self.passcode, wait=True) self.transaction_id = None self.version = ver try: self.nversion = float(ver) except ValueError: self.nversion = 1.0 self.__subscriptions = {} self.__subscription_id = 1 def __print_async(self, frame_type, headers, body): """ Utility function to print a message and setup the command prompt for the next input """ if self.__quit: return self.__sysout("\r \r", end='') if self.verbose: self.__sysout(frame_type) for k, v in headers.items(): self.__sysout('%s: %s' % (k, v)) if self.prompt != '': self.__sysout('') self.__sysout(body) self.__sysout(self.prompt, end='') self.stdout.flush() def __sysout(self, msg, end="\n"): self.stdout.write(str(msg) + end) def __error(self, msg, end="\n"): self.stdout.write(stomp.colors.BOLD + stomp.colors.RED + str(msg) + stomp.colors.NO_COLOR + end) def on_connecting(self, host_and_port): """ See :py:meth:`ConnectionListener.on_connecting` """ def on_disconnected(self): """ see :py:meth:`ConnectionListener.on_disconnected` """ if not self.__quit: self.__error("lost connection") def on_message(self, headers, body): """ See :py:meth:`ConnectionListener.on_message` Special case: if the header 'filename' is present, the content is written out as a file """ if 'filename' in headers: content = base64.b64decode(body.encode()) if os.path.exists(headers['filename']): fname = '%s.%s' % (headers['filename'], int(time.time())) else: fname = headers['filename'] with open(fname, 'wb') as f: f.write(content) self.__print_async("MESSAGE", headers, "Saved file: %s" % fname) else: self.__print_async("MESSAGE", headers, body) def on_error(self, headers, body): """ See :py:meth:`ConnectionListener.on_error` """ self.__print_async("ERROR", headers, body) def on_receipt(self, headers, body): """ See :py:meth:`ConnectionListener.on_receipt` """ self.__print_async("RECEIPT", headers, body) def on_connected(self, headers, body): """ See :py:meth:`ConnectionListener.on_connected` """ self.__print_async("CONNECTED", headers, body) def help_help(self): self.__sysout('Quick help on commands') def default(self, line): self.__error('Unknown command: %s' % line.split()[0]) def emptyline(self): pass def help(self, usage, description, required=(), optional=()): rparams = "\n\t" + "\n\t".join(required) oparams = "\n\t" + "\n\t".join(optional) m = { 'hl': stomp.colors.BOLD + stomp.colors.GREEN, 'nc': stomp.colors.NO_COLOR, 'usage': usage, 'description': description, 'required': rparams.rstrip(), 'optional': oparams.rstrip() } if rparams.rstrip() != '': rparams = '''%(hl)sRequired Parameters:%(nc)s%(required)s\n\n''' % m m['required'] = rparams if oparams.rstrip() != '': oparams = '''%(hl)sOptional Parameters:%(nc)s%(optional)s\n\n''' % m m['optional'] = oparams self.__sysout('''%(hl)sUsage:%(nc)s \t%(usage)s %(required)s%(optional)s%(hl)sDescription:%(nc)s \t%(description)s ''' % m) def do_quit(self, args): self.__quit = True self.__sysout('Shutting down, please wait') return True do_exit = do_quit do_EOF = do_quit def help_quit(self): self.help('exit', 'Exit the stomp client') help_exit = help_quit def help_EOF(self): self.help('exit', 'Exit the stomp client (using CTRL-D)') def do_subscribe(self, args): args = args.split() if len(args) < 1: self.__error('Expecting: subscribe [ack]') return name = args[0] if name in self.__subscriptions: self.__error('Already subscribed to %s' % name) return ack_mode = 'auto' if len(args) >= 2: ack_mode = args[1] sid = self.__subscription_id self.__subscription_id += 1 self.__sysout('Subscribing to "%s" with acknowledge set to "%s", id set to "%s"' % (name, ack_mode, sid)) self.conn.subscribe(destination=name, ack=ack_mode, id=sid) self.__subscriptions[name] = SubscriptionInfo(sid, ack_mode) def help_subscribe(self): self.help('subscribe [ack]', '''Register to listen to a given destination. Like send, the subscribe command requires a destination \theader indicating which destination to subscribe to. The ack parameter is optional, and defaults to \tauto.''', ['destination - the name to subscribe to'], ['ack - how to handle acknowledgements for a message; either automatically (auto) or manually (client)']) def do_unsubscribe(self, args): args = args.split() if len(args) < 1: self.__error('Expecting: unsubscribe ') return if args[0] not in self.__subscriptions: self.__sysout('Subscription %s not found' % args[0]) return self.__sysout('Unsubscribing from "%s"' % args[0]) self.conn.unsubscribe(destination=args[0], id=self.__subscriptions[args[0]].id) del self.__subscriptions[args[0]] def help_unsubscribe(self): self.help('unsubscribe ', 'Remove an existing subscription - so that the client no longer receive messages from that destination.', ['destination - the name to unsubscribe from'], ['ack - how to handle acknowledgements for a message; either automatically (auto) or manually (client)']) def do_send(self, args): args = args.split() if len(args) < 2: self.__error('Expecting: send ') elif not self.transaction_id: self.conn.send(args[0], ' '.join(args[1:])) else: self.conn.send(args[0], ' '.join(args[1:]), transaction=self.transaction_id) def complete_send(self, text, line, begidx, endidx): mline = line.split(' ')[1] offs = len(mline) - len(text) return [s[offs:] for s in self.__subscriptions if s.startswith(mline)] complete_unsubscribe = complete_send complete_sendrec = complete_send complete_sendreply = complete_send complete_sendfile = complete_send def help_send(self): self.help('send ', 'Sends a message to a destination in the messaging system.', ['destination - where to send the message', 'message - the content to send']) def do_sendrec(self, args): args = args.split() receipt_id = str(uuid.uuid4()) if len(args) < 2: self.__error('Expecting: sendrec ') elif not self.transaction_id: self.conn.send(args[0], ' '.join(args[1:]), receipt=receipt_id) else: self.conn.send(args[0], ' '.join(args[1:]), transaction=self.transaction_id, receipt=receipt_id) def help_sendrec(self): self.help('sendrec ', 'Sends a message to a destination in the messaging system and blocks for receipt of the message.', ['destination - where to send the message', 'message - the content to send']) def do_sendreply(self, args): args = args.split() if len(args) < 3: self.__error('Expecting: sendreply ') else: self.conn.send(args[0], "%s\n" % ' '.join(args[2:]), headers={'correlation-id': args[1]}) def help_sendreply(self): self.help('sendreply ', 'Sends a reply message to a destination in the messaging system.', ['destination - where to send the message', 'correlation-id - the correlating identifier to send with the response', 'message - the content to send']) def do_sendfile(self, args): args = args.split() if len(args) < 2: self.__error('Expecting: sendfile [headers.json]') elif not os.path.exists(args[1]): self.__error('File %s does not exist' % args[1]) else: headers = {} if len(args) == 3: if not os.path.exists(args[2]): self.__error('File %s does not exist' % args[2]) return self.__sysout("Loading %s" % args[2]) with open(args[2], mode='rb') as jf: headers = json.load(jf) self.__sysout('Using headers %s' % str(headers)) with open(args[1], mode='rb') as f: s = f.read() msg = base64.b64encode(s).decode() if not self.transaction_id: self.conn.send(args[0], msg, filename=args[1], headers=headers) else: self.conn.send(args[0], msg, filename=args[1], headers=headers, transaction=self.transaction_id) def help_sendfile(self): self.help('sendfile [headers.json]', 'Sends a file to a destination in the messaging system.', ['destination - where to send the message', 'filename - the file to send', 'headers.json - json map with headers to send']) def do_version(self, args): self.__sysout('%s%s [Protocol version %s]%s' % (stomp.colors.BOLD, version_string, self.conn.version, stomp.colors.NO_COLOR)) do_ver = do_version def help_version(self): self.help('version', 'Display the version of the client') help_ver = help_version def check_ack_nack(self, cmd, args): if self.nversion >= 1.2 and len(args) < 1: self.__error("Expecting: %s " % cmd) return None elif self.nversion == 1.1 and len(args) < 2: self.__error("Expecting: %s " % cmd) return None elif len(args) < 1: self.__error("Expecting: %s " % cmd) return None if len(args) == 1: return (args[0], None) else: return (args[0], args[1]) def do_ack(self, args): args = args.split() hdrs = self.check_ack_nack('ack', args) if hdrs is None: return (message_id, subscription_id) = hdrs if not self.transaction_id: self.conn.ack(message_id, subscription_id) else: self.conn.ack(message_id, subscription_id, transaction=self.transaction_id) def help_ack(self): self.help('ack [subscription-id]', '''The command 'ack' is used to acknowledge consumption of a message from a subscription using client \tacknowledgment. When a client has issued a 'subscribe' with the ack flag set to client, any messages \treceived from that destination will not be considered to have been consumed (by the server) until \tthe message has been acknowledged.''', ['message-id - the id of the message being acknowledged'], ['subscription-id the id of the subscription (only required for STOMP 1.1)']) def do_nack(self, args): args = args.split() hdrs = self.check_ack_nack('nack', args) if hdrs is None: return if not self.transaction_id: self.conn.nack(headers=hdrs) else: self.conn.nack(headers=hdrs, transaction=self.transaction_id) def help_nack(self): self.help('nack [subscription]', '''The command 'nack' is used to acknowledge the failure of a message from a subscription using client \tacknowledgment. When a client has issued a 'subscribe' with the ack flag set to client, any messages \treceived from that destination will not be considered to have been consumed (by the server) until \tthe message has been acknowledged (ack or nack).''', ['message-id - the id of the message being acknowledged']) def do_abort(self, args): if not self.transaction_id: self.__error("Not currently in a transaction") else: self.conn.abort(transaction=self.transaction_id) self.__sysout('Aborted transaction: %s' % self.transaction_id) self.transaction_id = None do_rollback = do_abort def help_abort(self): self.help('abort', 'Roll back a transaction in progress.') help_rollback = help_abort def do_begin(self, args): if self.transaction_id: self.__error("Currently in a transaction (%s)" % self.transaction_id) else: self.transaction_id = self.conn.begin() self.__sysout('Transaction id: %s' % self.transaction_id) def help_begin(self): self.help('begin', '''Start a transaction. Transactions in this case apply to sending and acknowledging - \tany messages sent or acknowledged during a transaction will be handled atomically based on the \ttransaction.''') def do_commit(self, args): if not self.transaction_id: self.__error("Not currently in a transaction") else: self.__sysout('Committing %s' % self.transaction_id) self.conn.commit(transaction=self.transaction_id) self.transaction_id = None def help_commit(self): self.help('commit', 'Commit a transaction in progress.') def do_stats(self, args): args = args.split() if len(args) < 1: stats = self.conn.get_listener('stats') if stats: self.__sysout(stats) else: self.__error('No stats available') elif args[0] == 'on': self.conn.set_listener('stats', StatsListener()) elif args[0] == 'off': self.conn.remove_listener('stats') else: self.__error('Expecting: stats [on|off]') def help_stats(self): self.help('stats [on|off]', '''Record statistics on messages sent, received, errors, etc. If no argument (on|off) is specified, \tdump the current statistics.''') def do_run(self, args): args = args.split() if len(args) == 0: self.__error("Expecting: run ") elif not os.path.exists(args[0]): self.__error("File %s was not found" % args[0]) else: with open(args[0]) as f: lines = f.read().split('\n') for line in lines: self.onecmd(line) def help_run(self): self.help('run ', 'Execute commands in a specified file') def do_nothing_loop(): while 1: time.sleep(1) def optional_arg(arg_default): def func(option, opt_str, value, parser): if parser.rargs and not parser.rargs[0].startswith('-'): val = parser.rargs[0] parser.rargs.pop(0) else: val = arg_default setattr(parser.values, option.dest, val) return func def main(): arguments = docopt(__doc__, version=version_string) if arguments['--listen'] is not None: prompt = '' else: prompt = '> ' if not heartbeat_pattern.match(arguments['--heartbeats']): print('Invalid heartbeats, expecting cx,cy') sys.exit(1) heartbeats = tuple(map(int, arguments['--heartbeats'].split(","))) st = StompCLI(arguments['--host'], arguments['--port'], arguments['--user'], arguments['--password'], arguments['--protocol'], prompt, arguments['--verbose'], arguments['--ssl'], heartbeats) if arguments['--listen'] is not None: st.do_subscribe(arguments['--listen']) try: while 1: time.sleep(10) except: print("\n") elif arguments['--file'] is not None: st.do_run(arguments['--file']) else: # disable CTRL-C, since can't guarantee correct handling of disconnect import signal def signal_handler(signal, frame): pass signal.signal(signal.SIGINT, signal_handler) try: try: st.cmdloop() except KeyboardInterrupt: st.do_quit() finally: st.conn.disconnect() # # command line access # if __name__ == '__main__': try: main() except: pass stomp.py-4.1.19/stomp/backwardsock.py0000644000175000017500000000051613171175615017122 0ustar sophiesophie"""Networking functions to support backwards compatibility. Distinct from the backward(2/3) functions to handle ipv6 changes between Python versions 2.5 and 2.6. """ import sys if sys.hexversion < 0x02060000: # < Python 2.6 from stomp.backwardsock25 import * else: # Python 2.6 onwards from stomp.backwardsock26 import * stomp.py-4.1.19/stomp/backwardsock26.py0000644000175000017500000000065513171175615017276 0ustar sophiesophie""" Python2.6 (and greater) specific versions of various networking (ipv6) functions used by stomp.py """ import socket def get_socket(host, port, timeout=None): """ Return a socket connection. :param str host: the hostname to connect to :param int port: the port number to connect to :param timeout: if specified, set the socket timeout """ return socket.create_connection((host, port), timeout) stomp.py-4.1.19/stomp/transport.py0000644000175000017500000010132213171175615016515 0ustar sophiesophie"""Provides the underlying transport functionality (for stomp message transmission) - (mostly) independent from the actual STOMP protocol """ import errno from io import BytesIO import logging import math import random import re import socket import sys import threading import time import warnings try: import ssl from ssl import SSLError DEFAULT_SSL_VERSION = ssl.PROTOCOL_TLSv1 except (ImportError, AttributeError): # python version < 2.6 without the backported ssl module ssl = None class SSLError(object): pass DEFAULT_SSL_VERSION = None try: from socket import SOL_SOCKET, SO_KEEPALIVE from socket import SOL_TCP, TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT LINUX_KEEPALIVE_AVAIL = True except ImportError: LINUX_KEEPALIVE_AVAIL = False from stomp.backward import decode, encode, get_errno, monotonic, pack from stomp.backwardsock import get_socket from stomp.constants import * import stomp.exception as exception import stomp.listener import stomp.utils as utils log = logging.getLogger('stomp.py') class BaseTransport(stomp.listener.Publisher): """ Base class for transport classes providing support for listeners, threading overrides, and anything else outside of actually establishing a network connection, sending and receiving of messages (so generally socket-agnostic functions). :param bool wait_on_receipt: deprecated, ignored :param bool auto_decode: automatically decode message responses as strings, rather than leaving them as bytes. This preserves the behaviour as of version 4.0.16. (To be defaulted to False as of the next release) """ # # Used to parse the STOMP "content-length" header lines, # __content_length_re = re.compile(b'^content-length[:]\\s*(?P[0-9]+)', re.MULTILINE) def __init__(self, wait_on_receipt=False, auto_decode=True): self.__recvbuf = b'' self.listeners = {} self.running = False self.blocking = None self.connected = False self.connection_error = False self.__receipts = {} self.current_host_and_port = None # flag used when we receive the disconnect receipt self.__disconnect_receipt = None # function for creating threads used by the connection self.create_thread_fc = utils.default_create_thread self.__receiver_thread_exit_condition = threading.Condition() self.__receiver_thread_exited = False self.__send_wait_condition = threading.Condition() self.__connect_wait_condition = threading.Condition() self.__auto_decode = auto_decode def override_threading(self, create_thread_fc): """ Override for thread creation. Use an alternate threading library by setting this to a function with a single argument (which is the receiver loop callback). The thread which is returned should be started (ready to run) :param function create_thread_fc: single argument function for creating a thread """ self.create_thread_fc = create_thread_fc # # Manage the connection # def start(self): """ Start the connection. This should be called after all listeners have been registered. If this method is not called, no frames will be received by the connection. """ self.running = True self.attempt_connection() receiver_thread = self.create_thread_fc(self.__receiver_loop) receiver_thread.name = "StompReceiver%s" % getattr(receiver_thread, "name", "Thread") self.notify('connecting') def stop(self): """ Stop the connection. Performs a clean shutdown by waiting for the receiver thread to exit. """ with self.__receiver_thread_exit_condition: while not self.__receiver_thread_exited and self.is_connected(): self.__receiver_thread_exit_condition.wait() def is_connected(self): """ :rtype: bool """ return self.connected def set_connected(self, connected): """ :param bool connected: """ with self.__connect_wait_condition: self.connected = connected if connected: self.__connect_wait_condition.notify() def set_receipt(self, receipt_id, value): if value: self.__receipts[receipt_id] = value elif receipt_id in self.__receipts: del self.__receipts[receipt_id] # # Manage objects listening to incoming frames # def set_listener(self, name, listener): """ Set a named listener to use with this connection. See :py:class:`stomp.listener.ConnectionListener` :param str name: the name of the listener :param ConnectionListener listener: the listener object """ self.listeners[name] = listener def remove_listener(self, name): """ Remove a listener according to the specified name :param str name: the name of the listener to remove """ del self.listeners[name] def get_listener(self, name): """ Return the named listener :param str name: the listener to return :rtype: ConnectionListener """ return self.listeners.get(name) def process_frame(self, f, frame_str): """ :param Frame f: Frame object :param bytes frame_str: raw frame content """ frame_type = f.cmd.lower() if frame_type in ['connected', 'message', 'receipt', 'error', 'heartbeat']: if frame_type == 'message': (f.headers, f.body) = self.notify('before_message', f.headers, f.body) if log.isEnabledFor(logging.DEBUG): log.debug("Received frame: %r, headers=%r, body=%r", f.cmd, f.headers, f.body) else: log.info("Received frame: %r, headers=%r, len(body)=%r", f.cmd, f.headers, utils.length(f.body)) self.notify(frame_type, f.headers, f.body) else: log.warning("Unknown response frame type: '%s' (frame length was %d)", frame_type, utils.length(frame_str)) def notify(self, frame_type, headers=None, body=None): """ Utility function for notifying listeners of incoming and outgoing messages :param str frame_type: the type of message :param dict headers: the map of headers associated with the message :param body: the content of the message """ if frame_type == 'receipt': # logic for wait-on-receipt notification receipt = headers['receipt-id'] receipt_value = self.__receipts.get(receipt) with self.__send_wait_condition: self.set_receipt(receipt, None) self.__send_wait_condition.notify() # received a stomp 1.1+ disconnect receipt if receipt == self.__disconnect_receipt: self.disconnect_socket() if receipt_value == CMD_DISCONNECT: self.set_connected(False) elif frame_type == 'connected': self.set_connected(True) elif frame_type == 'disconnected': self.set_connected(False) for listener in self.listeners.values(): if not listener: continue notify_func = getattr(listener, 'on_%s' % frame_type, None) if not notify_func: log.debug("listener %s has no method on_%s", listener, frame_type) continue if frame_type in ('heartbeat', 'disconnected'): notify_func() continue if frame_type == 'connecting': notify_func(self.current_host_and_port) continue if frame_type == 'error' and not self.connected: with self.__connect_wait_condition: self.connection_error = True self.__connect_wait_condition.notify() rtn = notify_func(headers, body) if rtn: (headers, body) = rtn return (headers, body) def transmit(self, frame): """ Convert a frame object to a frame string and transmit to the server. :param Frame frame: the Frame object to transmit """ for listener in self.listeners.values(): if not listener: continue try: listener.on_send(frame) except AttributeError: continue lines = utils.convert_frame_to_lines(frame) packed_frame = pack(lines) if log.isEnabledFor(logging.DEBUG): log.debug("Sending frame: %s", lines) else: log.info("Sending frame: %r, headers=%r", frame.cmd or "heartbeat", utils.clean_headers(frame.headers)) self.send(encode(packed_frame)) def send(self, encoded_frame): """ Send an encoded frame over this transport (to be implemented in subclasses) :param bytes encoded_frame: a Frame object which has been encoded for transmission """ pass def receive(self): """ Receive a chunk of data (to be implemented in subclasses) :rtype: bytes """ pass def cleanup(self): """ Cleanup the transport (to be implemented in subclasses) """ pass def attempt_connection(self): """ Attempt to establish a connection. """ pass def disconnect_socket(self): """ Disconnect the socket. """ def wait_for_connection(self, timeout=None): """ Wait until we've established a connection with the server. :param float timeout: how long to wait, in seconds """ if timeout is not None: wait_time = timeout / 10.0 else: wait_time = None with self.__connect_wait_condition: while self.running and not self.is_connected() and not self.connection_error: self.__connect_wait_condition.wait(wait_time) if not self.running or not self.is_connected(): raise exception.ConnectFailedException() def __receiver_loop(self): """ Main loop listening for incoming data. """ log.info("Starting receiver loop") try: while self.running: try: while self.running: frames = self.__read() for frame in frames: f = utils.parse_frame(frame) if f is None: continue if self.__auto_decode: f.body = decode(f.body) self.process_frame(f, frame) except exception.ConnectionClosedException: if self.running: # # Clear out any half-received messages after losing connection # self.__recvbuf = b'' self.running = False self.notify('disconnected') break finally: self.cleanup() finally: with self.__receiver_thread_exit_condition: self.__receiver_thread_exited = True self.__receiver_thread_exit_condition.notifyAll() log.info("Receiver loop ended") self.notify('receiver_loop_completed') with self.__connect_wait_condition: self.__connect_wait_condition.notifyAll() def __read(self): """ Read the next frame(s) from the socket. :return: list of frames read :rtype: list(bytes) """ fastbuf = BytesIO() while self.running: try: try: c = self.receive() except exception.InterruptedException: log.debug("socket read interrupted, restarting") continue except Exception: log.debug("socket read error", exc_info=True) c = b'' if c is None or len(c) == 0: raise exception.ConnectionClosedException() if c == b'\x0a' and not self.__recvbuf and not fastbuf.tell(): # # EOL to an empty receive buffer: treat as heartbeat. # Note that this may misdetect an optional EOL at end of frame as heartbeat in case the # previous receive() got a complete frame whose NUL at end of frame happened to be the # last byte of that read. But that should be harmless in practice. # fastbuf.close() return [c] fastbuf.write(c) if b'\x00' in c: # # Possible end of frame # break self.__recvbuf += fastbuf.getvalue() fastbuf.close() result = [] if self.__recvbuf and self.running: while True: pos = self.__recvbuf.find(b'\x00') if pos >= 0: frame = self.__recvbuf[0:pos] preamble_end_match = utils.PREAMBLE_END_RE.search(frame) if preamble_end_match: preamble_end = preamble_end_match.start() content_length_match = BaseTransport.__content_length_re.search(frame[0:preamble_end]) if content_length_match: content_length = int(content_length_match.group('value')) content_offset = preamble_end_match.end() frame_size = content_offset + content_length if frame_size > len(frame): # # Frame contains NUL bytes, need to read more # if frame_size < len(self.__recvbuf): pos = frame_size frame = self.__recvbuf[0:pos] else: # # Haven't read enough data yet, exit loop and wait for more to arrive # break result.append(frame) pos += 1 # # Ignore optional EOLs at end of frame # while self.__recvbuf[pos:pos + 1] == b'\x0a': pos += 1 self.__recvbuf = self.__recvbuf[pos:] else: break return result class Transport(BaseTransport): """ Represents a STOMP client 'transport'. Effectively this is the communications mechanism without the definition of the protocol. :param list((str,int)) host_and_ports: a list of (host, port) tuples :param bool prefer_localhost: if True and the local host is mentioned in the (host, port) tuples, try to connect to this first :param bool try_loopback_connect: if True and the local host is found in the host tuples, try connecting to it using loopback interface (127.0.0.1) :param float reconnect_sleep_initial: initial delay in seconds to wait before reattempting to establish a connection if connection to any of the hosts fails. :param float reconnect_sleep_increase: factor by which the sleep delay is increased after each connection attempt. For example, 0.5 means to wait 50% longer than before the previous attempt, 1.0 means wait twice as long, and 0.0 means keep the delay constant. :param float reconnect_sleep_max: maximum delay between connection attempts, regardless of the reconnect_sleep_increase. :param float reconnect_sleep_jitter: random additional time to wait (as a percentage of the time determined using the previous parameters) between connection attempts in order to avoid stampeding. For example, a value of 0.1 means to wait an extra 0%-10% (randomly determined) of the delay calculated using the previous three parameters. :param int reconnect_attempts_max: maximum attempts to reconnect :param bool use_ssl: deprecated, see :py:meth:`set_ssl` :param ssl_cert_file: deprecated, see :py:meth:`set_ssl` :param ssl_key_file: deprecated, see :py:meth:`set_ssl` :param ssl_ca_certs: deprecated, see :py:meth:`set_ssl` :param ssl_cert_validator: deprecated, see :py:meth:`set_ssl` :param ssl_version: deprecated, see :py:meth:`set_ssl` :param timeout: the timeout value to use when connecting the stomp socket :param bool wait_on_receipt: deprecated, ignored :param keepalive: some operating systems support sending the occasional heart beat packets to detect when a connection fails. This parameter can either be set set to a boolean to turn on the default keepalive options for your OS, or as a tuple of values, which also enables keepalive packets, but specifies options specific to your OS implementation :param str vhost: specify a virtual hostname to provide in the 'host' header of the connection """ def __init__(self, host_and_ports=None, prefer_localhost=True, try_loopback_connect=True, reconnect_sleep_initial=0.1, reconnect_sleep_increase=0.5, reconnect_sleep_jitter=0.1, reconnect_sleep_max=60.0, reconnect_attempts_max=3, use_ssl=False, ssl_key_file=None, ssl_cert_file=None, ssl_ca_certs=None, ssl_cert_validator=None, wait_on_receipt=False, ssl_version=None, timeout=None, keepalive=None, vhost=None, auto_decode=True ): BaseTransport.__init__(self, wait_on_receipt, auto_decode) if host_and_ports is None: host_and_ports = [('localhost', 61613)] sorted_host_and_ports = [] sorted_host_and_ports.extend(host_and_ports) # # If localhost is preferred, make sure all (host, port) tuples that refer to the local host come first in # the list # if prefer_localhost: sorted_host_and_ports.sort(key=utils.is_localhost) # # If the user wishes to attempt connecting to local ports using the loopback interface, for each (host, port) # tuple referring to a local host, add an entry with the host name replaced by 127.0.0.1 if it doesn't # exist already # loopback_host_and_ports = [] if try_loopback_connect: for host_and_port in sorted_host_and_ports: if utils.is_localhost(host_and_port) == 1: port = host_and_port[1] if not (("127.0.0.1", port) in sorted_host_and_ports or ("localhost", port) in sorted_host_and_ports): loopback_host_and_ports.append(("127.0.0.1", port)) # # Assemble the final, possibly sorted list of (host, port) tuples # self.__host_and_ports = [] self.__host_and_ports.extend(loopback_host_and_ports) self.__host_and_ports.extend(sorted_host_and_ports) self.__reconnect_sleep_initial = reconnect_sleep_initial self.__reconnect_sleep_increase = reconnect_sleep_increase self.__reconnect_sleep_jitter = reconnect_sleep_jitter self.__reconnect_sleep_max = reconnect_sleep_max self.__reconnect_attempts_max = reconnect_attempts_max self.__timeout = timeout self.socket = None self.__socket_semaphore = threading.BoundedSemaphore(1) self.current_host_and_port = None # setup SSL self.__ssl_params = {} if use_ssl: warnings.warn("Deprecated: use set_ssl instead", DeprecationWarning) self.set_ssl(host_and_ports, ssl_key_file, ssl_cert_file, ssl_ca_certs, ssl_cert_validator, ssl_version) self.__keepalive = keepalive self.vhost = vhost def is_connected(self): """ Return true if the socket managed by this connection is connected :rtype: bool """ try: return self.socket is not None and self.socket.getsockname()[1] != 0 and BaseTransport.is_connected(self) except socket.error: return False def disconnect_socket(self): """ Disconnect the underlying socket connection """ self.running = False if self.socket is not None: if self.__need_ssl(): # # Even though we don't want to use the socket, unwrap is the only API method which does a proper SSL # shutdown # try: self.socket = self.socket.unwrap() except Exception: # # unwrap seems flaky on Win with the back-ported ssl mod, so catch any exception and log it # _, e, _ = sys.exc_info() log.warning(e) elif hasattr(socket, 'SHUT_RDWR'): try: self.socket.shutdown(socket.SHUT_RDWR) except socket.error: _, e, _ = sys.exc_info() # ignore when socket already closed if get_errno(e) != errno.ENOTCONN: log.warning("Unable to issue SHUT_RDWR on socket because of error '%s'", e) # # split this into a separate check, because sometimes the socket is nulled between shutdown and this call # if self.socket is not None: try: self.socket.close() except socket.error: _, e, _ = sys.exc_info() log.warning("Unable to close socket because of error '%s'", e) self.current_host_and_port = None self.socket = None self.notify('disconnected') def send(self, encoded_frame): """ :param bytes encoded_frame: """ if self.socket is not None: try: with self.__socket_semaphore: self.socket.sendall(encoded_frame) except Exception: _, e, _ = sys.exc_info() log.error("Error sending frame", exc_info=1) raise e else: raise exception.NotConnectedException() def receive(self): """ :rtype: bytes """ try: return self.socket.recv(1024) except socket.error: _, e, _ = sys.exc_info() if get_errno(e) in (errno.EAGAIN, errno.EINTR): log.debug("socket read interrupted, restarting") raise exception.InterruptedException() if self.is_connected(): raise def cleanup(self): """ Close the socket and clear the current host and port details. """ try: self.socket.close() except: pass # ignore errors when attempting to close socket self.socket = None self.current_host_and_port = None def __enable_keepalive(self): def try_setsockopt(sock, name, fam, opt, val): if val is None: return True # no value to set always works try: sock.setsockopt(fam, opt, val) log.info("keepalive: set %r option to %r on socket", name, val) except: log.error("keepalive: unable to set %r option to %r on socket", name, val) return False return True ka = self.__keepalive if not ka: return if ka is True: ka_sig = 'auto' ka_args = () else: try: ka_sig = ka[0] ka_args = ka[1:] except Exception: log.error("keepalive: bad specification %r", ka) return if ka_sig == 'auto': if LINUX_KEEPALIVE_AVAIL: ka_sig = 'linux' ka_args = None log.info("keepalive: autodetected linux-style support") else: log.error("keepalive: unable to detect any implementation, DISABLED!") return if ka_sig == 'linux': log.info("keepalive: activating linux-style support") if ka_args is None: log.info("keepalive: using system defaults") ka_args = (None, None, None) lka_idle, lka_intvl, lka_cnt = ka_args if try_setsockopt(self.socket, 'enable', SOL_SOCKET, SO_KEEPALIVE, 1): try_setsockopt(self.socket, 'idle time', SOL_TCP, TCP_KEEPIDLE, lka_idle) try_setsockopt(self.socket, 'interval', SOL_TCP, TCP_KEEPINTVL, lka_intvl) try_setsockopt(self.socket, 'count', SOL_TCP, TCP_KEEPCNT, lka_cnt) else: log.error("keepalive: implementation %r not recognized or not supported", ka_sig) def attempt_connection(self): """ Try connecting to the (host, port) tuples specified at construction time. """ self.connection_error = False sleep_exp = 1 connect_count = 0 while self.running and self.socket is None and connect_count < self.__reconnect_attempts_max: for host_and_port in self.__host_and_ports: try: log.info("Attempting connection to host %s, port %s", host_and_port[0], host_and_port[1]) self.socket = get_socket(host_and_port[0], host_and_port[1], self.__timeout) self.__enable_keepalive() need_ssl = self.__need_ssl(host_and_port) if need_ssl: # wrap socket ssl_params = self.get_ssl(host_and_port) if ssl_params['ca_certs']: cert_validation = ssl.CERT_REQUIRED else: cert_validation = ssl.CERT_NONE try: tls_context = ssl.create_default_context(cafile=ssl_params['ca_certs']) except AttributeError: tls_context = None if tls_context: # Wrap the socket for TLS certfile = ssl_params['cert_file'] keyfile = ssl_params['key_file'] password = ssl_params.get('password') if certfile and not keyfile: keyfile = certfile if certfile: tls_context.load_cert_chain(certfile, keyfile, password) if cert_validation is None or cert_validation == ssl.CERT_NONE: tls_context.check_hostname = False tls_context.verify_mode = cert_validation self.socket = tls_context.wrap_socket(self.socket, server_hostname=host_and_port[0]) else: # Old-style wrap_socket where we don't have a modern SSLContext (so no SNI) self.socket = ssl.wrap_socket( self.socket, keyfile=ssl_params['key_file'], certfile=ssl_params['cert_file'], cert_reqs=cert_validation, ca_certs=ssl_params['ca_certs'], ssl_version=ssl_params['ssl_version']) self.socket.settimeout(self.__timeout) if self.blocking is not None: self.socket.setblocking(self.blocking) # # Validate server cert # if need_ssl and ssl_params['cert_validator']: cert = self.socket.getpeercert() (ok, errmsg) = ssl_params['cert_validator'](cert, host_and_port[0]) if not ok: raise SSLError("Server certificate validation failed: %s", errmsg) self.current_host_and_port = host_and_port log.info("Established connection to host %s, port %s", host_and_port[0], host_and_port[1]) break except socket.error: self.socket = None connect_count += 1 log.warning("Could not connect to host %s, port %s", host_and_port[0], host_and_port[1], exc_info=1) if self.socket is None: sleep_duration = (min(self.__reconnect_sleep_max, ((self.__reconnect_sleep_initial / (1.0 + self.__reconnect_sleep_increase)) * math.pow(1.0 + self.__reconnect_sleep_increase, sleep_exp))) * (1.0 + random.random() * self.__reconnect_sleep_jitter)) sleep_end = monotonic() + sleep_duration log.debug("Sleeping for %.1f seconds before attempting reconnect", sleep_duration) while self.running and monotonic() < sleep_end: time.sleep(0.2) if sleep_duration < self.__reconnect_sleep_max: sleep_exp += 1 if not self.socket: raise exception.ConnectFailedException() def set_ssl(self, for_hosts=(), key_file=None, cert_file=None, ca_certs=None, cert_validator=None, ssl_version=DEFAULT_SSL_VERSION, password=None): """ Sets up SSL configuration for the given hosts. This ensures socket is wrapped in a SSL connection, raising an exception if the SSL module can't be found. :param for_hosts: hosts this SSL configuration should be applied to :param cert_file: the path to a X509 certificate :param key_file: the path to a X509 key file :param ca_certs: the path to the a file containing CA certificates to validate the server against. If this is not set, server side certificate validation is not done. :param cert_validator: function which performs extra validation on the client certificate, for example checking the returned certificate has a commonName attribute equal to the hostname (to avoid man in the middle attacks). The signature is: (OK, err_msg) = validation_function(cert, hostname) where OK is a boolean, and cert is a certificate structure as returned by ssl.SSLSocket.getpeercert() :param ssl_version: SSL protocol to use for the connection. This should be one of the PROTOCOL_x constants provided by the ssl module. The default is ssl.PROTOCOL_TLSv1 """ if not ssl: raise Exception("SSL connection requested, but SSL library not found") for host_port in for_hosts: self.__ssl_params[host_port] = dict(key_file=key_file, cert_file=cert_file, ca_certs=ca_certs, cert_validator=cert_validator, ssl_version=ssl_version, password=password) def __need_ssl(self, host_and_port=None): """ Whether current host needs SSL or not. :param (str,int) host_and_port: the host/port pair to check, default current_host_and_port """ if not host_and_port: host_and_port = self.current_host_and_port return host_and_port in self.__ssl_params def get_ssl(self, host_and_port=None): """ Get SSL params for the given host. :param (str,int) host_and_port: the host/port pair we want SSL params for, default current_host_and_port """ if not host_and_port: host_and_port = self.current_host_and_port return self.__ssl_params.get(host_and_port) stomp.py-4.1.19/stomp/connect.py0000644000175000017500000001775713171175615016134 0ustar sophiesophie"""Main entry point for clients to create a STOMP connection. Provides connection classes for `1.0 `_, `1.1 `_, and `1.2 `_ versions of the STOMP protocol. """ from stomp.listener import * from stomp.protocol import * from stomp.transport import * class BaseConnection(Publisher): """ Base class for all connection classes. """ def __init__(self, transport): """ :param Transport transport: """ self.transport = transport def set_listener(self, name, lstnr): """ :param str name: :param ConnectionListener lstnr: """ self.transport.set_listener(name, lstnr) def remove_listener(self, name): """ :param str name: """ self.transport.remove_listener(name) def get_listener(self, name): """ :param str name: :rtype: ConnectionListener """ return self.transport.get_listener(name) def start(self): self.transport.start() def stop(self): self.transport.stop() def is_connected(self): """ :rtype: bool """ return self.transport.is_connected() def set_receipt(self, receipt_id, value): self.transport.set_receipt(receipt_id, value) def set_ssl(self, *args, **kwargs): self.transport.set_ssl(*args, **kwargs) def get_ssl(self, *args, **kwargs): self.transport.get_ssl(*args, **kwargs) class StompConnection10(BaseConnection, Protocol10): """ Represents a 1.0 connection (comprising transport plus 1.0 protocol class). See :py:class:`stomp.transport.Transport` for details on the initialisation parameters. """ def __init__(self, host_and_ports=None, prefer_localhost=True, try_loopback_connect=True, reconnect_sleep_initial=0.1, reconnect_sleep_increase=0.5, reconnect_sleep_jitter=0.1, reconnect_sleep_max=60.0, reconnect_attempts_max=3, use_ssl=False, ssl_key_file=None, ssl_cert_file=None, ssl_ca_certs=None, ssl_cert_validator=None, wait_on_receipt=False, ssl_version=DEFAULT_SSL_VERSION, timeout=None, keepalive=None, auto_decode=True, auto_content_length=True): transport = Transport(host_and_ports, prefer_localhost, try_loopback_connect, reconnect_sleep_initial, reconnect_sleep_increase, reconnect_sleep_jitter, reconnect_sleep_max, reconnect_attempts_max, use_ssl, ssl_key_file, ssl_cert_file, ssl_ca_certs, ssl_cert_validator, wait_on_receipt, ssl_version, timeout, keepalive, None, auto_decode) BaseConnection.__init__(self, transport) Protocol10.__init__(self, transport, auto_content_length) def disconnect(self, receipt=None, headers=None, **keyword_headers): """ Call the protocol disconnection, and then stop the transport itself. :param str receipt: the receipt to use with the disconnect :param dict headers: a map of any additional headers to send with the disconnection :param keyword_headers: any additional headers to send with the disconnection """ Protocol10.disconnect(self, receipt, headers, **keyword_headers) self.transport.stop() class StompConnection11(BaseConnection, Protocol11): """ Represents a 1.1 connection (comprising transport plus 1.1 protocol class) See :py:class:`stomp.transport.Transport` for details on the initialisation parameters. """ def __init__(self, host_and_ports=None, prefer_localhost=True, try_loopback_connect=True, reconnect_sleep_initial=0.1, reconnect_sleep_increase=0.5, reconnect_sleep_jitter=0.1, reconnect_sleep_max=60.0, reconnect_attempts_max=3, use_ssl=False, ssl_key_file=None, ssl_cert_file=None, ssl_ca_certs=None, ssl_cert_validator=None, wait_on_receipt=False, ssl_version=DEFAULT_SSL_VERSION, timeout=None, heartbeats=(0, 0), keepalive=None, vhost=None, auto_decode=True, auto_content_length=True): transport = Transport(host_and_ports, prefer_localhost, try_loopback_connect, reconnect_sleep_initial, reconnect_sleep_increase, reconnect_sleep_jitter, reconnect_sleep_max, reconnect_attempts_max, use_ssl, ssl_key_file, ssl_cert_file, ssl_ca_certs, ssl_cert_validator, wait_on_receipt, ssl_version, timeout, keepalive, vhost, auto_decode) BaseConnection.__init__(self, transport) Protocol11.__init__(self, transport, heartbeats, auto_content_length) def disconnect(self, receipt=None, headers=None, **keyword_headers): """ Call the protocol disconnection, and then stop the transport itself. :param str receipt: the receipt to use with the disconnect :param dict headers: a map of any additional headers to send with the disconnection :param keyword_headers: any additional headers to send with the disconnection """ Protocol11.disconnect(self, receipt, headers, **keyword_headers) self.transport.stop() class StompConnection12(BaseConnection, Protocol12): """ Represents a 1.2 connection (comprising transport plus 1.2 protocol class). See :py:class:`stomp.transport.Transport` for details on the initialisation parameters. """ def __init__(self, host_and_ports=None, prefer_localhost=True, try_loopback_connect=True, reconnect_sleep_initial=0.1, reconnect_sleep_increase=0.5, reconnect_sleep_jitter=0.1, reconnect_sleep_max=60.0, reconnect_attempts_max=3, use_ssl=False, ssl_key_file=None, ssl_cert_file=None, ssl_ca_certs=None, ssl_cert_validator=None, wait_on_receipt=False, ssl_version=DEFAULT_SSL_VERSION, timeout=None, heartbeats=(0, 0), keepalive=None, vhost=None, auto_decode=True, auto_content_length=True): transport = Transport(host_and_ports, prefer_localhost, try_loopback_connect, reconnect_sleep_initial, reconnect_sleep_increase, reconnect_sleep_jitter, reconnect_sleep_max, reconnect_attempts_max, use_ssl, ssl_key_file, ssl_cert_file, ssl_ca_certs, ssl_cert_validator, wait_on_receipt, ssl_version, timeout, keepalive, vhost, auto_decode) BaseConnection.__init__(self, transport) Protocol12.__init__(self, transport, heartbeats, auto_content_length) def disconnect(self, receipt=None, headers=None, **keyword_headers): """ Call the protocol disconnection, and then stop the transport itself. :param str receipt: the receipt to use with the disconnect :param dict headers: a map of any additional headers to send with the disconnection :param keyword_headers: any additional headers to send with the disconnection """ Protocol12.disconnect(self, receipt, headers, **keyword_headers) self.transport.stop() stomp.py-4.1.19/stomp/backward2.py0000644000175000017500000000207213171175615016323 0ustar sophiesophie""" Python2-specific versions of various functions used by stomp.py """ NULL = '\x00' def input_prompt(prompt): """ Get user input :rtype: str """ return raw_input(prompt) def decode(byte_data): """ Decode the byte data to a string - in the case of this Py2 version, we can't really do anything (Py3 differs). :param bytes byte_data: :rtype: str """ return byte_data # no way to know if it's unicode or not, so just pass through unmolested def encode(char_data): """ Encode the parameter as a byte string. :param char_data: :rtype: bytes """ if type(char_data) is unicode: return char_data.encode('utf-8') else: return char_data def pack(pieces=()): """ Join a sequence of strings together (note: py3 version differs) :param list pieces: :rtype: bytes """ return ''.join(encode(p) for p in pieces) def join(chars=()): """ Join a sequence of characters into a string. :param bytes chars: :rtype str: """ return ''.join(chars) stomp.py-4.1.19/stomp/test/0000755000175000017500000000000013171175615015067 5ustar sophiesophiestomp.py-4.1.19/stomp/test/transport_test.py0000644000175000017500000000154313171175615020537 0ustar sophiesophieimport logging import unittest import stomp class TestTransport(unittest.TestCase): def setUp(self): self.transport = stomp.transport.BaseTransport() def test_process_frame_unknown_command_empty_body(self): fr = stomp.utils.Frame('test', {}, None) self.transport.process_frame(fr, None) def test_process_frame_empty_body(self): stomp.transport.log.setLevel(logging.INFO) fr = stomp.utils.Frame('error', {}, None) self.transport.process_frame(fr, None) def test_process_frame_unknown_command(self): fr = stomp.utils.Frame('test', {}, 'test message') self.transport.process_frame(fr, None) def test_process_frame(self): stomp.transport.log.setLevel(logging.INFO) fr = stomp.utils.Frame('error', {}, 'test message') self.transport.process_frame(fr, None) stomp.py-4.1.19/stomp/test/p3_nonascii_test.py0000644000175000017500000001110013171175615020676 0ustar sophiesophie# -*- coding: UTF-8 -*- import filecmp import time import unittest import stomp from stomp.listener import TestListener from stomp.test.testutils import * class TestNonAsciiSend(unittest.TestCase): def setUp(self): conn = stomp.Connection(get_default_host(), auto_decode=False) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) self.conn = conn self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def tearDown(self): if self.conn: self.conn.disconnect(receipt=None) def test_send_nonascii(self): queuename = '/queue/p3nonasciitest-%s' % self.timestamp self.conn.subscribe(destination=queuename, ack='auto', id='1') txt = 'марко' self.conn.send(body=txt, destination=queuename, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.connections >= 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages >= 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') (_, msg) = self.listener.get_latest_message() self.assertEqual(stomp.backward.encode(txt), msg) def test_image_send(self): d = os.path.dirname(os.path.realpath(__file__)) srcname = os.path.join(d, 'test.gif') with open(srcname, 'rb') as f: img = f.read() queuename = '/queue/p3nonascii-image-%s' % self.timestamp self.conn.subscribe(destination=queuename, ack='auto', id='1') self.conn.send(body=img, destination=queuename, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.connections >= 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages >= 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') (_, msg) = self.listener.get_latest_message() self.assertEqual(img, msg) destname = os.path.join(d, 'test-out.gif') with open(destname, 'wb') as f: f.write(img) self.assertTrue(filecmp.cmp(srcname, destname)) def test_image_send(self): d = os.path.dirname(os.path.realpath(__file__)) srcname = os.path.join(d, 'test.gif.gz') with open(srcname, 'rb') as f: img = f.read() queuename = '/queue/p3nonascii-image-%s' % self.timestamp self.conn.subscribe(destination=queuename, ack='auto', id='1') self.conn.send(body=img, destination=queuename, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.connections >= 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages >= 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') (_, msg) = self.listener.get_latest_message() self.assertEqual(img, msg) destname = os.path.join(d, 'test-out.gif.gz') with open(destname, 'wb') as f: f.write(img) self.assertTrue(filecmp.cmp(srcname, destname)) class TestNonAsciiSendAutoEncoding(unittest.TestCase): def setUp(self): conn = stomp.Connection(get_default_host(), auto_decode=True) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) self.conn = conn self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def tearDown(self): if self.conn: self.conn.disconnect(receipt=None) def test_send_nonascii_auto_encoding(self): queuename = '/queue/p3nonasciitest2-%s' % self.timestamp self.conn.subscribe(destination=queuename, ack='auto', id='1') txt = 'марко' self.conn.send(body=txt, destination=queuename, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.connections >= 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages >= 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') (_, msg) = self.listener.get_latest_message() self.assertEqual(txt, msg) stomp.py-4.1.19/stomp/test/stompserver_test.py0000644000175000017500000000165213171175615021075 0ustar sophiesophieimport unittest import stomp from stomp.listener import TestListener from stomp.test.testutils import * class TestStompServerSend(unittest.TestCase): def setUp(self): pass def testbasic(self): conn = stomp.Connection10(get_stompserver_host()) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect(wait=True) conn.subscribe(destination='/queue/test', ack='auto') conn.send(body='this is a test', destination='/queue/test', receipt='123') listener.wait_on_receipt() conn.unsubscribe('/queue/test') conn.disconnect(receipt=None) self.assertTrue(listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(listener.messages == 1, 'should have received 1 message') self.assertTrue(listener.errors == 0, 'should not have received any errors') stomp.py-4.1.19/stomp/test/cli_ssl_test.py0000644000175000017500000000212213171175615020125 0ustar sophiesophieimport time import unittest from stomp.__main__ import StompCLI from stomp.test.testutils import * username = get_default_user() password = get_default_password() (sslhost, sslport) = get_default_ssl_host()[0] class TestSSLCLI(unittest.TestCase): def setUp(self): pass def testssl(self): teststdin = TestStdin() teststdout = TestStdout(self) teststdout.expect('CONNECTED') cli = StompCLI(sslhost, sslport, username, password, '1.0', use_ssl=True, stdin=teststdin, stdout=teststdout) time.sleep(3) teststdout.expect('Subscribing to "/queue/testsubscribe" with acknowledge set to "auto", id set to "1"') cli.onecmd('subscribe /queue/testsubscribe') teststdout.expect('MESSAGE') teststdout.expect('this is a test') cli.onecmd('send /queue/testsubscribe this is a test') time.sleep(3) teststdout.expect('Unsubscribing from "/queue/testsubscribe"') cli.onecmd('unsubscribe /queue/testsubscribe') teststdout.expect('Shutting down, please wait') cli.onecmd('quit') stomp.py-4.1.19/stomp/test/misc_test.py0000644000175000017500000000757613171175615017452 0ustar sophiesophieimport time import traceback import unittest import xml.dom.minidom import stomp from stomp.exception import * from stomp.listener import * from stomp.test.testutils import * log = logging.getLogger('stomp.py') class TransformationListener(TestListener): def __init__(self, receipt): TestListener.__init__(self, receipt) self.message = None def on_before_message(self, headers, body): if 'transformation' in headers: trans_type = headers['transformation'] if trans_type != 'jms-map-xml': return body try: entries = {} doc = xml.dom.minidom.parseString(body) rootElem = doc.documentElement for entryElem in rootElem.getElementsByTagName("entry"): pair = [] for node in entryElem.childNodes: if not isinstance(node, xml.dom.minidom.Element): continue pair.append(node.firstChild.nodeValue) assert len(pair) == 2 entries[pair[0]] = pair[1] return (headers, entries) except Exception: # # unable to parse message. return original # traceback.print_exc() return (headers, body) def on_message(self, headers, body): TestListener.on_message(self, headers, body) self.message = body class TestMessageTransform(unittest.TestCase): def setUp(self): conn = stomp.Connection(get_default_host()) listener = TransformationListener('123') conn.set_listener('', listener) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) self.conn = conn self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def tearDown(self): if self.conn: self.conn.disconnect(receipt=None) def test_transform(self): queuename = '/queue/testtransform-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto') self.conn.send(body=''' name Dejan city Belgrade ''', destination=queuename, headers={'transformation': 'jms-map-xml'}, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.message is not None, 'Did not receive a message') self.assertTrue(self.listener.message.__class__ == dict, 'Message type should be dict after transformation, was %s' % self.listener.message.__class__) self.assertTrue(self.listener.message['name'] == 'Dejan', 'Missing an expected dict element') self.assertTrue(self.listener.message['city'] == 'Belgrade', 'Missing an expected dict element') class TestNoResponseConnectionKill(unittest.TestCase): def setUp(self): self.server = TestStompServer('127.0.0.1', 60000) self.server.start() self.timeout_thread = threading.Thread(name='shutdown test server', target=self.timeout_server) def timeout_server(self): time.sleep(3) log.info('Stopping server') self.server.running = False self.server.stop() def test_noresponse(self): try: conn = stomp.Connection([('127.0.0.1', 60000)], heartbeats=(1000, 1000)) listener = TestListener() conn.set_listener('', listener) conn.start() self.timeout_thread.start() conn.connect(wait=True) self.fail("Shouldn't happen") except ConnectFailedException: log.info('Received connect failed - test success') except Exception: self.fail("Shouldn't happen") stomp.py-4.1.19/stomp/test/testutils.py0000644000175000017500000001163713171175615017511 0ustar sophiesophietry: from configparser import RawConfigParser except ImportError: from ConfigParser import RawConfigParser import logging import os import re import socket import threading from stomp.backward import * log = logging.getLogger('testutils.py') config = RawConfigParser() config.read(os.path.join(os.path.dirname(__file__), 'setup.ini')) header_re = re.compile(r'[^:]+:.*') def get_environ(name): try: return os.environ[name] except: return None def get_default_host(): host = config.get('default', 'host') port = config.get('default', 'port') return [(get_environ('STD_HOST') or host, int(get_environ('STD_PORT') or port))] def get_default_vhost(): try: vhost = config.get('default', 'vhost') except: vhost = None return get_environ('STD_VHOST') or vhost def get_default_user(): user = config.get('default', 'user') return get_environ('STD_USER') or user def get_default_password(): password = config.get('default', 'password') return get_environ('STD_PASSWORD') or password def get_ipv6_host(): host = config.get('ipv6', 'host') port = config.get('ipv6', 'port') return [(get_environ('IPV6_HOST') or host, int(get_environ('IPV6_PORT') or port))] def get_default_ssl_host(): host = config.get('default', 'host') port = config.get('default', 'ssl_port') return [(get_environ('STD_HOST') or host, int(get_environ('STD_SSL_PORT') or port))] def get_sni_ssl_host(): host = config.get('sni', 'host') port = config.get('sni', 'ssl_port') return [(get_environ('SNI_HOST') or host, int(get_environ('SNI_SSL_PORT') or port))] def get_rabbitmq_host(): host = config.get('rabbitmq', 'host') port = config.get('rabbitmq', 'port') return [(get_environ('RABBITMQ_HOST') or host, int(get_environ('RABBITMQ_PORT') or port))] def get_rabbitmq_user(): user = config.get('rabbitmq', 'user') return get_environ('RABBITMQ_USER') or user def get_rabbitmq_password(): password = config.get('rabbitmq', 'password') return get_environ('RABBITMQ_PASSWORD') or password def get_stompserver_host(): host = config.get('stompserver', 'host') port = config.get('stompserver', 'port') return [(get_environ('STOMPSERVER_HOST') or host, int(get_environ('STOMPSERVER_PORT') or port))] class TestStompServer(object): def __init__(self, host, port): self.host = host self.port = port self.frames = [] def start(self): log.debug('Starting stomp server') self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.s.bind((self.host, self.port)) self.s.listen(1) self.running = True thread = threading.Thread(None, self.run) thread.daemon = True thread.start() self.stopped = False log.debug('Stomp server started') def stop(self): log.debug('Stopping test server') if self.conn: try: self.conn.shutdown(socket.SHUT_WR) except Exception: pass self.conn.close() if self.s: self.s.close() self.running = False self.conn = None self.s = None self.stopped = True log.debug('Connection stopped') def get_next_frame(self): if len(self.frames) > 0: rtn = self.frames[0] del self.frames[0] return rtn else: return '' def add_frame(self, frame): self.frames.append(frame) def run(self): self.conn, _ = self.s.accept() while self.running: try: _ = self.conn.recv(1024) frame = self.get_next_frame() if self.conn is None: break if frame is not None: self.conn.send(encode(frame)) except Exception: _, e, _ = sys.exc_info() log.debug(e) break try: self.conn.close() except: pass self.stopped = True log.debug('Run loop completed') class TestStdin(object): pass class TestStdout(object): def __init__(self, test): self.test = test self.expects = [] def expect(self, txt): self.expects.insert(0, re.compile(txt)) def write(self, txt): txt = txt.rstrip() if txt != '': print(txt) if txt == '>' or txt == '' or header_re.match(txt): return if len(self.expects) == 0: self.test.fail('No expectations - actual "%s"' % txt) return for x in range(0, len(self.expects)): chk = self.expects[x] if chk.match(txt): del self.expects[x] return self.test.fail('"%s" was not expected' % txt) def flush(self): pass stomp.py-4.1.19/stomp/test/cli_test.py0000644000175000017500000001514613171175615017256 0ustar sophiesophieimport tempfile import time import unittest from stomp.__main__ import StompCLI from stomp.test.testutils import * username = get_default_user() password = get_default_password() (host, port) = get_default_host()[0] test_text = '''subscribe /queue/testfile send /queue/testfile this is a test unsubscribe /queue/testfile''' def create_test_file(): with tempfile.NamedTemporaryFile('w', delete=False) as f: f.write('''subscribe /queue/testfile send /queue/testfile this is a test unsubscribe /queue/testfile''') return f class TestCLI(unittest.TestCase): def setUp(self): pass def testsubscribe(self): teststdin = TestStdin() teststdout = TestStdout(self) teststdout.expect('CONNECTED') cli = StompCLI(host, port, username, password, '1.0', stdin=teststdin, stdout=teststdout) time.sleep(3) teststdout.expect('Subscribing to "/queue/testsubscribe" with acknowledge set to "auto", id set to "1"') cli.onecmd('subscribe /queue/testsubscribe') teststdout.expect('MESSAGE') teststdout.expect('this is a test') cli.onecmd('send /queue/testsubscribe this is a test') time.sleep(3) teststdout.expect('Unsubscribing from "/queue/testsubscribe"') cli.onecmd('unsubscribe /queue/testsubscribe') teststdout.expect('Shutting down, please wait') cli.onecmd('quit') def testsendrec(self): teststdin = TestStdin() teststdout = TestStdout(self) teststdout.expect('CONNECTED') cli = StompCLI(host, port, username, password, '1.0', stdin=teststdin, stdout=teststdout) time.sleep(3) teststdout.expect('RECEIPT') cli.onecmd('sendrec /queue/testsendrec this is a test') time.sleep(3) teststdout.expect('Shutting down, please wait') cli.onecmd('quit') def testsendfile(self): f = create_test_file() teststdin = TestStdin() teststdout = TestStdout(self) teststdout.expect('CONNECTED') cli = StompCLI(host, port, username, password, '1.0', stdin=teststdin, stdout=teststdout) time.sleep(3) cli.onecmd('sendfile /queue/testsendfile %s' % f.name) time.sleep(3) teststdout.expect('Shutting down, please wait') cli.onecmd('quit') def testsendfileheaders(self): f = create_test_file() teststdin = TestStdin() teststdout = TestStdout(self) teststdout.expect('CONNECTED') cli = StompCLI(host, port, username, password, '1.0', stdin=teststdin, stdout=teststdout) time.sleep(3) cli.onecmd('sendfile /queue/testsendfile %s { "custom" : "header" }' % f.name) time.sleep(3) teststdout.expect('Shutting down, please wait') cli.onecmd('quit') def testabort(self): teststdin = TestStdin() teststdout = TestStdout(self) teststdout.expect('CONNECTED') cli = StompCLI(host, port, username, password, '1.0', stdin=teststdin, stdout=teststdout) time.sleep(3) teststdout.expect('Subscribing to "/queue/testabort" with acknowledge set to "auto", id set to "1"') cli.onecmd('subscribe /queue/testabort') cli.onecmd('begin') cli.onecmd('send /queue/testabort this is a test') cli.onecmd('abort') time.sleep(3) teststdout.expect('Unsubscribing from "/queue/testabort"') cli.onecmd('unsubscribe /queue/testabort') teststdout.expect('Shutting down, please wait') cli.onecmd('quit') def testcommit(self): teststdin = TestStdin() teststdout = TestStdout(self) teststdout.expect('CONNECTED') cli = StompCLI(host, port, username, password, '1.0', stdin=teststdin, stdout=teststdout) time.sleep(3) teststdout.expect('Subscribing to "/queue/testcommit" with acknowledge set to "auto", id set to "1"') cli.onecmd('subscribe /queue/testcommit') cli.onecmd('begin') cli.onecmd('send /queue/testcommit this is a test') teststdout.expect('Committing.*') teststdout.expect('MESSAGE') teststdout.expect('this is a test') cli.onecmd('commit') time.sleep(3) teststdout.expect('Unsubscribing from "/queue/testcommit"') cli.onecmd('unsubscribe /queue/testcommit') teststdout.expect('Shutting down, please wait') cli.onecmd('quit') def teststats(self): teststdin = TestStdin() teststdout = TestStdout(self) teststdout.expect('CONNECTED') cli = StompCLI(host, port, username, password, '1.0', stdin=teststdin, stdout=teststdout) time.sleep(3) teststdout.expect('Subscribing to "/queue/teststats" with acknowledge set to "auto", id set to "1"') cli.onecmd('subscribe /queue/teststats') teststdout.expect('.*No stats available.*') cli.onecmd('stats') time.sleep(1) cli.onecmd('stats on') cli.onecmd('send /queue/teststats this is a test') teststdout.expect('MESSAGE') teststdout.expect('this is a test') time.sleep(3) cli.onecmd('stats') teststdout.expect('Unsubscribing from "/queue/teststats"') cli.onecmd('unsubscribe /queue/teststats') teststdout.expect('Shutting down, please wait') cli.onecmd('quit') def testrun(self): f = create_test_file() teststdin = TestStdin() teststdout = TestStdout(self) teststdout.expect('CONNECTED') cli = StompCLI(host, port, username, password, '1.0', stdin=teststdin, stdout=teststdout) time.sleep(3) teststdout.expect('Subscribing to "/queue/testfile" with acknowledge set to "auto", id set to "1"') teststdout.expect('this is a test') teststdout.expect('MESSAGE') teststdout.expect('Unsubscribing from "/queue/testfile"') cli.onecmd('run %s' % f.name) teststdout.expect('Shutting down, please wait') cli.onecmd('quit') def testrunarg(self): f = create_test_file() teststdin = TestStdin() teststdout = TestStdout(self) teststdout.expect('CONNECTED') cli = StompCLI(host, port, username, password, '1.0', stdin=teststdin, stdout=teststdout) time.sleep(3) teststdout.expect('Subscribing to "/queue/testfile" with acknowledge set to "auto", id set to "1"') teststdout.expect('this is a test') teststdout.expect('MESSAGE') teststdout.expect('Unsubscribing from "/queue/testfile"') cli.do_run(f.name) teststdout.expect('Shutting down, please wait') cli.onecmd('quit') stomp.py-4.1.19/stomp/test/s12_test.py0000644000175000017500000001056113171175615017110 0ustar sophiesophieimport time import unittest try: from unittest.mock import Mock except ImportError: from mock import Mock import stomp from stomp import exception from stomp.listener import TestListener from stomp.test.testutils import * class Test12Connect(unittest.TestCase): def setUp(self): conn = stomp.Connection12(get_default_host(), vhost=get_default_vhost()) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) self.conn = conn self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def tearDown(self): if self.conn: self.conn.disconnect(receipt=None) def test_send(self): queuename = '/queue/testsend12-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto') self.conn.send(body='this is a test using protocol 1.2', destination=queuename, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') def test_clientack(self): queuename = '/queue/testclientack12-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='client-individual') self.conn.send(body='this is a test', destination=queuename, receipt='123') self.listener.wait_for_message() (headers, _) = self.listener.get_latest_message() ack_id = headers['ack'] self.conn.ack(ack_id) def test_clientnack(self): queuename = '/queue/testclientnack12-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='client-individual') self.conn.send(body='this is a test', destination=queuename, receipt='123') self.listener.wait_for_message() (headers, _) = self.listener.get_latest_message() ack_id = headers['ack'] self.conn.nack(ack_id) def test_timeout(self): server = TestStompServer('127.0.0.1', 60000) try: server.start() server.add_frame('''ERROR message: connection failed\x00''') conn = stomp.Connection12([('127.0.0.1', 60000)]) listener = TestListener() conn.set_listener('', listener) conn.start() try: conn.connect(wait=True) self.fail("shouldn't happen") except exception.ConnectFailedException: pass finally: server.stop() def test_specialchars(self): queuename = '/queue/testspecialchars12-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='client') hdrs = { 'special-1': 'test with colon : test', 'special-2': 'test with backslash \\ test', 'special-3': 'test with newlines \n \n', 'special-4': 'test with carriage return \r' } self.conn.send(body='this is a test', headers=hdrs, destination=queuename, receipt='123') self.listener.wait_for_message() (headers, _) = self.listener.get_latest_message() _ = headers['message-id'] _ = headers['subscription'] self.assertTrue('special-1' in headers) self.assertEqual('test with colon : test', headers['special-1']) self.assertTrue('special-2' in headers) self.assertEqual('test with backslash \\ test', headers['special-2']) self.assertTrue('special-3' in headers) self.assertEqual('test with newlines \n \n', headers['special-3']) self.assertTrue('special-4' in headers) self.assertEqual('test with carriage return \r', headers['special-4']) def test_suppress_content_length(self): queuename = '/queue/testspecialchars12-%s' % self.timestamp self.conn = stomp.Connection12(get_default_host(), vhost=get_default_vhost(), auto_content_length=False) self.conn.transport = Mock() self.conn.send(body='test', destination=queuename, receipt='123') args, kwargs = self.conn.transport.transmit.call_args frame = args[0] self.assertTrue('content-length' not in frame.headers) stomp.py-4.1.19/stomp/test/p2_nonascii_test.py0000644000175000017500000000251413171175615020706 0ustar sophiesophie# -*- coding: UTF-8 -*- import time import unittest import stomp from stomp.listener import TestListener from stomp.test.testutils import * class TestNonAsciiSend(unittest.TestCase): def setUp(self): conn = stomp.Connection(get_default_host()) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) self.conn = conn self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def tearDown(self): if self.conn: self.conn.disconnect(receipt=None) def test_send_nonascii(self): queuename = '/queue/p2nonasciitest-%s' % self.timestamp self.conn.subscribe(destination=queuename, ack='auto', id="1") txt = u'марко' self.conn.send(body=txt, destination=queuename, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') (_, msg) = self.listener.get_latest_message() self.assertEqual(stomp.backward.encode(txt), msg) stomp.py-4.1.19/stomp/test/utils_test.py0000644000175000017500000000602213171175615017640 0ustar sophiesophieimport unittest from stomp.backward import * from stomp.utils import * class TestUtils(unittest.TestCase): def test_returns_true_when_localhost(self): self.assertEqual(1, is_localhost(('localhost', 8000))) self.assertEqual(1, is_localhost(('127.0.0.1', 8000))) self.assertEqual(2, is_localhost(('192.168.1.92', 8000))) def test_convert_frame_to_lines(self): f = Frame('SEND', { 'header1': 'value1', 'headerNone': None, ' no ': ' trimming ', }, 'this is the body') lines = convert_frame_to_lines(f) s = pack(lines) if sys.hexversion >= 0x03000000: self.assertEqual(bytearray('SEND\n no : trimming \nheader1:value1\n\nthis is the body\x00', 'ascii'), s) else: self.assertEqual('SEND\n no : trimming \nheader1:value1\n\nthis is the body\x00', s) def test_parse_headers(self): lines = [ r'h1:foo\c\\bar ', r'h1:2nd h1 ignored -- not a must, but allowed and that is how we behave ATM', r'h\c2:baz\r\nquux', r'h3:\\n\\c', r'against-spec:\t', # should actually raise or something, we're against spec here ATM r' foo : bar', ] self.assertEqual({ 'h1': r'foo:\bar ', 'h:2': 'baz\r\nquux', 'h3': r'\n\c', 'against-spec': r'\t', ' foo ': ' bar', }, parse_headers(lines)) def test_calculate_heartbeats(self): chb = (3000, 5000) shb = map(str, reversed(chb)) self.assertEqual((3000, 5000), calculate_heartbeats(shb, chb)) shb = ('6000', '2000') self.assertEqual((3000, 6000), calculate_heartbeats(shb, chb)) shb = ('0', '0') self.assertEqual((0, 0), calculate_heartbeats(shb, chb)) shb = ('10000', '0') self.assertEqual((0, 10000), calculate_heartbeats(shb, chb)) chb = (0, 0) self.assertEqual((0, 0), calculate_heartbeats(shb, chb)) def test_parse_frame(self): # heartbeat f = parse_frame(b'\x0a') self.assertEqual(str(f), str(Frame('heartbeat'))) # oddball/broken f = parse_frame(b'FOO') self.assertEqual(str(f), str(Frame('FOO', body=b''))) # empty body f = parse_frame(b'RECEIPT\nreceipt-id:message-12345\n\n') self.assertEqual(str(f), str(Frame('RECEIPT', {'receipt-id': 'message-12345'}, b''))) # no headers f = parse_frame(b'ERROR\n\n') self.assertEqual(str(f), str(Frame('ERROR', body=b''))) # regular, different linefeeds for lf in b'\n', b'\r\n': f = parse_frame( b'MESSAGE' + lf + b'content-type:text/plain' + lf + lf + b'hello world!' ) self.assertEqual(str(f), str(Frame('MESSAGE', {'content-type': 'text/plain'}, b'hello world!'))) def test_clean_default_headers(self): Frame().headers['foo'] = 'bar' self.assertEqual(Frame().headers, {}) stomp.py-4.1.19/stomp/test/multicast_test.py0000644000175000017500000000550613171175615020513 0ustar sophiesophieimport time import unittest import uuid from stomp.adapter.multicast import MulticastConnection from stomp.listener import TestListener from stomp.test.testutils import * class TestMulticast(unittest.TestCase): def setUp(self): conn = MulticastConnection() listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect() self.conn = conn self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def tearDown(self): if self.conn: self.conn.disconnect(receipt=None) def testsubscribe(self): queuename = '/queue/test1-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto') self.conn.send(body='this is a test', destination=queuename, receipt='123') self.listener.wait_on_receipt() self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') def testunsubscribe(self): queuename = '/queue/test1-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto') self.conn.send(body='this is a test', destination=queuename, receipt='123') self.listener.wait_on_receipt() self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') self.conn.unsubscribe(1) self.conn.send(body='this is a test', destination=queuename, receipt='124') time.sleep(3) self.assertTrue(self.listener.messages == 1, 'should have only received 1 message') def testtransactions(self): queuename = '/queue/test1-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto') trans_id = str(uuid.uuid4()) self.conn.begin(trans_id) self.conn.send(body='this is a test', transaction=trans_id, destination=queuename, receipt='123') time.sleep(1) self.assertTrue(self.listener.messages == 0, 'should not have received any messages') self.conn.commit(trans_id) self.listener.wait_on_receipt() self.assertTrue(self.listener.messages == 1, 'should have received 1 message') self.conn.begin(trans_id) self.conn.send(body='this is a test', transaction=trans_id, destination=queuename, receipt='124') self.conn.abort(trans_id) time.sleep(3) self.assertTrue(self.listener.messages == 1, 'should have only received 1 message') stomp.py-4.1.19/stomp/test/p3_backward_test.py0000644000175000017500000000153613171175615020665 0ustar sophiesophieimport unittest from stomp import backward3 class TestBackward3(unittest.TestCase): def test_pack_mixed_string_and_bytes(self): lines = ['SEND', '\n', 'header1:test', '\u6771'] self.assertEqual(backward3.encode(backward3.pack(lines)), b'SEND\nheader1:test\xe6\x9d\xb1') lines = ['SEND', '\n', 'header1:test', b'\xe6\x9d\xb1'] self.assertEqual(backward3.encode(backward3.pack(lines)), b'SEND\nheader1:test\xe6\x9d\xb1') def test_decode(self): self.assertTrue(backward3.decode(None) is None) self.assertEqual('test', backward3.decode(b'test')) def test_encode(self): self.assertEqual(b'test', backward3.encode('test')) self.assertEqual(b'test', backward3.encode(b'test')) self.assertRaises(TypeError, backward3.encode, None) stomp.py-4.1.19/stomp/test/haproxy.cfg0000644000175000017500000000054613171175615017247 0ustar sophiesophiedefaults mode tcp option tcplog frontend ft_test bind 0.0.0.0:65001 ssl crt tmp/myorg.pem crt tmp/mycom.pem no-sslv3 no-tls-tickets use_backend bk_com_cert if { ssl_fc_sni my.example.com } use_backend bk_org_cert if { ssl_fc_sni my.example.org } backend bk_com_cert server srv1 127.0.0.1:62613 backend bk_org_cert server srv2 127.0.0.1:62614stomp.py-4.1.19/stomp/test/override_threading_test.py0000644000175000017500000000402013171175615022340 0ustar sophiesophieimport os import signal import time import unittest import stomp from stomp import exception from stomp.backward import monotonic from stomp.listener import TestListener from stomp.test.testutils import * from concurrent.futures import ThreadPoolExecutor executor = ThreadPoolExecutor() def create_thread(fc): f = executor.submit(fc) print('Created future %s on executor %s' % (f, executor)) return f class ReconnectListener(TestListener): def __init__(self, conn): TestListener.__init__(self, '123') self.conn = conn def on_receiver_loop_ended(self, *args): if self.conn: c = self.conn self.conn = None c.start() c.connect(get_default_user(), get_default_password(), wait=True) c.disconnect() class TestThreadingOverride(unittest.TestCase): def setUp(self): conn = stomp.Connection(get_default_host()) # check thread override here conn.transport.override_threading(create_thread) listener = ReconnectListener(conn) conn.set_listener('', listener) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) self.conn = conn self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def test_basic(self): queuename = '/queue/test1-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto') self.conn.send(body='this is a test', destination=queuename, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') self.conn.disconnect(receipt=None) self.conn.start() self.conn.connect(get_default_user(), get_default_password(), wait=True) self.conn.disconnect()stomp.py-4.1.19/stomp/test/p2_backward_test.py0000644000175000017500000000144313171175615020661 0ustar sophiesophieimport unittest from stomp import backward2 class TestBackward2(unittest.TestCase): def test_pack_mixed_string_and_bytes(self): lines = ['SEND', '\n', u'header1:test', u'\u6771'] self.assertEqual(backward2.encode(backward2.pack(lines)), 'SEND\nheader1:test\xe6\x9d\xb1') lines = ['SEND', '\n', u'header1:test', '\xe6\x9d\xb1'] self.assertEqual(backward2.encode(backward2.pack(lines)), 'SEND\nheader1:test\xe6\x9d\xb1') def test_decode(self): self.assertTrue(backward2.decode(None) is None) self.assertEqual(b'test', backward2.decode(b'test')) def test_encode(self): self.assertEqual(b'test', backward2.encode(u'test')) self.assertEqual(b'test', backward2.encode(b'test')) stomp.py-4.1.19/stomp/test/local_test.py0000644000175000017500000000220413171175615017570 0ustar sophiesophieimport time import unittest import stomp from stomp.listener import TestListener from stomp.test.testutils import * class TestIPV6Send(unittest.TestCase): def setUp(self): conn = stomp.Connection11(get_ipv6_host()) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect('admin', 'password', wait=True) self.conn = conn self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def tearDown(self): if self.conn: self.conn.disconnect(receipt=None) def test_ipv6(self): queuename = '/queue/testipv6-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto') self.conn.send(body='this is a test', destination=queuename, receipt='123') self.listener.wait_on_receipt() self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') stomp.py-4.1.19/stomp/test/s11_test.py0000644000175000017500000000572113171175615017111 0ustar sophiesophieimport time import unittest import stomp from stomp.listener import TestListener, WaitingListener from stomp.test.testutils import * class Test11Send(unittest.TestCase): def test11(self): conn = stomp.Connection(get_default_host()) tl = TestListener('123') conn.set_listener('', tl) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) conn.subscribe(destination='/queue/test', ack='auto', id=1) conn.send(body='this is a test', destination='/queue/test', receipt='123') tl.wait_for_message() self.assertTrue(tl.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(tl.messages >= 1, 'should have received at least 1 message') self.assertTrue(tl.errors == 0, 'should not have received any errors') conn.unsubscribe(destination='/queue/test', id=1) wl = WaitingListener('DISCONNECT1') conn.set_listener('waiting', wl) # stomp1.1 disconnect with receipt conn.disconnect(receipt='DISCONNECT1') # wait for the receipt wl.wait_on_receipt() def testheartbeat(self): conn = stomp.Connection(get_default_host(), heartbeats=(2000, 3000)) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) self.assertTrue(conn.heartbeats[0] > 0) conn.subscribe(destination='/queue/test', ack='auto', id=1) conn.send(body='this is a test', destination='/queue/test', receipt='123') listener.wait_for_message() conn.disconnect(receipt=None) self.assertTrue(listener.connections >= 1, 'should have received 1 connection acknowledgement, was %s' % listener.connections) self.assertTrue(listener.messages >= 1, 'should have received 1 message, was %s' % listener.messages) self.assertTrue(listener.errors == 0, 'should not have received any errors, was %s' % listener.errors) self.assertTrue(listener.heartbeat_timeouts == 0, 'should not have received a heartbeat timeout, was %s' % listener.heartbeat_timeouts) def testheartbeat_timeout(self): server = TestStompServer('127.0.0.1', 60000) server.start() try: server.add_frame('''CONNECTED version:1.1 session:1 server:test heart-beat:1000,1000 \x00''') conn = stomp.Connection([('127.0.0.1', 60000)], heartbeats=(1000, 1000)) listener = TestListener() conn.set_listener('', listener) conn.start() conn.connect() time.sleep(5) server.running = False except Exception: _, e, _ = sys.exc_info() log.error("Error: %s", e) finally: server.stop() self.assertTrue(listener.heartbeat_timeouts >= 1, 'should have received a heartbeat timeout') stomp.py-4.1.19/stomp/test/ss_test.py0000644000175000017500000001222713171175615017131 0ustar sophiesophietry: from exceptions import AssertionError except ImportError: pass import logging import sys import time import unittest import stomp from stomp.listener import TestListener from stomp.test.testutils import * log = logging.getLogger('ss_test.py') class TestWithStompServer(unittest.TestCase): def setUp(self): self.server = TestStompServer('127.0.0.1', 60000) self.server.start() def tearDown(self): self.server.stop() def test_disconnect(self): self.server.add_frame('''CONNECTED version:1.1 session:1 server:test heart-beat:1000,1000 \x00''') conn = stomp.Connection([('127.0.0.1', 60000)]) listener = TestListener() conn.set_listener('', listener) conn.start() conn.connect() time.sleep(2) self.server.stop() for _ in range(100): if self.server.stopped: break time.sleep(0.1) else: assert False, 'server never disconnected' time.sleep(1) try: conn.send(body='test disconnect', destination='/test/disconnectqueue') self.fail('Should not have successfully sent a message at this point') except Exception: _, e, _ = sys.exc_info() if e.__class__ == AssertionError: self.fail(str(e)) log.debug('stopping conn after expected exception %s', e) # lost connection, now restart the server try: conn.disconnect(receipt=None) except: pass time.sleep(2) self.server.add_frame('''CONNECTED version:1.1 session:1 server:test heart-beat:1000,1000 \x00''') self.server.start() conn.start() conn.connect() time.sleep(5) self.assertTrue(listener.connections >= 2, 'should have received 2 connection acknowledgements') def test_parsing(self): def pump(n): # pump; test server gives us one frame per received something for _ in range(n): conn.transport.send(b'\n') time.sleep(0.01) # Trailing optional EOLs in a frame self.server.add_frame('''CONNECTED version:1.1 session:1 server:test heart-beat:1000,1000 \x00''' + '\n\n\n') expected_heartbeat_count = 0 conn = stomp.Connection([('127.0.0.1', 60000)]) listener = TestListener() conn.set_listener('', listener) conn.start() conn.connect() time.sleep(2) self.assertEqual(expected_heartbeat_count, listener.heartbeat_count) # No trailing EOLs, separate heartbeat message_body = 'Hello\n...world!' message_frame = '''MESSAGE content-type:text/plain %s\x00''' % message_body self.server.add_frame(message_frame) self.server.add_frame('\n') expected_heartbeat_count += 1 pump(2) listener.wait_for_heartbeat() headers, body = listener.get_latest_message() self.assertEqual(expected_heartbeat_count, listener.heartbeat_count) self.assertEqual({"content-type": "text/plain"}, headers) self.assertEqual(message_body, body) # Trailing EOL, separate heartbeat, another message self.server.add_frame(message_frame + '\n') self.server.add_frame('\n') self.server.add_frame(message_frame + '\n') expected_heartbeat_count += 1 pump(3) listener.wait_for_heartbeat() listener.wait_for_message() headers, body = listener.get_latest_message() self.assertEqual(expected_heartbeat_count, listener.heartbeat_count) self.assertEqual({"content-type": "text/plain"}, headers) self.assertEqual(message_body, body) # Torture tests: return content one byte at a time self.server.add_frame('\n') for c in message_frame: self.server.add_frame(c) self.server.add_frame('\n') expected_heartbeat_count += 2 pump(len(message_frame) + 2) listener.wait_for_heartbeat() headers, body = listener.get_latest_message() self.assertEqual(expected_heartbeat_count, listener.heartbeat_count) self.assertEqual({"content-type": "text/plain"}, headers) self.assertEqual(message_body, body) # ...and a similar one with content-length and null bytes in body message_body = '%s\x00\x00%s' % (message_body, message_body) message_frame = '''MESSAGE content-type:text/plain content-length:%s %s\x00''' % (len(message_body), message_body) self.server.add_frame('\n') self.server.add_frame('\n') for c in message_frame: self.server.add_frame(c) self.server.add_frame('\n') expected_heartbeat_count += 3 pump(len(message_frame) + 3) listener.wait_for_heartbeat() headers, body = listener.get_latest_message() self.assertEqual(expected_heartbeat_count, listener.heartbeat_count) self.assertEqual({ "content-type": "text/plain", "content-length": str(len(message_body)), }, headers) self.assertEqual(message_body, body) stomp.py-4.1.19/stomp/test/setup.ini0000644000175000017500000000047113171175615016732 0ustar sophiesophie[default] host = 10.0.0.113 port = 62613 ssl_port = 62614 user = admin password = password [ipv6] host = fe80::a00:27ff:fe90:3f1a%en1 port = 62613 [rabbitmq] host = 10.0.0.113 port = 61613 user = guest password = guest [stompserver] host = 10.0.0.113 port = 63613 [sni] host = my.example.com ssl_port = 65001 stomp.py-4.1.19/stomp/test/test-out.gif.gz0000644000175000017500000000012113171175615017753 0ustar sophiesophieKVtest.gifst0Obbh`+da1u@HI1AF׶S+f[xgdŮE<stomp.py-4.1.19/stomp/test/basic_test.py0000644000175000017500000001632013171175615017563 0ustar sophiesophieimport os import signal import time import unittest import stomp from stomp import exception from stomp.backward import monotonic from stomp.listener import TestListener from stomp.test.testutils import * class TestBasicSend(unittest.TestCase): def setUp(self): conn = stomp.Connection(get_default_host()) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) self.conn = conn self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def tearDown(self): if self.conn: self.conn.disconnect(receipt=None) def test_basic(self): queuename = '/queue/test1-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto') self.conn.send(body='this is a test', destination=queuename, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') def test_commit(self): queuename = '/queue/test2-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto') trans_id = self.conn.begin() self.conn.send(body='this is a test1', destination=queuename, transaction=trans_id) self.conn.send(body='this is a test2', destination=queuename, transaction=trans_id) self.conn.send(body='this is a test3', destination=queuename, transaction=trans_id, receipt='123') time.sleep(3) self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 0, 'should not have received any messages') self.conn.commit(transaction=trans_id) self.listener.wait_for_message() time.sleep(3) self.assertTrue(self.listener.messages == 3, 'should have received 3 messages') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') def test_abort(self): queuename = '/queue/test3-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto') trans_id = self.conn.begin() self.conn.send(body='this is a test1', destination=queuename, transaction=trans_id) self.conn.send(body='this is a test2', destination=queuename, transaction=trans_id) self.conn.send(body='this is a test3', destination=queuename, transaction=trans_id) time.sleep(3) self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 0, 'should not have received any messages') self.conn.abort(transaction=trans_id) time.sleep(3) self.assertTrue(self.listener.messages == 0, 'should not have received any messages') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') def test_timeout(self): conn = stomp.Connection([('192.0.2.0', 60000)], timeout=5, reconnect_attempts_max=1) conn.set_listener('', self.listener) try: ms = monotonic() conn.start() self.fail("shouldn't happen") except exception.ConnectFailedException: pass # success! ms = monotonic() - ms self.assertTrue(ms > 5.0, 'connection timeout should have been at least 5 seconds') def test_childinterrupt(self): def childhandler(signum, frame): print("received child signal") oldhandler = signal.signal(signal.SIGCHLD, childhandler) queuename = '/queue/test5-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='auto', receipt='123') self.listener.wait_on_receipt() self.conn.send(body='this is an interrupt test 1', destination=queuename) print("causing signal by starting child process") os.system("sleep 1") time.sleep(1) signal.signal(signal.SIGCHLD, oldhandler) print("completed signal section") self.conn.send(body='this is an interrupt test 2', destination=queuename, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgment') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') self.assertTrue(self.conn.is_connected(), 'should still be connected to STOMP provider') def test_clientack(self): queuename = '/queue/testclientack-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='client') self.conn.send(body='this is a test', destination=queuename, receipt='123') self.listener.wait_for_message() (headers, _) = self.listener.get_latest_message() message_id = headers['message-id'] subscription = headers['subscription'] self.conn.ack(message_id, subscription) def test_clientnack(self): queuename = '/queue/testclientnack-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='client') self.conn.send(body='this is a test', destination=queuename, receipt='123') self.listener.wait_for_message() (headers, _) = self.listener.get_latest_message() message_id = headers['message-id'] subscription = headers['subscription'] self.conn.nack(message_id, subscription) def test_specialchars(self): queuename = '/queue/testspecialchars-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='client') hdrs = { 'special-1': 'test with colon : test', 'special-2': 'test with backslash \\ test', 'special-3': 'test with newline \n' } self.conn.send(body='this is a test', headers=hdrs, destination=queuename, receipt='123') self.listener.wait_for_message() (headers, _) = self.listener.get_latest_message() _ = headers['message-id'] _ = headers['subscription'] self.assertTrue('special-1' in headers) self.assertEqual('test with colon : test', headers['special-1']) self.assertTrue('special-2' in headers) self.assertEqual('test with backslash \\ test', headers['special-2']) self.assertTrue('special-3' in headers) self.assertEqual('test with newline \n', headers['special-3']) class TestConnectionErrors(unittest.TestCase): def test_connect_wait_error(self): conn = stomp.Connection(get_default_host()) conn.start() try: conn.connect('invalid', 'user', True) self.fail("Shouldn't happen") except: pass def test_connect_nowait_error(self): conn = stomp.Connection(get_default_host()) conn.start() try: conn.connect('invalid', 'user', False) self.assertFalse(conn.is_connected(), 'Should not be connected') except: self.fail("Shouldn't happen") stomp.py-4.1.19/stomp/test/test.gif.gz0000644000175000017500000000012113171175615017146 0ustar sophiesophieKVtest.gifst0Obbh`+da1u@HI1AF׶S+f[xgdŮE<stomp.py-4.1.19/stomp/test/s10_test.py0000644000175000017500000000315213171175615017104 0ustar sophiesophieimport time import unittest import stomp from stomp.listener import TestListener from stomp.test.testutils import * class Test10Connect(unittest.TestCase): def setUp(self): conn = stomp.Connection10(get_default_host()) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) self.conn = conn self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def tearDown(self): if self.conn: self.conn.disconnect(receipt=None) def testsend10(self): queuename = '/queue/testsend10-%s' % self.timestamp self.conn.subscribe(destination=queuename, ack='auto') self.conn.send(body='this is a test using protocol 1.0', destination=queuename, receipt='123') self.listener.wait_for_message() self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') def testclientack10(self): queuename = '/queue/testclientack10-%s' % self.timestamp self.conn.subscribe(destination=queuename, id=1, ack='client') self.conn.send(body='this is a test', destination=queuename) self.listener.wait_for_message() (headers, _) = self.listener.get_latest_message() message_id = headers['message-id'] self.conn.ack(message_id) time.sleep(1) stomp.py-4.1.19/stomp/test/threading_test.py0000644000175000017500000000746713171175615020463 0ustar sophiesophietry: from queue import Empty, Full, Queue except ImportError: from Queue import Empty, Full, Queue import sys import threading import time import unittest import stomp from stomp.backward import monotonic from stomp.test.testutils import * class MQ(object): def __init__(self): self.connection = stomp.Connection(get_default_host()) self.connection.set_listener('', None) self.connection.start() self.connection.connect('admin', 'password', wait=True) def send(self, topic, msg, persistent='true', retry=False): self.connection.send(destination="/topic/%s" % topic, body=msg, persistent=persistent) mq = MQ() class TestThreading(unittest.TestCase): def setUp(self): """Test that mq sends don't wedge their threads. Starts a number of sender threads, and runs for a set amount of time. Each thread sends messages as fast as it can, and after each send, pops from a Queue. Meanwhile, the Queue is filled with one marker per second. If the Queue fills, the test fails, as that indicates that all threads are no longer emptying the queue, and thus must be wedged in their send() calls. """ self.Q = Queue(10) self.Cmd = Queue() self.Error = Queue() self.clients = 20 self.threads = [] self.runfor = 20 for i in range(0, self.clients): t = threading.Thread(name="client %s" % i, target=self.make_sender(i)) t.setDaemon(1) self.threads.append(t) def tearDown(self): for t in self.threads: if not t.isAlive: print("thread", t, "died") self.Cmd.put('stop') for t in self.threads: t.join() print() print() errs = [] while 1: try: errs.append(self.Error.get(block=False)) except Empty: break print("Dead threads:", len(errs), "of", self.clients) etype = {} for ec, _, _ in errs: if ec in etype: etype[ec] += 1 else: etype[ec] = 1 for k in sorted(etype.keys()): print("%s: %s" % (k, etype[k])) mq.connection.disconnect() def make_sender(self, i): Q = self.Q Cmd = self.Cmd Error = self.Error def send(i=i, Q=Q, Cmd=Cmd, Error=Error): counter = 0 print("%s starting" % i) try: while 1: # print "%s sending %s" % (i, counter) try: mq.send('testclientwedge', 'Message %s:%s' % (i, counter)) except: Error.put(sys.exc_info()) # thread will die raise else: # print "%s sent %s" % (i, counter) try: Q.get(block=False) except Empty: pass try: if Cmd.get(block=False): break except Empty: pass counter += 1 finally: print("final", i, counter) return send def test_threads_dont_wedge(self): for t in self.threads: t.start() start = monotonic() while monotonic() - start < self.runfor: try: self.Q.put(1, False) time.sleep(1.0) except Full: assert False, "Failed: 'request' queue filled up" print("passed") stomp.py-4.1.19/stomp/test/ssl_sni_test.py0000644000175000017500000000264013171175615020154 0ustar sophiesophieimport unittest import stomp from stomp.listener import TestListener from stomp.test.testutils import * class TestSNIMQSend(unittest.TestCase): """ To test SNI: - Run a STOMP server in 127.0.0.1:62613 - Add a couple fully qualified hostnames to your /etc/hosts # SNI test hosts 127.0.0.1 my.example.com 127.0.0.1 my.example.org - Run `make haproxy` which will generate keys and run the haproxy load balancer Connections with SNI to "my.example.com" will be routed to the STOMP server on port 62613. Connections without SNI won't be routed. """ def setUp(self): pass def testconnect(self): conn = stomp.Connection11(get_sni_ssl_host()) conn.set_ssl(get_sni_ssl_host()) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect(get_default_user(), get_default_password(), wait=True) conn.subscribe(destination='/queue/test', id=1, ack='auto') conn.send(body='this is a test', destination='/queue/test', receipt='123') listener.wait_on_receipt() conn.disconnect(receipt=None) self.assertTrue(listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(listener.messages == 1, 'should have received 1 message') self.assertTrue(listener.errors == 0, 'should not have received any errors') stomp.py-4.1.19/stomp/test/ssl_test.py0000644000175000017500000000774613171175615017317 0ustar sophiesophieimport time import unittest import stomp from stomp import transport from stomp.listener import TestListener from stomp.test.testutils import * class TestSSL(unittest.TestCase): def setUp(self): listener = TestListener('123') self.listener = listener self.timestamp = time.strftime('%Y%m%d%H%M%S') def test_ssl_connection(self): try: import ssl queuename = '/queue/test4-%s' % self.timestamp conn = stomp.Connection(get_default_ssl_host()) conn.set_ssl(get_default_ssl_host()) conn.set_listener('', self.listener) conn.start() conn.connect('admin', 'password', wait=True) conn.subscribe(destination=queuename, id=1, ack='auto') conn.send(body='this is a test', destination=queuename, receipt='123') self.listener.wait_on_receipt() conn.disconnect(receipt=None) self.assertTrue(self.listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(self.listener.messages == 1, 'should have received 1 message') self.assertTrue(self.listener.errors == 0, 'should not have received any errors') except ImportError: pass class TestSSLParams(unittest.TestCase): def setUp(self): self.host1 = get_default_ssl_host()[0] self.host2 = get_default_ssl_host()[0] self.transport = transport.Transport(host_and_ports=[ self.host1, self.host2, ]) self.ssl_key_file = 'ssl_key_file' self.ssl_cert_file = 'ssl_cert_file' self.ssl_ca_certs = 'ssl_ca_certs' self.ssl_cert_validator = 'validator' self.ssl_version = 'version' def test_set_ssl(self): self.transport.set_ssl([self.host1], self.ssl_key_file, self.ssl_cert_file, self.ssl_ca_certs, self.ssl_cert_validator, self.ssl_version) self.assertEqual( self.transport._Transport__ssl_params[self.host1]['key_file'], self.ssl_key_file, ) self.assertEqual( self.transport._Transport__ssl_params[self.host1]['cert_file'], self.ssl_cert_file, ) self.assertEqual( self.transport._Transport__ssl_params[self.host1]['ca_certs'], self.ssl_ca_certs, ) self.assertEqual( self.transport._Transport__ssl_params[self.host1]['cert_validator'], self.ssl_cert_validator, ) self.assertEqual( self.transport._Transport__ssl_params[self.host1]['ssl_version'], self.ssl_version, ) def test_init_ssl_params(self): trans = transport.Transport( use_ssl=True, ssl_key_file=self.ssl_key_file, ssl_cert_file=self.ssl_cert_file, ssl_ca_certs=self.ssl_ca_certs, ssl_cert_validator=self.ssl_cert_validator, ssl_version=self.ssl_version, host_and_ports=[ self.host1, self.host2, ]) for host_port in [self.host1, self.host2]: self.assertEqual( trans._Transport__ssl_params[host_port]['key_file'], self.ssl_key_file, ) self.assertEqual( trans._Transport__ssl_params[host_port]['cert_file'], self.ssl_cert_file, ) self.assertEqual( trans._Transport__ssl_params[host_port]['ca_certs'], self.ssl_ca_certs, ) self.assertEqual( trans._Transport__ssl_params[host_port]['cert_validator'], self.ssl_cert_validator, ) self.assertEqual( trans._Transport__ssl_params[host_port]['ssl_version'], self.ssl_version, ) stomp.py-4.1.19/stomp/test/rabbitmq_test.py0000644000175000017500000000165613171175615020311 0ustar sophiesophieimport unittest import stomp from stomp.listener import TestListener from stomp.test.testutils import * class TestRabbitMQSend(unittest.TestCase): def setUp(self): pass def testbasic(self): conn = stomp.Connection11(get_rabbitmq_host()) listener = TestListener('123') conn.set_listener('', listener) conn.start() conn.connect(get_rabbitmq_user(), get_rabbitmq_password(), wait=True) conn.subscribe(destination='/queue/test', id=1, ack='auto') conn.send(body='this is a test', destination='/queue/test', receipt='123') listener.wait_on_receipt() conn.disconnect(receipt=None) self.assertTrue(listener.connections == 1, 'should have received 1 connection acknowledgement') self.assertTrue(listener.messages == 1, 'should have received 1 message') self.assertTrue(listener.errors == 0, 'should not have received any errors') stomp.py-4.1.19/stomp/test/test-out.gif0000644000175000017500000000007413171175615017343 0ustar sophiesophieGIF87a !, `|"L ʨ8KL_;stomp.py-4.1.19/stomp/test/__init__.py0000755000175000017500000000072313171175615017205 0ustar sophiesophieimport sys __all__ = ['basic_test', 'ss_test', 'cli_test', 'cli_ssl_test', 's10_test', 's11_test', 's12_test', 'rabbitmq_test', 'stompserver_test', 'misc_test', 'multicast_test', 'ssl_test', 'utils_test', 'transport_test', 'local_test'] if sys.hexversion >= 0x03000000: __all__.append('p3_nonascii_test') __all__.append('p3_backward_test') else: __all__.append('p2_nonascii_test') __all__.append('p2_backward_test') stomp.py-4.1.19/stomp/test/test.gif0000644000175000017500000000007413171175615016536 0ustar sophiesophieGIF87a !, `|"L ʨ8KL_;stomp.py-4.1.19/stomp/exception.py0000644000175000017500000000153213171175615016461 0ustar sophiesophie"""Errors thrown by stomp.py connections. """ class StompException(Exception): """ Common exception class. All specific stomp.py exceptions are subclasses of StompException, allowing the library user to catch all current and future library exceptions. """ class ConnectionClosedException(StompException): """ Raised in the receiver thread when the connection has been closed by the server. """ class NotConnectedException(StompException): """ Raised when there is currently no server connection. """ class ConnectFailedException(StompException): """ Raised by Connection.attempt_connection when reconnection attempts have exceeded Connection.__reconnect_attempts_max. """ class InterruptedException(StompException): """ Raised by receive when data read is interrupted. """ stomp.py-4.1.19/stomp/backward.py0000644000175000017500000000133613171175615016243 0ustar sophiesophieimport sys """Functions to support backwards compatibility. Basically where we have functions which differ between python 2 and 3, we provide implementations here and then Python-specific versions in backward2 and backward3. """ if sys.hexversion >= 0x03000000: # Python 3+ from stomp.backward3 import * else: # Python 2 from stomp.backward2 import * def get_errno(e): """ Return the errno of an exception, or the first argument if errno is not available. :param Exception e: the exception object """ try: return e.errno except AttributeError: return e.args[0] try: from time import monotonic except ImportError: # Python < 3.3/3.5 from time import time as monotonic stomp.py-4.1.19/stomp/listener.py0000644000175000017500000004054713171175615016321 0ustar sophiesophie"""Various listeners for using with stomp.py connections. """ import logging import sys import threading import time from stomp.backward import monotonic from stomp.constants import * import stomp.exception as exception import stomp.utils as utils log = logging.getLogger('stomp.py') class Publisher(object): """ Simply a registry of listeners. """ def set_listener(self, name, listener): """ Set a named listener to use with this connection. See :py:class:`stomp.listener.ConnectionListener` :param str name: the name of the listener :param ConnectionListener listener: the listener object """ pass def remove_listener(self, name): """ Remove a listener. :param str name: the name of the listener to remove """ pass def get_listener(self, name): """ Return the named listener. :param str name: the listener to return :rtype: ConnectionListener """ return None class ConnectionListener(object): """ This class should be used as a base class for objects registered using Connection.set_listener(). """ def on_connecting(self, host_and_port): """ Called by the STOMP connection once a TCP/IP connection to the STOMP server has been established or re-established. Note that at this point, no connection has been established on the STOMP protocol level. For this, you need to invoke the "connect" method on the connection. :param (str,int) host_and_port: a tuple containing the host name and port number to which the connection has been established. """ pass def on_connected(self, headers, body): """ Called by the STOMP connection when a CONNECTED frame is received (after a connection has been established or re-established). :param dict headers: a dictionary containing all headers sent by the server as key/value pairs. :param body: the frame's payload. This is usually empty for CONNECTED frames. """ pass def on_disconnected(self): """ Called by the STOMP connection when a TCP/IP connection to the STOMP server has been lost. No messages should be sent via the connection until it has been reestablished. """ pass def on_heartbeat_timeout(self): """ Called by the STOMP connection when a heartbeat message has not been received beyond the specified period. """ pass def on_before_message(self, headers, body): """ Called by the STOMP connection before a message is returned to the client app. Returns a tuple containing the headers and body (so that implementing listeners can pre-process the content). :param dict headers: the message headers :param body: the message body """ return headers, body def on_message(self, headers, body): """ Called by the STOMP connection when a MESSAGE frame is received. :param dict headers: a dictionary containing all headers sent by the server as key/value pairs. :param body: the frame's payload - the message body. """ pass def on_receipt(self, headers, body): """ Called by the STOMP connection when a RECEIPT frame is received, sent by the server if requested by the client using the 'receipt' header. :param dict headers: a dictionary containing all headers sent by the server as key/value pairs. :param body: the frame's payload. This is usually empty for RECEIPT frames. """ pass def on_error(self, headers, body): """ Called by the STOMP connection when an ERROR frame is received. :param dict headers: a dictionary containing all headers sent by the server as key/value pairs. :param body: the frame's payload - usually a detailed error description. """ pass def on_send(self, frame): """ Called by the STOMP connection when it is in the process of sending a message :param Frame frame: the frame to be sent """ pass def on_heartbeat(self): """ Called on receipt of a heartbeat. """ pass class HeartbeatListener(ConnectionListener): """ Listener used to handle STOMP heartbeating. """ def __init__(self, heartbeats): self.running = False self.heartbeats = heartbeats self.received_heartbeat = None self.heartbeat_thread = None self.next_outbound_heartbeat = None def on_connected(self, headers, body): """ Once the connection is established, and 'heart-beat' is found in the headers, we calculate the real heartbeat numbers (based on what the server sent and what was specified by the client) - if the heartbeats are not 0, we start up the heartbeat loop accordingly. :param dict headers: headers in the connection message :param body: the message body """ if 'heart-beat' in headers: self.heartbeats = utils.calculate_heartbeats( headers['heart-beat'].replace(' ', '').split(','), self.heartbeats) if self.heartbeats != (0, 0): self.send_sleep = self.heartbeats[0] / 1000 # receive gets an additional grace of 50% self.receive_sleep = (self.heartbeats[1] / 1000) * 1.5 # Give grace of receiving the first heartbeat self.received_heartbeat = monotonic() + self.receive_sleep self.running = True if self.heartbeat_thread is None: self.heartbeat_thread = utils.default_create_thread( self.__heartbeat_loop) self.heartbeat_thread.name = "StompHeartbeat%s" % \ getattr(self.heartbeat_thread, "name", "Thread") def on_disconnected(self): self.running = False def on_message(self, headers, body): """ Reset the last received time whenever a message is received. :param dict headers: headers in the message :param body: the message content """ # reset the heartbeat for any received message self.__update_heartbeat() def on_receipt(self, *_): """ Reset the last received time whenever a receipt is received. """ self.__update_heartbeat() def on_error(self, *_): """ Reset the last received time whenever an error is received. """ self.__update_heartbeat() def on_heartbeat(self): """ Reset the last received time whenever a heartbeat message is received. """ self.__update_heartbeat() def on_send(self, frame): """ Add the heartbeat header to the frame when connecting, and bump next outbound heartbeat timestamp. :param Frame frame: the Frame object """ if frame.cmd == CMD_CONNECT or frame.cmd == CMD_STOMP: if self.heartbeats != (0, 0): frame.headers[HDR_HEARTBEAT] = '%s,%s' % self.heartbeats if self.next_outbound_heartbeat is not None: self.next_outbound_heartbeat = monotonic() + self.send_sleep def __update_heartbeat(self): # Honour any grace that has been already included if self.received_heartbeat is None: return now = monotonic() if now > self.received_heartbeat: self.received_heartbeat = now def __heartbeat_loop(self): """ Main loop for sending (and monitoring received) heartbeats. """ now = monotonic() # Setup the initial due time for the outbound heartbeat if self.send_sleep != 0: self.next_outbound_heartbeat = now + self.send_sleep while self.running: now = monotonic() next_events = [] if self.next_outbound_heartbeat is not None: next_events.append(self.next_outbound_heartbeat - now) if self.receive_sleep != 0: t = self.received_heartbeat + self.receive_sleep - now if t > 0: next_events.append(t) sleep_time = min(next_events) if sleep_time > 0: time.sleep(sleep_time) now = monotonic() if not self.transport.is_connected(): time.sleep(self.send_sleep) continue if self.send_sleep != 0 and now > self.next_outbound_heartbeat: log.debug("Sending a heartbeat message at %s", now) try: self.transport.transmit(utils.Frame(None, {}, None)) except exception.NotConnectedException: log.debug("Lost connection, unable to send heartbeat") except Exception: _, e, _ = sys.exc_info() log.debug("Unable to send heartbeat, due to: %s", e) if self.receive_sleep != 0: diff_receive = now - self.received_heartbeat if diff_receive > self.receive_sleep: # heartbeat timeout log.warning("Heartbeat timeout: diff_receive=%s, time=%s, lastrec=%s", diff_receive, now, self.received_heartbeat) self.transport.set_connected(False) self.transport.disconnect_socket() self.transport.stop() for listener in self.transport.listeners.values(): listener.on_heartbeat_timeout() self.heartbeat_thread = None class WaitingListener(ConnectionListener): """ A listener which waits for a specific receipt to arrive. """ def __init__(self, receipt): """ :param str receipt: """ self.condition = threading.Condition() self.receipt = receipt self.received = False def on_receipt(self, headers, body): """ If the receipt id can be found in the headers, then notify the waiting thread. :param dict headers: headers in the message :param body: the message content """ if 'receipt-id' in headers and headers['receipt-id'] == self.receipt: with self.condition: self.received = True self.condition.notify() def wait_on_receipt(self): """ Wait until we receive a message receipt. """ with self.condition: while not self.received: self.condition.wait() self.received = False class StatsListener(ConnectionListener): """ A connection listener for recording statistics on messages sent and received. """ def __init__(self): # The number of errors received self.errors = 0 # The number of connections established self.connections = 0 # The number of disconnections self.disconnects = 0 # The number of messages received self.messages = 0 # The number of messages sent self.messages_sent = 0 # The number of heartbeat timeouts self.heartbeat_timeouts = 0 # The number of heartbeats self.heartbeat_count = 0 def on_disconnected(self): """ Increment the disconnect count. See :py:meth:`ConnectionListener.on_disconnected` """ self.disconnects += 1 log.info("disconnected (x %s)", self.disconnects) def on_error(self, headers, body): """ Increment the error count. See :py:meth:`ConnectionListener.on_error` :param dict headers: headers in the message :param body: the message content """ log.info("received an error %s [%s]", body, headers) self.errors += 1 def on_connecting(self, host_and_port): """ Increment the connection count. See :py:meth:`ConnectionListener.on_connecting` :param (str,int) host_and_port: the host and port as a tuple """ log.info("connecting %s %s (x %s)", host_and_port[0], host_and_port[1], self.connections) self.connections += 1 def on_message(self, headers, body): """ Increment the message received count. See :py:meth:`ConnectionListener.on_message` :param dict headers: headers in the message :param body: the message content """ self.messages += 1 def on_send(self, frame): """ Increment the send count. See :py:meth:`ConnectionListener.on_send` :param Frame frame: """ self.messages_sent += 1 def on_heartbeat_timeout(self): """ Increment the heartbeat timeout. See :py:meth:`ConnectionListener.on_heartbeat_timeout` """ log.debug("received heartbeat timeout") self.heartbeat_timeouts += 1 def on_heartbeat(self): """ Increment the heartbeat count. See :py:meth:`ConnectionListener.on_heartbeat` """ self.heartbeat_count += 1 def __str__(self): """ Return a string containing the current statistics (messages sent and received, errors, etc) """ return '''Connections: %s Messages sent: %s Messages received: %s Heartbeats received: %s Errors: %s''' % (self.connections, self.messages_sent, self.messages, self.heartbeat_count, self.errors) class PrintingListener(ConnectionListener): def on_connecting(self, host_and_port): """ :param (str,int) host_and_port: """ print('on_connecting %s %s' % host_and_port) def on_connected(self, headers, body): """ :param dict headers: :param body: """ print('on_connected %s %s' % (headers, body)) def on_disconnected(self): print('on_disconnected') def on_heartbeat_timeout(self): print('on_heartbeat_timeout') def on_before_message(self, headers, body): """ :param dict headers: :param body: """ print('on_before_message %s %s' % (headers, body)) return headers, body def on_message(self, headers, body): """ :param dict headers: :param body: """ print('on_message %s %s' % (headers, body)) def on_receipt(self, headers, body): """ :param dict headers: :param body: """ print('on_receipt %s %s' % (headers, body)) def on_error(self, headers, body): """ :param dict headers: :param body: """ print('on_error %s %s' % (headers, body)) def on_send(self, frame): """ :param Frame frame: """ print('on_send %s %s %s' % (frame.cmd, frame.headers, frame.body)) def on_heartbeat(self): print('on_heartbeat') class TestListener(StatsListener, WaitingListener): """ Implementation of StatsListener and WaitingListener. Useful for testing. """ def __init__(self, receipt=None): """ :param str receipt: """ StatsListener.__init__(self) WaitingListener.__init__(self, receipt) self.message_list = [] self.message_condition = threading.Condition() self.message_received = False self.heartbeat_condition = threading.Condition() self.heartbeat_received = False def on_message(self, headers, message): """ :param dict headers: :param message: """ StatsListener.on_message(self, headers, message) self.message_list.append((headers, message)) with self.message_condition: self.message_received = True self.message_condition.notify() def wait_for_message(self): with self.message_condition: while not self.message_received: self.message_condition.wait() self.message_received = False def get_latest_message(self): return self.message_list[-1] def on_heartbeat(self): StatsListener.on_heartbeat(self) with self.heartbeat_condition: self.heartbeat_received = True self.heartbeat_condition.notify() def wait_for_heartbeat(self): with self.heartbeat_condition: while not self.heartbeat_received: self.heartbeat_condition.wait() self.heartbeat_received = False stomp.py-4.1.19/stomp/adapter/0000755000175000017500000000000013171175615015530 5ustar sophiesophiestomp.py-4.1.19/stomp/adapter/multicast.py0000644000175000017500000001423613171175615020115 0ustar sophiesophie"""Multicast transport for stomp.py. Obviously not a typical message broker, but convenient if you don't have a broker, but still want to use stomp.py methods. """ import socket import struct from stomp.connect import BaseConnection from stomp.protocol import * from stomp.transport import * from stomp.utils import * MCAST_GRP = '224.1.1.1' MCAST_PORT = 5000 class MulticastTransport(Transport): """ Transport over multicast connections rather than using a broker. """ def __init__(self): Transport.__init__(self, [], False, False, 0.0, 0.0, 0.0, 0.0, 0, False, None, None, None, None, False, DEFAULT_SSL_VERSION, None, None, None) self.subscriptions = {} self.current_host_and_port = (MCAST_GRP, MCAST_PORT) def attempt_connection(self): """ Establish a multicast connection - uses 2 sockets (one for sending, the other for receiving) """ self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) self.receiver_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.receiver_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.receiver_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) self.receiver_socket.bind(('', MCAST_PORT)) mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY) self.receiver_socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) if not self.socket or not self.receiver_socket: raise exception.ConnectFailedException() def send(self, encoded_frame): """ Send an encoded frame through the mcast socket. :param bytes encoded_frame: """ self.socket.sendto(encoded_frame, (MCAST_GRP, MCAST_PORT)) def receive(self): """ Receive 1024 bytes from the multicast receiver socket. :rtype: bytes """ return self.receiver_socket.recv(1024) def process_frame(self, f, frame_str): """ :param Frame f: Frame object :param bytes frame_str: Raw frame content """ frame_type = f.cmd.lower() if frame_type in ['disconnect']: return if frame_type == 'send': frame_type = 'message' f.cmd = 'MESSAGE' if frame_type in ['connected', 'message', 'receipt', 'error', 'heartbeat']: if frame_type == 'message': if f.headers['destination'] not in self.subscriptions.values(): return (f.headers, f.body) = self.notify('before_message', f.headers, f.body) self.notify(frame_type, f.headers, f.body) if 'receipt' in f.headers: receipt_frame = Frame('RECEIPT', {'receipt-id': f.headers['receipt']}) lines = convert_frame_to_lines(receipt_frame) self.send(encode(pack(lines))) log.debug("Received frame: %r, headers=%r, body=%r", f.cmd, f.headers, f.body) def stop(self): self.running = False if hasattr(self.receiver_socket, 'SHUT_RDWR'): self.receiver_socket.shutdown(socket.SHUT_RDWR) self.receiver_socket.close() self.disconnect_socket() Transport.stop(self) class MulticastConnection(BaseConnection, Protocol12): def __init__(self, wait_on_receipt=False): """ :param bool wait_on_receipt: deprecated, ignored """ self.transport = MulticastTransport() self.transport.set_listener('mcast-listener', self) self.transactions = {} Protocol12.__init__(self, self.transport, (0, 0)) def connect(self, username=None, passcode=None, wait=False, headers=None, **keyword_headers): """ :param str username: :param str passcode: :param bool wait: :param dict headers: :param keyword_headers: """ pass def subscribe(self, destination, id, ack='auto', headers=None, **keyword_headers): """ :param str destination: :param str id: :param str ack: :param dict headers: :param keyword_headers: """ self.transport.subscriptions[id] = destination def unsubscribe(self, id, headers=None, **keyword_headers): """ :param str id: :param dict headers: :param keyword_headers: """ del self.transport.subscriptions[id] def disconnect(self, receipt=None, headers=None, **keyword_headers): """ :param str receipt: :param dict headers: :param keyword_headers: """ Protocol12.disconnect(self, receipt, headers, **keyword_headers) self.transport.stop() def send_frame(self, cmd, headers=None, body=''): """ :param str cmd: :param dict headers: :param body: """ if headers is None: headers = {} frame = utils.Frame(cmd, headers, body) if cmd == CMD_BEGIN: trans = headers[HDR_TRANSACTION] if trans in self.transactions: self.notify('error', {}, 'Transaction %s already started' % trans) else: self.transactions[trans] = [] elif cmd == CMD_COMMIT: trans = headers[HDR_TRANSACTION] if trans not in self.transactions: self.notify('error', {}, 'Transaction %s not started' % trans) else: for f in self.transactions[trans]: self.transport.transmit(f) del self.transactions[trans] elif cmd == CMD_ABORT: trans = headers['transaction'] del self.transactions[trans] else: if 'transaction' in headers: trans = headers['transaction'] if trans not in self.transactions: self.transport.notify('error', {}, 'Transaction %s not started' % trans) return else: self.transactions[trans].append(frame) else: self.transport.transmit(frame) stomp.py-4.1.19/stomp/adapter/__init__.py0000644000175000017500000000003613171175615017640 0ustar sophiesophie"""Non-standard adapters. """ stomp.py-4.1.19/stomp/protocol.py0000644000175000017500000005207713171175615016336 0ustar sophiesophie"""Provides the 1.0, 1.1 and 1.2 protocol classes. """ import uuid from stomp.backward import encode from stomp.constants import * from stomp.exception import ConnectFailedException from stomp.listener import * import stomp.utils as utils log = logging.getLogger('stomp.py') class Protocol10(ConnectionListener): """ Represents version 1.0 of the protocol (see https://stomp.github.io/stomp-specification-1.0.html). Most users should not instantiate the protocol directly. See :py:mod:`stomp.connect` for connection classes. :param transport: :param bool auto_content_length: Whether to calculate and send the content-length header automatically if it has not been set """ def __init__(self, transport, auto_content_length=True): self.transport = transport self.auto_content_length = auto_content_length transport.set_listener('protocol-listener', self) self.version = '1.0' def send_frame(self, cmd, headers=None, body=''): """ Encode and send a stomp frame through the underlying transport. :param str cmd: the protocol command :param dict headers: a map of headers to include in the frame :param body: the content of the message """ frame = utils.Frame(cmd, headers, body) self.transport.transmit(frame) def abort(self, transaction, headers=None, **keyword_headers): """ Abort a transaction. :param str transaction: the identifier of the transaction :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ assert transaction is not None, "'transaction' is required" headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_TRANSACTION] = transaction self.send_frame(CMD_ABORT, headers) def ack(self, id, transaction=None, receipt=None): """ Acknowledge 'consumption' of a message by id. :param str id: identifier of the message :param str transaction: include the acknowledgement in the specified transaction """ assert id is not None, "'id' is required" headers = {HDR_MESSAGE_ID: id} if transaction: headers[HDR_TRANSACTION] = transaction if receipt: headers[HDR_RECEIPT] = receipt self.send_frame(CMD_ACK, headers) def begin(self, transaction=None, headers=None, **keyword_headers): """ Begin a transaction. :param str transaction: the identifier for the transaction (optional - if not specified a unique transaction id will be generated) :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires :return: the transaction id :rtype: str """ headers = utils.merge_headers([headers, keyword_headers]) if not transaction: transaction = str(uuid.uuid4()) headers[HDR_TRANSACTION] = transaction self.send_frame(CMD_BEGIN, headers) return transaction def commit(self, transaction=None, headers=None, **keyword_headers): """ Commit a transaction. :param str transaction: the identifier for the transaction :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ assert transaction is not None, "'transaction' is required" headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_TRANSACTION] = transaction self.send_frame(CMD_COMMIT, headers) def connect(self, username=None, passcode=None, wait=False, headers=None, **keyword_headers): """ Start a connection. :param str username: the username to connect with :param str passcode: the password used to authenticate with :param bool wait: if True, wait for the connection to be established/acknowledged :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ cmd = CMD_CONNECT headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_ACCEPT_VERSION] = self.version if username is not None: headers[HDR_LOGIN] = username if passcode is not None: headers[HDR_PASSCODE] = passcode self.send_frame(cmd, headers) if wait: self.transport.wait_for_connection() if self.transport.connection_error: raise ConnectFailedException() def disconnect(self, receipt=None, headers=None, **keyword_headers): """ Disconnect from the server. :param str receipt: the receipt to use (once the server acknowledges that receipt, we're officially disconnected; optional - if not specified a unique receipt id will be generated) :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ if not self.transport.is_connected(): log.debug('Not sending disconnect, already disconnected') return headers = utils.merge_headers([headers, keyword_headers]) rec = receipt or str(uuid.uuid4()) headers[HDR_RECEIPT] = rec self.set_receipt(rec, CMD_DISCONNECT) self.send_frame(CMD_DISCONNECT, headers) def send(self, destination, body, content_type=None, headers=None, **keyword_headers): """ Send a message to a destination. :param str destination: the destination of the message (e.g. queue or topic name) :param body: the content of the message :param str content_type: the content type of the message :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ assert destination is not None, "'destination' is required" assert body is not None, "'body' is required" headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_DESTINATION] = destination if content_type: headers[HDR_CONTENT_TYPE] = content_type body = encode(body) if self.auto_content_length and body and HDR_CONTENT_LENGTH not in headers: headers[HDR_CONTENT_LENGTH] = len(body) self.send_frame(CMD_SEND, headers, body) def subscribe(self, destination, id=None, ack='auto', headers=None, **keyword_headers): """ Subscribe to a destination. :param str destination: the topic or queue to subscribe to :param str id: a unique id to represent the subscription :param str ack: acknowledgement mode, either auto, client, or client-individual (see http://stomp.github.io/stomp-specification-1.2.html#SUBSCRIBE_ack_Header) for more information :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ assert destination is not None, "'destination' is required" headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_DESTINATION] = destination if id: headers[HDR_ID] = id headers[HDR_ACK] = ack self.send_frame(CMD_SUBSCRIBE, headers) def unsubscribe(self, destination=None, id=None, headers=None, **keyword_headers): """ Unsubscribe from a destination by either id or the destination name. :param str destination: the name of the topic or queue to unsubscribe from :param str id: the unique identifier of the topic or queue to unsubscribe from :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ assert id is not None or destination is not None, "'id' or 'destination' is required" headers = utils.merge_headers([headers, keyword_headers]) if id: headers[HDR_ID] = id if destination: headers[HDR_DESTINATION] = destination self.send_frame(CMD_UNSUBSCRIBE, headers) class Protocol11(HeartbeatListener, ConnectionListener): """ Represents version 1.1 of the protocol (see https://stomp.github.io/stomp-specification-1.1.html). Most users should not instantiate the protocol directly. See :py:mod:`stomp.connect` for connection classes. :param transport: :param (int,int) heartbeats: :param bool auto_content_length: Whether to calculate and send the content-length header automatically if it has not been set """ def __init__(self, transport, heartbeats=(0, 0), auto_content_length=True): HeartbeatListener.__init__(self, heartbeats) self.transport = transport self.auto_content_length = auto_content_length transport.set_listener('protocol-listener', self) self.version = '1.1' def _escape_headers(self, headers): """ :param dict(str,str) headers: """ for key, val in headers.items(): try: val = val.replace('\\', '\\\\').replace('\n', '\\n').replace(':', '\\c') except: pass headers[key] = val def send_frame(self, cmd, headers=None, body=''): """ Encode and send a stomp frame through the underlying transport: :param str cmd: the protocol command :param dict headers: a map of headers to include in the frame :param body: the content of the message """ if cmd != CMD_CONNECT: if headers is None: headers = {} self._escape_headers(headers) frame = utils.Frame(cmd, headers, body) self.transport.transmit(frame) def abort(self, transaction, headers=None, **keyword_headers): """ Abort a transaction. :param str transaction: the identifier of the transaction :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ assert transaction is not None, "'transaction' is required" headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_TRANSACTION] = transaction self.send_frame(CMD_ABORT, headers) def ack(self, id, subscription, transaction=None, receipt=None): """ Acknowledge 'consumption' of a message by id. :param str id: identifier of the message :param str subscription: the subscription this message is associated with :param str transaction: include the acknowledgement in the specified transaction """ assert id is not None, "'id' is required" assert subscription is not None, "'subscription' is required" headers = {HDR_MESSAGE_ID: id, HDR_SUBSCRIPTION: subscription} if transaction: headers[HDR_TRANSACTION] = transaction if receipt: headers[HDR_RECEIPT] = receipt self.send_frame(CMD_ACK, headers) def begin(self, transaction=None, headers=None, **keyword_headers): """ Begin a transaction. :param str transaction: the identifier for the transaction (optional - if not specified a unique transaction id will be generated) :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires :return: the transaction id :rtype: str """ headers = utils.merge_headers([headers, keyword_headers]) if not transaction: transaction = str(uuid.uuid4()) headers[HDR_TRANSACTION] = transaction self.send_frame(CMD_BEGIN, headers) return transaction def commit(self, transaction=None, headers=None, **keyword_headers): """ Commit a transaction. :param str transaction: the identifier for the transaction :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ assert transaction is not None, "'transaction' is required" headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_TRANSACTION] = transaction self.send_frame(CMD_COMMIT, headers) def connect(self, username=None, passcode=None, wait=False, headers=None, **keyword_headers): """ Start a connection. :param str username: the username to connect with :param str passcode: the password used to authenticate with :param bool wait: if True, wait for the connection to be established/acknowledged :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ cmd = CMD_STOMP headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_ACCEPT_VERSION] = self.version if self.transport.vhost: headers[HDR_HOST] = self.transport.vhost if username is not None: headers[HDR_LOGIN] = username if passcode is not None: headers[HDR_PASSCODE] = passcode self.send_frame(cmd, headers) if wait: self.transport.wait_for_connection() if self.transport.connection_error: raise ConnectFailedException() def disconnect(self, receipt=None, headers=None, **keyword_headers): """ Disconnect from the server. :param str receipt: the receipt to use (once the server acknowledges that receipt, we're officially disconnected; optional - if not specified a unique receipt id will be generated) :param dict headers: a map of any additional headers the broker requires :param keyword_headers: any additional headers the broker requires """ if not self.transport.is_connected(): log.debug('Not sending disconnect, already disconnected') return headers = utils.merge_headers([headers, keyword_headers]) rec = receipt or str(uuid.uuid4()) headers[HDR_RECEIPT] = rec self.set_receipt(rec, CMD_DISCONNECT) self.send_frame(CMD_DISCONNECT, headers) def nack(self, id, subscription, transaction=None, receipt=None): """ Let the server know that a message was not consumed. :param str id: the unique id of the message to nack :param str subscription: the subscription this message is associated with :param str transaction: include this nack in a named transaction """ assert id is not None, "'id' is required" assert subscription is not None, "'subscription' is required" headers = {HDR_MESSAGE_ID: id, HDR_SUBSCRIPTION: subscription} if transaction: headers[HDR_TRANSACTION] = transaction if receipt: headers[HDR_RECEIPT] = receipt self.send_frame(CMD_NACK, headers) def send(self, destination, body, content_type=None, headers=None, **keyword_headers): """ Send a message to a destination in the messaging system (as per https://stomp.github.io/stomp-specification-1.2.html#SEND) :param str destination: the destination (such as a message queue - for example '/queue/test' - or a message topic) :param body: the content of the message :param str content_type: the MIME type of message :param dict headers: additional headers to send in the message frame :param keyword_headers: any additional headers the broker requires """ assert destination is not None, "'destination' is required" assert body is not None, "'body' is required" headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_DESTINATION] = destination if content_type: headers[HDR_CONTENT_TYPE] = content_type body = encode(body) if self.auto_content_length and body and HDR_CONTENT_LENGTH not in headers: headers[HDR_CONTENT_LENGTH] = len(body) self.send_frame(CMD_SEND, headers, body) def subscribe(self, destination, id, ack='auto', headers=None, **keyword_headers): """ Subscribe to a destination :param str destination: the topic or queue to subscribe to :param str id: the identifier to uniquely identify the subscription :param str ack: either auto, client or client-individual (see https://stomp.github.io/stomp-specification-1.2.html#SUBSCRIBE for more info) :param dict headers: a map of any additional headers to send with the subscription :param keyword_headers: any additional headers to send with the subscription """ assert destination is not None, "'destination' is required" assert id is not None, "'id' is required" headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_DESTINATION] = destination headers[HDR_ID] = id headers[HDR_ACK] = ack self.send_frame(CMD_SUBSCRIBE, headers) def unsubscribe(self, id, headers=None, **keyword_headers): """ Unsubscribe from a destination by its unique identifier :param str id: the unique identifier to unsubscribe from :param dict headers: additional headers to send with the unsubscribe :param keyword_headers: any additional headers to send with the subscription """ assert id is not None, "'id' is required" headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_ID] = id self.send_frame(CMD_UNSUBSCRIBE, headers) class Protocol12(Protocol11): """ Represents version 1.2 of the protocol (see https://stomp.github.io/stomp-specification-1.2.html). Most users should not instantiate the protocol directly. See :py:mod:`stomp.connect` for connection classes. :param transport: :param (int,int) heartbeats: :param bool auto_content_length: Whether to calculate and send the content-length header automatically if it has not been set """ def __init__(self, transport, heartbeats=(0, 0), auto_content_length=True): Protocol11.__init__(self, transport, heartbeats, auto_content_length) self.version = '1.2' def _escape_headers(self, headers): """ :param dict(str,str) headers: """ for key, val in headers.items(): try: val = val.replace('\\', '\\\\').replace('\n', '\\n').replace(':', '\\c').replace('\r', '\\r') except: pass headers[key] = val def ack(self, id, transaction=None, receipt=None): """ Acknowledge 'consumption' of a message by id. :param str id: identifier of the message :param str transaction: include the acknowledgement in the specified transaction """ assert id is not None, "'id' is required" headers = {HDR_ID: id} if transaction: headers[HDR_TRANSACTION] = transaction if receipt: headers[HDR_RECEIPT] = receipt self.send_frame(CMD_ACK, headers) def nack(self, id, transaction=None, receipt=None): """ Let the server know that a message was not consumed. :param str id: the unique id of the message to nack :param str transaction: include this nack in a named transaction """ assert id is not None, "'id' is required" headers = {HDR_ID: id} if transaction: headers[HDR_TRANSACTION] = transaction if receipt: headers[HDR_RECEIPT] = receipt self.send_frame(CMD_NACK, headers) def connect(self, username=None, passcode=None, wait=False, headers=None, **keyword_headers): """ Send a STOMP CONNECT frame. Differs from 1.0 and 1.1 versions in that the HOST header is enforced. :param str username: optionally specify the login user :param str passcode: optionally specify the user password :param bool wait: wait for the connection to complete before returning :param dict headers: a map of any additional headers to send with the subscription :param keyword_headers: any additional headers to send with the subscription """ cmd = CMD_STOMP headers = utils.merge_headers([headers, keyword_headers]) headers[HDR_ACCEPT_VERSION] = self.version headers[HDR_HOST] = self.transport.current_host_and_port[0] if self.transport.vhost: headers[HDR_HOST] = self.transport.vhost if username is not None: headers[HDR_LOGIN] = username if passcode is not None: headers[HDR_PASSCODE] = passcode self.send_frame(cmd, headers) if wait: self.transport.wait_for_connection() if self.transport.connection_error: raise ConnectFailedException() stomp.py-4.1.19/stomp/__init__.py0000644000175000017500000000224413171175615016223 0ustar sophiesophie"""stomp.py provides connectivity to a message broker supporting the STOMP protocol. Protocol versions 1.0, 1.1 and 1.2 are supported. See the GITHUB project page for more information. Author: Jason R Briggs License: http://www.apache.org/licenses/LICENSE-2.0 Project Page: https://github.com/jasonrbriggs/stomp.py """ import stomp.connect as connect import stomp.listener as listener __version__ = (4, 1, 19) ## # Alias for STOMP 1.0 connections. # Connection10 = connect.StompConnection10 StompConnection10 = Connection10 ## # Alias for STOMP 1.1 connections. # Connection11 = connect.StompConnection11 StompConnection11 = Connection11 ## # Alias for STOMP 1.2 connections. # Connection12 = connect.StompConnection12 StompConnection12 = Connection12 ## # Default connection alias (STOMP 1.1). # Connection = connect.StompConnection11 ## # Access to the default connection listener. # ConnectionListener = listener.ConnectionListener ## # Access to the stats listener. # StatsListener = listener.StatsListener ## # Access to the 'waiting' listener. WaitingListener = listener.WaitingListener ## # Access to the printing listener PrintingListener = listener.PrintingListener stomp.py-4.1.19/stomp/backward3.py0000644000175000017500000000232613171175615016326 0ustar sophiesophie""" Python3-specific versions of various functions used by stomp.py """ NULL = b'\x00' def input_prompt(prompt): """ Get user input :param str prompt: the prompt to display to the user :rtype: str """ return input(prompt) def decode(byte_data): """ Decode the byte data to a string if not None. :param bytes byte_data: the data to decode :rtype: str """ if byte_data is None: return None return byte_data.decode() def encode(char_data): """ Encode the parameter as a byte string. :param char_data: the data to encode :rtype: bytes """ if type(char_data) is str: return char_data.encode() elif type(char_data) is bytes: return char_data else: raise TypeError('message should be a string or bytes') def pack(pieces=()): """ Join a sequence of strings together. :param list pieces: list of strings :rtype: bytes """ encoded_pieces = (encode(piece) for piece in pieces) return b''.join(encoded_pieces) def join(chars=()): """ Join a sequence of characters into a string. :param bytes chars: list of chars :rtype: str """ return b''.join(chars).decode() stomp.py-4.1.19/debian/0000755000175000017500000000000013171175615014170 5ustar sophiesophiestomp.py-4.1.19/CHANGELOG0000644000175000017500000003121113171175615014156 0ustar sophiesophieVersion 4.1.19 - * Replace custom script by setuptools' entry_point for creating the executable * Stop waiting for protocol answers when the connection has been closed * Add check for is_connected on transport.stop * Change command-line tool to use docopt Version 4.1.18 - * Strip passcode from log messages Version 4.1.17 - Feb 2017 * Add support for password callback (https://github.com/jasonrbriggs/stomp.py/pull/140) * Add disconnect receipt handling for better notification of disconnects * Error handling for null frames * Stop raising exceptions in the receiver loop, if a connection has been disconnected Version 4.1.16 - Jan 2017 * bug fix for heartbeat timeout (https://github.com/jasonrbriggs/stomp.py/issues/129) * handle error with invalid/empty frames Version 4.1.15 - Nov 2016 * Minor change to release wheel * Note: rolled forward releases to try to fix issue 132 (https://github.com/jasonrbriggs/stomp.py/issues/132) Version 4.1.14 - Nov 2016 * Minor changes for ssl testing (update: now removed) Version 4.1.13 - Oct 2016 * Minor change to release wheel * Add proxy testing to makefile * Tidy up method parameters * Tidy up documentation * Improvement to heartbeat handling Version 4.1.12 - Oct 2016 * Merge patch from Nigel S, improving heartbeat accuracy (https://github.com/jasonrbriggs/stomp.py/pull/95) * Merge various patches from Ville S (including): * fixing receipt id generation (https://github.com/jasonrbriggs/stomp.py/pull/102) * generate disconnect receipt ids (https://github.com/jasonrbriggs/stomp.py/pull/108) * don't send unnecessary heartbeats (https://github.com/jasonrbriggs/stomp.py/pull/113) * fix misdetection of heartbeats (https://github.com/jasonrbriggs/stomp.py/pull/120) * Merge patch from Hugh P, adding SNI support (https://github.com/jasonrbriggs/stomp.py/pull/124) * Fix for heartbeat calculation error (https://github.com/jasonrbriggs/stomp.py/pull/125) Version 4.1.11 - Apr 2016 * Minor tidy up (missed from prior release) Version 4.1.10 - Apr 2016 * Bug fix for header escaping (https://github.com/jasonrbriggs/stomp.py/issues/82) * Merge patches from Ville S: * heartbeats - set received timestamp on receipt and error too (https://github.com/jasonrbriggs/stomp.py/pull/79) * test class name fixes (https://github.com/jasonrbriggs/stomp.py/pull/80) * support \r\n\r\n preamble end on content-length search (https://github.com/jasonrbriggs/stomp.py/pull/81) * on-demand logging message expansion (https://github.com/jasonrbriggs/stomp.py/pull/85) * bump connect error logging level to warning (https://github.com/jasonrbriggs/stomp.py/pull/87) * assign names to heartbeat and receiver threads (https://github.com/jasonrbriggs/stomp.py/pull/88) * remove unused HeartbeatListener.connected (https://github.com/jasonrbriggs/stomp.py/pull/89) * support for heartbeats on CLI (https://github.com/jasonrbriggs/stomp.py/pull/100) * Merge patch from Mikael V: * add header support in CLI (https://github.com/jasonrbriggs/stomp.py/pull/86) * Bug fix for on_before_message error (https://github.com/jasonrbriggs/stomp.py/issues/99) Version 4.1.9 - Jan 2016 * Merge patches from Pavel S: * support mixed string and bytes as input (https://github.com/jasonrbriggs/stomp.py/pull/66) * toggle sending of `content-length` header (https://github.com/jasonrbriggs/stomp.py/pull/67) * Minor logging change * Various documentation updates * Merge code improvement patches from Ville S: * use time.monotonic for timekeeping where available (https://github.com/jasonrbriggs/stomp.py/pull/74) * define gcd compat only where needed (https://github.com/jasonrbriggs/stomp.py/pull/75) * handle locking with "with" (https://github.com/jasonrbriggs/stomp.py/pull/76) * misc small improvements (https://github.com/jasonrbriggs/stomp.py/pull/77) * Merge patch from nigelsim to improve heartbeat handling for ActiveMQ: * heartbeat flexibility to support ActiveMQ (https://github.com/jasonrbriggs/stomp.py/pull/78) Version 4.1.8 - Nov 2015 * Fix missing import (https://github.com/jasonrbriggs/stomp.py/issues/61) * Code tidy up Version 4.1.7 - Nov 2015 * Merge patches from Ville S: * use constants more (https://github.com/jasonrbriggs/stomp.py/pull/56) * do not send headers with None values (https://github.com/jasonrbriggs/stomp.py/pull/57) * Update source to tidy up documentation * Add sphinx generated documentation * Fix keepalive bug (https://github.com/jasonrbriggs/stomp.py/issues/60) Version 4.1.6 - Aug 2015 * Generic exception catch on heartbeat send * Fix timeout (https://github.com/jasonrbriggs/stomp.py/issues/55) Version 4.1.5 - Aug 2015 * Remove incorrect \r escaping from 1.1 protocol * Merge patch from Ville S: * don't ship *.pyc (https://github.com/jasonrbriggs/stomp.py/pull/52) Version 4.1.4 - Aug 2015 * Add --ssl option to command line tool * Disable CTRL-C in command line tool (when in interactive mode) * Add shutdown message to cli Version 4.1.3 - Aug 2015 * Merge patches from Ville S: * auto-send content-length when message body is present (https://github.com/jasonrbriggs/stomp.py/pull/48) * unescape header names in addition to values (https://github.com/jasonrbriggs/stomp.py/pull/49) * remove unnecessary code (https://github.com/jasonrbriggs/stomp.py/pull/50) Version 4.1.2 - Jul 2015 * Merge patch from Ville S to fix coverage in setup (https://github.com/jasonrbriggs/stomp.py/pull/44) * Add Ville's change for None-check in backward3.decode (plus unit tests) Version 4.1.1 - Jul 2015 * Merge patches from Ville S covering invalid module references for colors in the CLI, fixing an attribute error and correctly invoking the python exe using sys.executable (https://github.com/jasonrbriggs/stomp.py/pull/41, https://github.com/jasonrbriggs/stomp.py/pull/42, https://github.com/jasonrbriggs/stomp.py/pull/43) Version 4.1.0 - Jul 2015 * Merge patch from George G (https://github.com/jasonrbriggs/stomp.py/pull/31) to fix binary message handling. Note that current text-only behaviour is still the default (auto_decode=True on the connection), but will be switched to false in a future release, before ultimately being removed. * Merge code cleanup patches from Ville S (https://github.com/jasonrbriggs/stomp.py/pull/35, https://github.com/jasonrbriggs/stomp.py/pull/36) * Merge another code cleanup patch from Ville E (https://github.com/jasonrbriggs/stomp.py/pull/37) Version 4.0.16 - Apr 2015 * Catch attribute error in SSL import (https://github.com/jasonrbriggs/stomp.py/issues/30) * Set default ssl version to TLSv1 Version 4.0.15 - Mar 2015 * Fix for type error in transport logging (https://github.com/jasonrbriggs/stomp.py/issues/29) Version 4.0.14 - Mar 2015 * refactor transport to make providing new transports easier * fix bug in listener (https://github.com/jasonrbriggs/stomp.py/issues/26) * Merge Andre's logging changes * fix for issue #23 (https://github.com/jasonrbriggs/stomp.py/issues/23), stop stomp.py inserting into the path Version 4.0.12 - Jun 2014 * Merge Chaskiel's patch for defaulting receipt headers * Fix defaulting for host_and_ports list * Fix exception handling in receiver loop * Tidy up logging Version 4.0.11 - Feb 2014 * Merge Rafael's patches for specifying ssl settings as a separate method rather than constructor args - https://github.com/jasonrbriggs/stomp.py/pull/6 - https://github.com/jasonrbriggs/stomp.py/pull/10 * Fix for header escaping (as per https://github.com/jasonrbriggs/stomp.py/issues/9) * Move ip/ports for tests into setup.ini Version 4.0.10 - Jan 2014 * Fix package info on setup (missing adapter package causes problems for command line client - see https://github.com/jasonrbriggs/stomp.py/issues/7 for more info Version 4.0.9 - Jan 2014 * Fix minor issue with backward uuid func * Fixes for error number handling * Fix for message listener return values Version 4.0.8 - Jan 2014 * Fix return on get_listener method (https://github.com/jasonrbriggs/stomp.py/issues/4) Version 4.0.7 - Jan 2014 * Fix problem with heartbeat listener (https://github.com/jasonrbriggs/stomp.py/issues/4) * Add alternate aliases for the connection classes * Add initial version multicast adapter (providing an interface to use the stomp.py API and send messages via multicast) - note: work in progress Version 4.0.6 - Dec 2013 * Fix missing headers in connect func * Throw ConnectFailedException when a connection fails in the 1.2 protocol - if wait=True is set Version 4.0.5 - Nov 2013 * Add command-line subscription listener. So you can do: > stomp -H localhost -P 61613 -L /queue/test * Add verbose option to command-line client (verbose "on" by default, if "off", headers aren't written to stdout) * Fix problem with connect wait (should not wait if the connection fails) * Throw exception when the connect fails (only if wait=True) * Add PrintingListener (useful for debugging) Version 4.0.4 - Nov 2013 * Fix ack/nack function inconsistencies in each protocol version (as per https://github.com/jasonrbriggs/stomp.py/issues/1) Version 4.0.3 - Nov 2013 * Add script for cmd line access (so you can run `stomp -H localhost -P 61613` rather than python $PATH_TO_STOMP/stomp ....) Version 4.0.2 - Oct 2013 * Fix minor error with 1.2 connections. Add basic unit tests for 1.0, and 1.2 Version 4.0.1 - Oct 2013 * Remove the 'transform' method/functionality, as this never went into the official spec. Clients who still want this should implement themselves, using a listener (see https://github.com/jasonrbriggs/stomp.py/blob/master/stomp/test/misc_test.py for an example) * Add support for STOMP 1.2 line endings * Enforce "host" header on 1.2 connect requests (note: should be enforced on 1.1 connections, but it seems rabbitmq connections fail if host is set) Version 4.0.0 - Oct 2013 * Separate protocol from transport mechanism, to improve the ability to support multiple protocol versions. Note: constructor args are changing accordingly. * Add initial support for STOMP 1.2 * Moved username and passcode params out of constructor and into the connect method. The basic connection process is therefore now: > conn = stomp.Connection([('localhost', 61613)]) > conn.start() > conn.connect('admin', 'password', wait=True) Version 3.1.6 - Sep 2013 * Integrate fix for threading primitives issue (http://code.google.com/p/stomppy/issues/detail?id=53) * Add vhost constructor arg * Change cli to __main__ (so you can run `python stomp` rather than `python stomp/cli.py`) * Integrate interrupt patch (http://code.google.com/p/stomppy/issues/detail?id=48) * Change test hosts and ports so that they're provided from the setup.py file Version 3.1.5 - Aug 2013 * Fix for gcd division error (http://code.google.com/p/stomppy/issues/detail?id=44) * Fix bytes incompatibility issue in Python 3.3 (http://code.google.com/p/stomppy/issues/detail?id=51) Version 3.1.4 - Jul 2012 * Add receipt header to disconnect frame if not already present on a 1.1 connection Version 3.1.3 - May 2012 * Fix signature on override_threading method * Fix for duplicate header handling * Minor fix for version var Version 3.1.1 - Feb 2012 * Fix for encoding problems (issue #34) [Jayson Vantuyl] * Possible fix for reconnection problems (issue #32) * Fix for broken pipe (error not passed to client - issue #33) * Various tidying up of the codebase Version 3.1.0 (beta 4) - Oct 2011 * Heartbeat functionality completed * General tidy up of unit tests Version 3.1.0 (beta 3) - Oct 2011 * Stop loading logging configuration in module itself (so stomp.py works better as an add-on library) * Fix for connection wait (so that it now actually waits) * Add initial heartbeat functionality * Add Linux TCP-Keepalive functionality, provided by Jayson Vantuyl Version 3.1.0 (beta 2) - Sep 2011 * Various bug fixes in 1.1 code * Fixed bug in ssl support * Added facility to override threading library * Updated unit test code for Apache Apollo Version 3.1.0 (beta 1) - Sep 2011 * Initial support for STOMP Protocol 1.1 * New version of CLI * Added disconnect receipt functionality Version 3.0.4 - Sep 2011 * Added wait-for-receipt functionality * Fixed bug in CLI version command * SSL protocol patch * Added connection timeout * Added facility to not send disconnect frame on disconnect (argument to disconnect function) Version 3.0.3 - Jan 2011 * Fixes for python 2.4 * Added config.dox to distribution Version 3.0.2 beta - Jun 2010 * Fix for localhost connection problem (issue #17) Version 3.0.1 beta - Apr 2010 * Fixes for Oracle AQ bridge for Python3 * Change to debian style changelog stomp.py-4.1.19/.gitignore0000644000175000017500000000057513171175615014745 0ustar sophiesophie*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 __pycache__ # VIM .*.swp .*.un~ # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject .idea MANIFEST tmp/ docs/build .DS_Store stomp.py-4.1.19/stomp.log.conf0000644000175000017500000000052213171175615015536 0ustar sophiesophie[loggers] keys=root [handlers] keys=consoleHandler [formatters] keys=simpleFormatter [logger_root] level=NOTSET handlers=consoleHandler [handler_consoleHandler] class=StreamHandler level=INFO formatter=simpleFormatter args=(sys.stdout,) [formatter_simpleFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s datefmt=stomp.py-4.1.19/MANIFEST.in0000644000175000017500000000015513171175615014505 0ustar sophiesophieinclude LICENSE include CHANGELOG include *.conf include *.dox include stomp/test/* exclude stomp/test/*.pyc