[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
+------------
+[![Build Status](https://travis-ci.org/jrief/django-websocket-redis.png?branch=master)](https://travis-ci.org/jrief/django-websocket-redis)
+[![Downloads](http://img.shields.io/pypi/dm/django-websocket-redis.svg?style=flat-square)](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