[Python-modules-commits] [django-websocket-redis] 01/03: importing django-websocket-redis_0.4.5.orig.tar.gz
Michael Fladischer
fladi at moszumanska.debian.org
Tue Jan 12 09:20:25 UTC 2016
This is an automated email from the git hooks/post-receive script.
fladi pushed a commit to branch master
in repository django-websocket-redis.
commit 634d491a98fd19c1c90fa46cdf5774a52180e2da
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date: Mon Jan 11 20:49:14 2016 +0100
importing django-websocket-redis_0.4.5.orig.tar.gz
---
LICENSE.txt | 22 ++
MANIFEST.in | 5 +
PKG-INFO | 114 ++++++
README.md | 64 ++++
django_websocket_redis.egg-info/PKG-INFO | 114 ++++++
django_websocket_redis.egg-info/SOURCES.txt | 24 ++
.../dependency_links.txt | 1 +
django_websocket_redis.egg-info/not-zip-safe | 1 +
django_websocket_redis.egg-info/requires.txt | 14 +
django_websocket_redis.egg-info/top_level.txt | 1 +
setup.cfg | 5 +
setup.py | 63 +++
ws4redis/__init__.py | 2 +
ws4redis/context_processors.py | 16 +
ws4redis/django_runserver.py | 86 +++++
ws4redis/exceptions.py | 27 ++
ws4redis/models.py | 14 +
ws4redis/publisher.py | 50 +++
ws4redis/redis_store.py | 175 +++++++++
ws4redis/settings.py | 54 +++
ws4redis/static/js/ws4redis.js | 102 +++++
ws4redis/subscriber.py | 76 ++++
ws4redis/utf8validator.py | 137 +++++++
ws4redis/uwsgi_runserver.py | 56 +++
ws4redis/websocket.py | 421 +++++++++++++++++++++
ws4redis/wsgi_server.py | 147 +++++++
26 files changed, 1791 insertions(+)
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..133bf17
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2013 Jacob Rief
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..4d8ca6b
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,5 @@
+include LICENSE.txt
+include README.md
+include setup.py
+recursive-include ws4redis *.py
+recursive-include ws4redis/static *
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..ade0da2
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,114 @@
+Metadata-Version: 1.1
+Name: django-websocket-redis
+Version: 0.4.5
+Summary: Websocket support for Django using Redis as datastore
+Home-page: https://github.com/jrief/django-websocket-redis
+Author: Jacob Rief
+Author-email: jacob.rief at gmail.com
+License: MIT
+Description: django-websocket-redis
+ ======================
+
+ Project home: https://github.com/jrief/django-websocket-redis
+
+ Detailed documentation on
+ `ReadTheDocs <http://django-websocket-redis.readthedocs.org/en/latest/>`__.
+
+ Online demo: http://websocket.aws.awesto.com/
+
+ Websockets for Django using Redis as message queue
+ --------------------------------------------------
+
+ This module implements websockets on top of Django without requiring any
+ additional framework. For messaging it uses the
+ `Redis <http://redis.io/>`__ datastore and in a production environment,
+ it is intended to work under `uWSGI <http://projects.unbit.it/uwsgi/>`__
+ and behind `NGiNX <http://nginx.com/>`__ or
+ `Apache <http://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html>`__
+ version 2.4.5 or later.
+
+ New in 0.4.5
+ ------------
+
+ - Created 1 requirements file under
+ ``examples/chatserver/requirements.txt``.
+ - Renamed chatclient.py to test\_chatclient.py - for django-nose
+ testrunner.
+ - Migrated example project to django 1.7.
+ - Edited ``docs/testing.rst`` to show new changes for using example
+ project.
+ - Added support for Python 3.3 and 3.4.
+ - Added support for Django-1.8
+ - Removes the check for middleware by name.
+
+ Features
+ --------
+
+ - Largely scalable for Django applications with many hundreds of open
+ websocket connections.
+ - Runs a seperate Django main loop in a cooperative concurrency model
+ using `gevent <http://www.gevent.org/>`__, thus only one
+ thread/process is required to control *all* open websockets
+ simultaneously.
+ - Full control over this seperate main loop during development, so
+ **Django** can be started as usual with ``./manage.py runserver``.
+ - No dependency to any other asynchronous event driven framework, such
+ as Tornado, Twisted or Socket.io/Node.js.
+ - Normal Django requests communicate with this seperate main loop
+ through **Redis** which, by the way is a good replacement for
+ memcached.
+ - Optionally persiting messages, allowing server reboots and client
+ reconnections.
+
+ If unsure, if this proposed architecture is the correct approach on how
+ to integrate Websockets with Django, then please read Roberto De Ioris
+ (BDFL of uWSGI) article about `Offloading Websockets and Server-Sent
+ Events AKA “Combine them with Django
+ safely” <http://uwsgi-docs.readthedocs.org/en/latest/articles/OffloadingWebsocketsAndSSE.html>`__.
+
+ Please also consider, that whichever alternative technology you use, you
+ always need a message queue, so that the Django application can “talk”
+ to the browser. This is because the only link between the browser and
+ the server is through the Websocket and thus, by definition a long
+ living connection. For scalability reasons you can't start a Django
+ server thread for each of these connections.
+
+ Build status
+ ------------
+
+ |Build Status| |Downloads|
+
+ Questions
+ ---------
+
+ Please use the issue tracker to ask questions.
+
+ License
+ -------
+
+ Copyright © 2015 Jacob Rief.
+
+ MIT licensed.
+
+ .. |Build Status| image:: https://travis-ci.org/jrief/django-websocket-redis.png?branch=master
+ :target: https://travis-ci.org/jrief/django-websocket-redis
+ .. |Downloads| image:: http://img.shields.io/pypi/dm/django-websocket-redis.svg?style=flat-square
+ :target: https://pypi.python.org/pypi/django-websocket-redis/
+
+Keywords: django,websocket,redis
+Platform: OS Independent
+Classifier: Environment :: Web Environment
+Classifier: Framework :: Django
+Classifier: Framework :: Django :: 1.5
+Classifier: Framework :: Django :: 1.6
+Classifier: Framework :: Django :: 1.7
+Classifier: Framework :: Django :: 1.8
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Development Status :: 4 - Beta
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2892225
--- /dev/null
+++ b/README.md
@@ -0,0 +1,64 @@
+django-websocket-redis
+======================
+
+Project home: https://github.com/jrief/django-websocket-redis
+
+Detailed documentation on [ReadTheDocs](http://django-websocket-redis.readthedocs.org/en/latest/).
+
+Online demo: http://websocket.aws.awesto.com/
+
+Websockets for Django using Redis as message queue
+--------------------------------------------------
+This module implements websockets on top of Django without requiring any additional framework. For
+messaging it uses the [Redis](http://redis.io/) datastore and in a production environment, it is
+intended to work under [uWSGI](http://projects.unbit.it/uwsgi/) and behind [NGiNX](http://nginx.com/)
+or [Apache](http://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html) version 2.4.5 or later.
+
+
+New in 0.4.5
+------------
+* Created 1 requirements file under ``examples/chatserver/requirements.txt``.
+* Renamed chatclient.py to test_chatclient.py - for django-nose testrunner.
+* Migrated example project to django 1.7.
+* Edited ``docs/testing.rst`` to show new changes for using example project.
+* Added support for Python 3.3 and 3.4.
+* Added support for Django-1.8
+* Removes the check for middleware by name.
+
+
+Features
+--------
+* Largely scalable for Django applications with many hundreds of open websocket connections.
+* Runs a seperate Django main loop in a cooperative concurrency model using [gevent](http://www.gevent.org/),
+ thus only one thread/process is required to control *all* open websockets simultaneously.
+* Full control over this seperate main loop during development, so **Django** can be started as usual with
+ ``./manage.py runserver``.
+* No dependency to any other asynchronous event driven framework, such as Tornado, Twisted or
+ Socket.io/Node.js.
+* Normal Django requests communicate with this seperate main loop through **Redis** which, by the way is a good
+ replacement for memcached.
+* Optionally persiting messages, allowing server reboots and client reconnections.
+
+If unsure, if this proposed architecture is the correct approach on how to integrate Websockets with Django, then
+please read Roberto De Ioris (BDFL of uWSGI) article about
+[Offloading Websockets and Server-Sent Events AKA “Combine them with Django safely”](http://uwsgi-docs.readthedocs.org/en/latest/articles/OffloadingWebsocketsAndSSE.html).
+
+Please also consider, that whichever alternative technology you use, you always need a message queue,
+so that the Django application can “talk” to the browser. This is because the only link between the browser and
+the server is through the Websocket and thus, by definition a long living connection. For scalability reasons you
+can't start a Django server thread for each of these connections.
+
+Build status
+------------
+[](https://travis-ci.org/jrief/django-websocket-redis)
+[](https://pypi.python.org/pypi/django-websocket-redis/)
+
+Questions
+---------
+Please use the issue tracker to ask questions.
+
+License
+-------
+Copyright © 2015 Jacob Rief.
+
+MIT licensed.
diff --git a/django_websocket_redis.egg-info/PKG-INFO b/django_websocket_redis.egg-info/PKG-INFO
new file mode 100644
index 0000000..ade0da2
--- /dev/null
+++ b/django_websocket_redis.egg-info/PKG-INFO
@@ -0,0 +1,114 @@
+Metadata-Version: 1.1
+Name: django-websocket-redis
+Version: 0.4.5
+Summary: Websocket support for Django using Redis as datastore
+Home-page: https://github.com/jrief/django-websocket-redis
+Author: Jacob Rief
+Author-email: jacob.rief at gmail.com
+License: MIT
+Description: django-websocket-redis
+ ======================
+
+ Project home: https://github.com/jrief/django-websocket-redis
+
+ Detailed documentation on
+ `ReadTheDocs <http://django-websocket-redis.readthedocs.org/en/latest/>`__.
+
+ Online demo: http://websocket.aws.awesto.com/
+
+ Websockets for Django using Redis as message queue
+ --------------------------------------------------
+
+ This module implements websockets on top of Django without requiring any
+ additional framework. For messaging it uses the
+ `Redis <http://redis.io/>`__ datastore and in a production environment,
+ it is intended to work under `uWSGI <http://projects.unbit.it/uwsgi/>`__
+ and behind `NGiNX <http://nginx.com/>`__ or
+ `Apache <http://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html>`__
+ version 2.4.5 or later.
+
+ New in 0.4.5
+ ------------
+
+ - Created 1 requirements file under
+ ``examples/chatserver/requirements.txt``.
+ - Renamed chatclient.py to test\_chatclient.py - for django-nose
+ testrunner.
+ - Migrated example project to django 1.7.
+ - Edited ``docs/testing.rst`` to show new changes for using example
+ project.
+ - Added support for Python 3.3 and 3.4.
+ - Added support for Django-1.8
+ - Removes the check for middleware by name.
+
+ Features
+ --------
+
+ - Largely scalable for Django applications with many hundreds of open
+ websocket connections.
+ - Runs a seperate Django main loop in a cooperative concurrency model
+ using `gevent <http://www.gevent.org/>`__, thus only one
+ thread/process is required to control *all* open websockets
+ simultaneously.
+ - Full control over this seperate main loop during development, so
+ **Django** can be started as usual with ``./manage.py runserver``.
+ - No dependency to any other asynchronous event driven framework, such
+ as Tornado, Twisted or Socket.io/Node.js.
+ - Normal Django requests communicate with this seperate main loop
+ through **Redis** which, by the way is a good replacement for
+ memcached.
+ - Optionally persiting messages, allowing server reboots and client
+ reconnections.
+
+ If unsure, if this proposed architecture is the correct approach on how
+ to integrate Websockets with Django, then please read Roberto De Ioris
+ (BDFL of uWSGI) article about `Offloading Websockets and Server-Sent
+ Events AKA “Combine them with Django
+ safely” <http://uwsgi-docs.readthedocs.org/en/latest/articles/OffloadingWebsocketsAndSSE.html>`__.
+
+ Please also consider, that whichever alternative technology you use, you
+ always need a message queue, so that the Django application can “talk”
+ to the browser. This is because the only link between the browser and
+ the server is through the Websocket and thus, by definition a long
+ living connection. For scalability reasons you can't start a Django
+ server thread for each of these connections.
+
+ Build status
+ ------------
+
+ |Build Status| |Downloads|
+
+ Questions
+ ---------
+
+ Please use the issue tracker to ask questions.
+
+ License
+ -------
+
+ Copyright © 2015 Jacob Rief.
+
+ MIT licensed.
+
+ .. |Build Status| image:: https://travis-ci.org/jrief/django-websocket-redis.png?branch=master
+ :target: https://travis-ci.org/jrief/django-websocket-redis
+ .. |Downloads| image:: http://img.shields.io/pypi/dm/django-websocket-redis.svg?style=flat-square
+ :target: https://pypi.python.org/pypi/django-websocket-redis/
+
+Keywords: django,websocket,redis
+Platform: OS Independent
+Classifier: Environment :: Web Environment
+Classifier: Framework :: Django
+Classifier: Framework :: Django :: 1.5
+Classifier: Framework :: Django :: 1.6
+Classifier: Framework :: Django :: 1.7
+Classifier: Framework :: Django :: 1.8
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Development Status :: 4 - Beta
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
diff --git a/django_websocket_redis.egg-info/SOURCES.txt b/django_websocket_redis.egg-info/SOURCES.txt
new file mode 100644
index 0000000..e545435
--- /dev/null
+++ b/django_websocket_redis.egg-info/SOURCES.txt
@@ -0,0 +1,24 @@
+LICENSE.txt
+MANIFEST.in
+README.md
+setup.py
+django_websocket_redis.egg-info/PKG-INFO
+django_websocket_redis.egg-info/SOURCES.txt
+django_websocket_redis.egg-info/dependency_links.txt
+django_websocket_redis.egg-info/not-zip-safe
+django_websocket_redis.egg-info/requires.txt
+django_websocket_redis.egg-info/top_level.txt
+ws4redis/__init__.py
+ws4redis/context_processors.py
+ws4redis/django_runserver.py
+ws4redis/exceptions.py
+ws4redis/models.py
+ws4redis/publisher.py
+ws4redis/redis_store.py
+ws4redis/settings.py
+ws4redis/subscriber.py
+ws4redis/utf8validator.py
+ws4redis/uwsgi_runserver.py
+ws4redis/websocket.py
+ws4redis/wsgi_server.py
+ws4redis/static/js/ws4redis.js
\ No newline at end of file
diff --git a/django_websocket_redis.egg-info/dependency_links.txt b/django_websocket_redis.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/django_websocket_redis.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/django_websocket_redis.egg-info/not-zip-safe b/django_websocket_redis.egg-info/not-zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/django_websocket_redis.egg-info/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/django_websocket_redis.egg-info/requires.txt b/django_websocket_redis.egg-info/requires.txt
new file mode 100644
index 0000000..fd488c1
--- /dev/null
+++ b/django_websocket_redis.egg-info/requires.txt
@@ -0,0 +1,14 @@
+setuptools
+redis
+gevent
+greenlet
+six
+
+[django-redis-sessions]
+django-redis-sessions>=0.4.0
+
+[uwsgi]
+uWSGI>=1.9.20
+
+[wsaccel]
+wsaccel>=0.6.2
diff --git a/django_websocket_redis.egg-info/top_level.txt b/django_websocket_redis.egg-info/top_level.txt
new file mode 100644
index 0000000..2f95951
--- /dev/null
+++ b/django_websocket_redis.egg-info/top_level.txt
@@ -0,0 +1 @@
+ws4redis
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..4418aca
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from setuptools import setup, find_packages
+from ws4redis import __version__
+try:
+ from pypandoc import convert
+except ImportError:
+ import io
+
+ def convert(filename, fmt):
+ with io.open(filename, encoding='utf-8') as fd:
+ return fd.read()
+
+DESCRIPTION = 'Websocket support for Django using Redis as datastore'
+
+CLASSIFIERS = [
+ 'Environment :: Web Environment',
+ 'Framework :: Django',
+ 'Framework :: Django :: 1.5',
+ 'Framework :: Django :: 1.6',
+ 'Framework :: Django :: 1.7',
+ 'Framework :: Django :: 1.8',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Development Status :: 4 - Beta',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+]
+
+setup(
+ name='django-websocket-redis',
+ version=__version__,
+ author='Jacob Rief',
+ author_email='jacob.rief at gmail.com',
+ description=DESCRIPTION,
+ long_description=convert('README.md', 'rst'),
+ url='https://github.com/jrief/django-websocket-redis',
+ license='MIT',
+ keywords=['django', 'websocket', 'redis'],
+ platforms=['OS Independent'],
+ classifiers=CLASSIFIERS,
+ packages=find_packages(exclude=['examples', 'docs']),
+ include_package_data=True,
+ install_requires=[
+ 'setuptools',
+ 'redis',
+ 'gevent',
+ 'greenlet',
+ 'six',
+ ],
+ extras_require={
+ 'uwsgi': ['uWSGI>=1.9.20'],
+ 'wsaccel': ['wsaccel>=0.6.2'],
+ 'django-redis-sessions': ['django-redis-sessions>=0.4.0'],
+ },
+ zip_safe=False,
+)
diff --git a/ws4redis/__init__.py b/ws4redis/__init__.py
new file mode 100644
index 0000000..9550b82
--- /dev/null
+++ b/ws4redis/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+__version__ = '0.4.5'
diff --git a/ws4redis/context_processors.py b/ws4redis/context_processors.py
new file mode 100644
index 0000000..7238b0e
--- /dev/null
+++ b/ws4redis/context_processors.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+from django.utils.safestring import mark_safe
+from ws4redis import settings
+
+
+def default(request):
+ """
+ Adds additional context variables to the default context.
+ """
+ protocol = request.is_secure() and 'wss://' or 'ws://'
+ heartbeat_msg = settings.WS4REDIS_HEARTBEAT and '"{0}"'.format(settings.WS4REDIS_HEARTBEAT) or 'null'
+ context = {
+ 'WEBSOCKET_URI': protocol + request.get_host() + settings.WEBSOCKET_URL,
+ 'WS4REDIS_HEARTBEAT': mark_safe(heartbeat_msg),
+ }
+ return context
diff --git a/ws4redis/django_runserver.py b/ws4redis/django_runserver.py
new file mode 100644
index 0000000..4cb3ff8
--- /dev/null
+++ b/ws4redis/django_runserver.py
@@ -0,0 +1,86 @@
+#-*- coding: utf-8 -*-
+import six
+import base64
+import select
+from hashlib import sha1
+from wsgiref import util
+from django.core.wsgi import get_wsgi_application
+from django.core.servers.basehttp import WSGIServer, WSGIRequestHandler
+from django.core.handlers.wsgi import logger
+from django.conf import settings
+from django.core.management.commands import runserver
+from django.utils.six.moves import socketserver
+from django.utils.encoding import force_str
+from ws4redis.websocket import WebSocket
+from ws4redis.wsgi_server import WebsocketWSGIServer, HandshakeError, UpgradeRequiredError
+
+util._hoppish = {}.__contains__
+
+
+class WebsocketRunServer(WebsocketWSGIServer):
+ WS_GUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
+ WS_VERSIONS = ('13', '8', '7')
+
+ def upgrade_websocket(self, environ, start_response):
+ """
+ Attempt to upgrade the socket environ['wsgi.input'] into a websocket enabled connection.
+ """
+ websocket_version = environ.get('HTTP_SEC_WEBSOCKET_VERSION', '')
+ if not websocket_version:
+ raise UpgradeRequiredError
+ elif websocket_version not in self.WS_VERSIONS:
+ raise HandshakeError('Unsupported WebSocket Version: {0}'.format(websocket_version))
+
+ key = environ.get('HTTP_SEC_WEBSOCKET_KEY', '').strip()
+ if not key:
+ raise HandshakeError('Sec-WebSocket-Key header is missing/empty')
+ try:
+ key_len = len(base64.b64decode(key))
+ except TypeError:
+ raise HandshakeError('Invalid key: {0}'.format(key))
+ if key_len != 16:
+ # 5.2.1 (3)
+ raise HandshakeError('Invalid key: {0}'.format(key))
+
+ sec_ws_accept = base64.b64encode(sha1(six.b(key) + self.WS_GUID).digest())
+ if six.PY3:
+ sec_ws_accept = sec_ws_accept.decode('ascii')
+ headers = [
+ ('Upgrade', 'websocket'),
+ ('Connection', 'Upgrade'),
+ ('Sec-WebSocket-Accept', sec_ws_accept),
+ ('Sec-WebSocket-Version', str(websocket_version)),
+ ]
+ logger.debug('WebSocket request accepted, switching protocols')
+ start_response(force_str('101 Switching Protocols'), headers)
+ six.get_method_self(start_response).finish_content()
+ return WebSocket(environ['wsgi.input'])
+
+ def select(self, rlist, wlist, xlist, timeout=None):
+ return select.select(rlist, wlist, xlist, timeout)
+
+
+def run(addr, port, wsgi_handler, ipv6=False, threading=False):
+ """
+ Function to monkey patch the internal Django command: manage.py runserver
+ """
+ logger.info('Websocket support is enabled')
+ server_address = (addr, port)
+ if not threading:
+ raise Exception("Django's Websocket server must run with threading enabled")
+ httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, WSGIServer), {'daemon_threads': True})
+ httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
+ httpd.set_app(wsgi_handler)
+ httpd.serve_forever()
+runserver.run = run
+
+
+_django_app = get_wsgi_application()
+_websocket_app = WebsocketRunServer()
+_websocket_url = getattr(settings, 'WEBSOCKET_URL')
+
+
+def application(environ, start_response):
+ if _websocket_url and environ.get('PATH_INFO').startswith(_websocket_url):
+ return _websocket_app(environ, start_response)
+ return _django_app(environ, start_response)
diff --git a/ws4redis/exceptions.py b/ws4redis/exceptions.py
new file mode 100644
index 0000000..1b79c33
--- /dev/null
+++ b/ws4redis/exceptions.py
@@ -0,0 +1,27 @@
+#-*- coding: utf-8 -*-
+from socket import error as socket_error
+from django.http import BadHeaderError
+
+
+class WebSocketError(socket_error):
+ """
+ Raised when an active websocket encounters a problem.
+ """
+
+
+class FrameTooLargeException(WebSocketError):
+ """
+ Raised if a received frame is too large.
+ """
+
+
+class HandshakeError(BadHeaderError):
+ """
+ Raised if an error occurs during protocol handshake.
+ """
+
+
+class UpgradeRequiredError(HandshakeError):
+ """
+ Raised if protocol must be upgraded.
+ """
diff --git a/ws4redis/models.py b/ws4redis/models.py
new file mode 100644
index 0000000..7e95688
--- /dev/null
+++ b/ws4redis/models.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+from django.contrib.auth.signals import user_logged_in
+from django.dispatch import receiver
+
+
+ at receiver(user_logged_in)
+def store_groups_in_session(sender, user, request, **kwargs):
+ """
+ When a user logs in, fetch its groups and store them in the users session.
+ This is required by ws4redis, since fetching groups accesses the database, which is a blocking
+ operation and thus not allowed from within the websocket loop.
+ """
+ if hasattr(user, 'groups'):
+ request.session['ws4redis:memberof'] = [g.name for g in user.groups.all()]
diff --git a/ws4redis/publisher.py b/ws4redis/publisher.py
new file mode 100644
index 0000000..3b2e55c
--- /dev/null
+++ b/ws4redis/publisher.py
@@ -0,0 +1,50 @@
+#-*- coding: utf-8 -*-
+from redis import ConnectionPool, StrictRedis
+from ws4redis import settings
+from ws4redis.redis_store import RedisStore
+
+redis_connection_pool = ConnectionPool(**settings.WS4REDIS_CONNECTION)
+
+
+class RedisPublisher(RedisStore):
+ def __init__(self, **kwargs):
+ """
+ Initialize the channels for publishing messages through the message queue.
+ """
+ connection = StrictRedis(connection_pool=redis_connection_pool)
+ super(RedisPublisher, self).__init__(connection)
+ for key in self._get_message_channels(**kwargs):
+ self._publishers.add(key)
+
+ def fetch_message(self, request, facility, audience='any'):
+ """
+ Fetch the first message available for the given ``facility`` and ``audience``, if it has
+ been persisted in the Redis datastore.
+ The current HTTP ``request`` is used to determine to whom the message belongs.
+ A unique string is used to identify the bucket's ``facility``.
+ Determines the ``audience`` to check for the message. Must be one of ``broadcast``,
+ ``group``, ``user``, ``session`` or ``any``. The default is ``any``, which means to check
+ for all possible audiences.
+ """
+ prefix = self.get_prefix()
+ channels = []
+ if audience in ('session', 'any',):
+ if request and request.session:
+ channels.append('{prefix}session:{0}:{facility}'.format(request.session.session_key, prefix=prefix, facility=facility))
+ if audience in ('user', 'any',):
+ if request and request.user and request.user.is_authenticated():
+ channels.append('{prefix}user:{0}:{facility}'.format(request.user.get_username(), prefix=prefix, facility=facility))
+ if audience in ('group', 'any',):
+ try:
+ if request.user.is_authenticated():
+ groups = request.session['ws4redis:memberof']
+ channels.extend('{prefix}group:{0}:{facility}'.format(g, prefix=prefix, facility=facility)
+ for g in groups)
+ except (KeyError, AttributeError):
+ pass
+ if audience in ('broadcast', 'any',):
+ channels.append('{prefix}broadcast:{facility}'.format(prefix=prefix, facility=facility))
+ for channel in channels:
+ message = self._connection.get(channel)
+ if message:
+ return message
diff --git a/ws4redis/redis_store.py b/ws4redis/redis_store.py
new file mode 100644
index 0000000..ce69dd8
--- /dev/null
+++ b/ws4redis/redis_store.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+import six
+import warnings
+from ws4redis import settings
+
+
+"""
+A type instance to handle the special case, when a request shall refer to itself, or as a user,
+or as a group.
+"""
+SELF = type('SELF_TYPE', (object,), {})()
+
+
+def _wrap_users(users, request):
+ """
+ Returns a list with the given list of users and/or the currently logged in user, if the list
+ contains the magic item SELF.
+ """
+ result = set()
+ for u in users:
+ if u is SELF and request and request.user and request.user.is_authenticated():
+ result.add(request.user.get_username())
+ else:
+ result.add(u)
+ return result
+
+
+def _wrap_groups(groups, request):
+ """
+ Returns a list of groups for the given list of groups and/or the current logged in user, if
+ the list contains the magic item SELF.
+ Note that this method bypasses Django's own group resolution, which requires a database query
+ and thus is unsuitable for coroutines.
+ Therefore the membership is takes from the session store, which is filled by a signal handler,
+ while the users logs in.
+ """
+ result = set()
+ for g in groups:
+ if g is SELF and request and request.user and request.user.is_authenticated():
+ result.update(request.session.get('ws4redis:memberof', []))
+ else:
+ result.add(g)
+ return result
+
+
+def _wrap_sessions(sessions, request):
+ """
+ Returns a list of session keys for the given lists of sessions and/or the session key of the
+ current logged in user, if the list contains the magic item SELF.
+ """
+ result = set()
+ for s in sessions:
+ if s is SELF and request:
+ result.add(request.session.session_key)
+ else:
+ result.add(s)
+ return result
+
+
+class RedisMessage(six.binary_type):
+ """
+ A class wrapping messages to be send and received through RedisStore. This class behaves like
+ a normal string class, but silently discards heartbeats and converts messages received from
+ Redis.
+ """
+ def __new__(cls, value):
+ if six.PY3:
+ if isinstance(value, str):
+ if value != settings.WS4REDIS_HEARTBEAT:
+ value = value.encode()
+ return super(RedisMessage, cls).__new__(cls, value)
+ elif isinstance(value, bytes):
+ if value != settings.WS4REDIS_HEARTBEAT.encode():
+ return super(RedisMessage, cls).__new__(cls, value)
+ elif isinstance(value, list):
+ if len(value) >= 2 and value[0] == b'message':
+ return super(RedisMessage, cls).__new__(cls, value[2])
+ else:
+ if isinstance(value, six.string_types):
+ if value != settings.WS4REDIS_HEARTBEAT:
+ return six.binary_type.__new__(cls, value)
+ elif isinstance(value, list):
+ if len(value) >= 2 and value[0] == 'message':
+ return six.binary_type.__new__(cls, value[2])
+ return None
+
+
+class RedisStore(object):
+ """
+ Abstract base class to control publishing and subscription for messages to and from the Redis
+ datastore.
+ """
+ _expire = settings.WS4REDIS_EXPIRE
+
+ def __init__(self, connection):
+ self._connection = connection
+ self._publishers = set()
+
+ def publish_message(self, message, expire=None):
+ """
+ Publish a ``message`` on the subscribed channel on the Redis datastore.
+ ``expire`` sets the time in seconds, on how long the message shall additionally of being
+ published, also be persisted in the Redis datastore. If unset, it defaults to the
+ configuration settings ``WS4REDIS_EXPIRE``.
+ """
+ if expire is None:
+ expire = self._expire
+ if not isinstance(message, RedisMessage):
+ raise ValueError('message object is not of type RedisMessage')
+ for channel in self._publishers:
+ self._connection.publish(channel, message)
+ if expire > 0:
+ self._connection.setex(channel, expire, message)
+
+ @staticmethod
+ def get_prefix():
+ return settings.WS4REDIS_PREFIX and '{0}:'.format(settings.WS4REDIS_PREFIX) or ''
+
+ def _get_message_channels(self, request=None, facility='{facility}', broadcast=False,
+ groups=(), users=(), sessions=()):
+ prefix = self.get_prefix()
+ channels = []
+ if broadcast is True:
+ # broadcast message to each subscriber listening on the named facility
+ channels.append('{prefix}broadcast:{facility}'.format(prefix=prefix, facility=facility))
+
+ # handle group messaging
+ if isinstance(groups, (list, tuple)):
+ # message is delivered to all listed groups
+ channels.extend('{prefix}group:{0}:{facility}'.format(g, prefix=prefix, facility=facility)
+ for g in _wrap_groups(groups, request))
+ elif groups is True and request and request.user and request.user.is_authenticated():
+ # message is delivered to all groups the currently logged in user belongs to
+ warnings.warn('Wrap groups=True into a list or tuple using SELF', DeprecationWarning)
+ channels.extend('{prefix}group:{0}:{facility}'.format(g, prefix=prefix, facility=facility)
+ for g in request.session.get('ws4redis:memberof', []))
+ elif isinstance(groups, basestring):
+ # message is delivered to the named group
+ warnings.warn('Wrap a single group into a list or tuple', DeprecationWarning)
+ channels.append('{prefix}group:{0}:{facility}'.format(groups, prefix=prefix, facility=facility))
+ elif not isinstance(groups, bool):
+ raise ValueError('Argument `groups` must be a list or tuple')
+
+ # handle user messaging
+ if isinstance(users, (list, tuple)):
+ # message is delivered to all listed users
+ channels.extend('{prefix}user:{0}:{facility}'.format(u, prefix=prefix, facility=facility)
+ for u in _wrap_users(users, request))
+ elif users is True and request and request.user and request.user.is_authenticated():
+ # message is delivered to browser instances of the currently logged in user
+ warnings.warn('Wrap users=True into a list or tuple using SELF', DeprecationWarning)
+ channels.append('{prefix}user:{0}:{facility}'.format(request.user.get_username(), prefix=prefix, facility=facility))
+ elif isinstance(users, basestring):
+ # message is delivered to the named user
+ warnings.warn('Wrap a single user into a list or tuple', DeprecationWarning)
+ channels.append('{prefix}user:{0}:{facility}'.format(users, prefix=prefix, facility=facility))
+ elif not isinstance(users, bool):
+ raise ValueError('Argument `users` must be a list or tuple')
+
+ # handle session messaging
+ if isinstance(sessions, (list, tuple)):
+ # message is delivered to all browsers instances listed in sessions
+ channels.extend('{prefix}session:{0}:{facility}'.format(s, prefix=prefix, facility=facility)
+ for s in _wrap_sessions(sessions, request))
+ elif sessions is True and request and request.session:
+ # message is delivered to browser instances owning the current session
+ warnings.warn('Wrap a single session key into a list or tuple using SELF', DeprecationWarning)
+ channels.append('{prefix}session:{0}:{facility}'.format(request.session.session_key, prefix=prefix, facility=facility))
+ elif isinstance(sessions, basestring):
+ # message is delivered to the named user
+ warnings.warn('Wrap a single session key into a list or tuple', DeprecationWarning)
+ channels.append('{prefix}session:{0}:{facility}'.format(sessions, prefix=prefix, facility=facility))
+ elif not isinstance(sessions, bool):
+ raise ValueError('Argument `sessions` must be a list or tuple')
+ return channels
diff --git a/ws4redis/settings.py b/ws4redis/settings.py
new file mode 100644
index 0000000..18fda6a
--- /dev/null
+++ b/ws4redis/settings.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+from django.conf import settings
+
+WEBSOCKET_URL = getattr(settings, 'WEBSOCKET_URL', '/ws/')
+
+WS4REDIS_CONNECTION = getattr(settings, 'WS4REDIS_CONNECTION', {
+ 'host': 'localhost',
+ 'port': 6379,
+ 'db': 0,
+ 'password': None,
+})
+
+"""
+A string to prefix elements in the Redis datastore, to avoid naming conflicts with other services.
+"""
+WS4REDIS_PREFIX = getattr(settings, 'WS4REDIS_PREFIX', None)
+
+"""
+The time in seconds, items shall be persisted by the Redis datastore.
+"""
+WS4REDIS_EXPIRE = getattr(settings, 'WS4REDIS_EXPIRE', 3600)
+
+"""
+Replace the subscriber class by a customized version.
+"""
+WS4REDIS_SUBSCRIBER = getattr(settings, 'WS4REDIS_SUBSCRIBER', 'ws4redis.subscriber.RedisSubscriber')
+
+"""
+This set the magic string to recognize heartbeat messages. If set, this message string is ignored
+by the server and also shall be ignored on the client.
+
+If WS4REDIS_HEARTBEAT is not None, the server sends at least every 4 seconds a heartbeat message.
+It is then up to the client to decide, what to do with these messages.
+"""
+WS4REDIS_HEARTBEAT = getattr(settings, 'WS4REDIS_HEARTBEAT', None)
+
+
+"""
+If set, this callback function is called right after the initialization of the Websocket.
+This function can be used to restrict the subscription/publishing channels for the current client.
+As its first parameter, it takes the current ``request`` object.
+The second parameter is a list of desired subscription channels.
+This callback function shall return a list of allowed channels or throw a ``PermissionDenied``
+exception.
+Remember that this function is not allowed to perform any blocking requests, such as accessing the
+database!
+"""
... 982 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-websocket-redis.git
More information about the Python-modules-commits
mailing list