django-websocket-0.3.0/0000775000175000017500000000000011420620256014274 5ustar gregorgregordjango-websocket-0.3.0/examples/0000775000175000017500000000000011420350107016105 5ustar gregorgregordjango-websocket-0.3.0/examples/settings.py0000644000175000017500000000666611420350153020334 0ustar gregorgregor# Django settings for examples project. import os import sys PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.dirname(PROJECT_ROOT)) DEBUG = True TEMPLATE_DEBUG = DEBUG ADMINS = ( # ('Your Name', 'your_email@domain.com'), ) MANAGERS = ADMINS DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 'NAME': 'db.sqlite', # Or path to database file if using sqlite3. 'USER': '', # Not used with sqlite3. 'PASSWORD': '', # Not used with sqlite3. 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 'PORT': '', # Set to empty string for default. Not used with sqlite3. } } # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # On Unix systems, a value of None will cause Django to use the same # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. TIME_ZONE = 'America/Chicago' # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGE_CODE = 'en-us' SITE_ID = 1 # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = True # If you set this to False, Django will not format dates, numbers and # calendars according to the current locale USE_L10N = True # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). # Examples: "http://media.lawrence.com", "http://example.com/media/" MEDIA_URL = '' # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # trailing slash. # Examples: "http://foo.com/media/", "/media/". ADMIN_MEDIA_PREFIX = '/media/' # Make this unique, and don't share it with anybody. SECRET_KEY = 'a#(pgz7yyyld!7mgs3(yve=t0^!psep_-&w=e@0&p)a##s(&r-' # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ) ROOT_URLCONF = 'examples.urls' TEMPLATE_DIRS = ( os.path.join(PROJECT_ROOT, 'templates'), # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) INSTALLED_APPS = ( 'django_websocket', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', # Uncomment the next line to enable the admin: # 'django.contrib.admin', ) django-websocket-0.3.0/examples/urls.py0000644000175000017500000000163411420350107017446 0ustar gregorgregorfrom django.conf.urls.defaults import * from django.shortcuts import render_to_response from django.template import RequestContext from django_websocket import require_websocket # Uncomment the next two lines to enable the admin: # from django.contrib import admin # admin.autodiscover() def base_view(request): return render_to_response('index.html', { }, context_instance=RequestContext(request)) @require_websocket def echo(request): for message in request.websocket: request.websocket.send(message) urlpatterns = patterns('', # Example: url(r'^$', base_view), url(r'^echo$', echo), # Uncomment the admin/doc line below and add 'django.contrib.admindocs' # to INSTALLED_APPS to enable admin documentation: # (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: # (r'^admin/', include(admin.site.urls)), ) django-websocket-0.3.0/examples/__init__.py0000644000175000017500000000000011420327735020215 0ustar gregorgregordjango-websocket-0.3.0/examples/templates/0000775000175000017500000000000011420332604020105 5ustar gregorgregordjango-websocket-0.3.0/examples/templates/index.html0000664000175000017500000000160111420332604022100 0ustar gregorgregor django-websocket

Received Messages

django-websocket-0.3.0/examples/manage.py0000644000175000017500000000104211420327735017715 0ustar gregorgregor#!/usr/bin/env python from django.core.management import execute_manager try: import settings # Assumed to be in the same directory. except ImportError: import sys sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) sys.exit(1) if __name__ == "__main__": execute_manager(settings) django-websocket-0.3.0/django_websocket_tests/0000775000175000017500000000000011420605715021031 5ustar gregorgregordjango-websocket-0.3.0/django_websocket_tests/tests.py0000644000175000017500000001150011420606757022547 0ustar gregorgregor# -*- coding: utf-8 -*- from mock import Mock from django.core.urlresolvers import reverse from django.contrib.auth.models import User from django.http import HttpResponse from django.test import TestCase from test_utils.mocks import RequestFactory from django_websocket.decorators import accept_websocket, require_websocket from django_websocket.websocket import WebSocket class WebSocketTests(TestCase): def setUp(self): self.socket = Mock() self.protocol = '1' def test_send_handshake(self): handshake = 'Hi!' ws = WebSocket(self.socket, self.protocol, handshake_reply=handshake) self.assertEquals(ws._handshake_sent, False) ws.send_handshake() self.assertEquals(self.socket.sendall.call_count, 1) self.assertEquals(self.socket.sendall.call_args, ((handshake,), {})) def test_message_sending(self): ws = WebSocket(self.socket, self.protocol) ws.send('foobar') self.assertEquals(self.socket.sendall.call_count, 1) self.assertEquals(self.socket.sendall.call_args, (('\x00foobar\xFF',), {})) message = self.socket.sendall.call_args[0][0] self.assertEquals(type(message), str) ws.send(u'Küss die Hand schöne Frau') self.assertEquals(self.socket.sendall.call_count, 2) self.assertEquals(self.socket.sendall.call_args, (('\x00K\xc3\xbcss die Hand sch\xc3\xb6ne Frau\xFF',), {})) message = self.socket.sendall.call_args[0][0] self.assertEquals(type(message), str) def test_message_receiving(self): ws = WebSocket(self.socket, self.protocol) self.assertFalse(ws.closed) results = [ '\x00spam & eggs\xFF', '\x00K\xc3\xbcss die Hand sch\xc3\xb6ne Frau\xFF', '\xFF\x00'][::-1] def return_results(*args, **kwargs): return results.pop() self.socket.recv.side_effect = return_results self.assertEquals(ws.wait(), u'spam & eggs') self.assertEquals(ws.wait(), u'Küss die Hand schöne Frau') def test_closing_socket_by_client(self): self.socket.recv.return_value = '\xFF\x00' ws = WebSocket(self.socket, self.protocol) self.assertFalse(ws.closed) self.assertEquals(ws.wait(), None) self.assertTrue(ws.closed) self.assertEquals(self.socket.shutdown.call_count, 0) self.assertEquals(self.socket.close.call_count, 0) def test_closing_socket_by_server(self): ws = WebSocket(self.socket, self.protocol) self.assertFalse(ws.closed) ws.close() self.assertEquals(self.socket.sendall.call_count, 1) self.assertEquals(self.socket.sendall.call_args, (('\xFF\x00',), {})) # don't close system socket! django still needs it. self.assertEquals(self.socket.shutdown.call_count, 0) self.assertEquals(self.socket.close.call_count, 0) self.assertTrue(ws.closed) # closing again will not send another close message ws.close() self.assertTrue(ws.closed) self.assertEquals(self.socket.sendall.call_count, 1) self.assertEquals(self.socket.shutdown.call_count, 0) self.assertEquals(self.socket.close.call_count, 0) def test_iterator_behaviour(self): results = [ '\x00spam & eggs\xFF', '\x00K\xc3\xbcss die Hand sch\xc3\xb6ne Frau\xFF', '\xFF\x00'][::-1] expected_results = [ u'spam & eggs', u'Küss die Hand schöne Frau'] def return_results(*args, **kwargs): return results.pop() self.socket.recv.side_effect = return_results ws = WebSocket(self.socket, self.protocol) for i, message in enumerate(ws): self.assertEquals(message, expected_results[i]) @accept_websocket def add_one(request): if request.is_websocket(): for message in request.websocket: request.websocket.send(int(message) + 1) else: value = int(request.GET['value']) value += 1 return HttpResponse(unicode(value)) @require_websocket def echo_once(request): request.websocket.send(request.websocket.wait()) class DecoratorTests(TestCase): def setUp(self): self.rf = RequestFactory() def test_require_websocket_decorator(self): # view requires websocket -> bad request request = self.rf.get('/echo/') response = echo_once(request) self.assertEquals(response.status_code, 400) def test_accept_websocket_decorator(self): request = self.rf.get('/add/', {'value': '23'}) response = add_one(request) self.assertEquals(response.status_code, 200) self.assertEquals(response.content, '24') # TODO: test views with actual websocket connection - not really possible yet # with django's test client/request factory. Heavy use of mock objects # necessary. django-websocket-0.3.0/django_websocket_tests/settings.py0000644000175000017500000000641011420312633023234 0ustar gregorgregorDEBUG = True TEMPLATE_DEBUG = DEBUG ADMINS = ( # ('Your Name', 'your_email@domain.com'), ) MANAGERS = ADMINS DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 'NAME': 'db.sqlite', # Or path to database file if using sqlite3. 'USER': '', # Not used with sqlite3. 'PASSWORD': '', # Not used with sqlite3. 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 'PORT': '', # Set to empty string for default. Not used with sqlite3. } } # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # On Unix systems, a value of None will cause Django to use the same # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. TIME_ZONE = 'America/Chicago' # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGE_CODE = 'en-us' SITE_ID = 1 # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = True # If you set this to False, Django will not format dates, numbers and # calendars according to the current locale USE_L10N = True # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). # Examples: "http://media.lawrence.com", "http://example.com/media/" MEDIA_URL = '' # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # trailing slash. # Examples: "http://foo.com/media/", "/media/". ADMIN_MEDIA_PREFIX = '/media/' # Make this unique, and don't share it with anybody. SECRET_KEY = '' # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ) ROOT_URLCONF = 'django_websocket_tests.urls' TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) INSTALLED_APPS = ( 'django_websocket_tests', 'django_websocket', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', # Uncomment the next line to enable the admin: # 'django.contrib.admin', ) TEST_APPS = ( 'django_websocket_tests', ) django-websocket-0.3.0/django_websocket_tests/runtests.py0000644000175000017500000000107311420313203023255 0ustar gregorgregor#This file mainly exists to allow python setup.py test to work. import os, sys os.environ['DJANGO_SETTINGS_MODULE'] = 'django_websocket_tests.settings' test_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, test_dir) from django.test.utils import get_runner from django.conf import settings def runtests(): TestRunner = get_runner(settings) test_runner = TestRunner(verbosity=1, interactive=True) failures = test_runner.run_tests(settings.TEST_APPS) sys.exit(bool(failures)) if __name__ == '__main__': runtests() django-websocket-0.3.0/django_websocket_tests/utils.py0000644000175000017500000000431011420605715022537 0ustar gregorgregorfrom django.test import Client from django.core.handlers.wsgi import WSGIRequest class RequestFactory(Client): """ Class that lets you create mock Request objects for use in testing. Usage: rf = RequestFactory() get_request = rf.get('/hello/') post_request = rf.post('/submit/', {'foo': 'bar'}) This class re-uses the django.test.client.Client interface, docs here: http://www.djangoproject.com/documentation/testing/#the-test-client Once you have a request object you can pass it to any view function, just as if that view had been hooked up using a URLconf. """ def request(self, **request): """ Similar to parent class, but returns the request object as soon as it has created it. """ environ = { 'HTTP_COOKIE': self.cookies, 'PATH_INFO': '/', 'QUERY_STRING': '', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'SERVER_NAME': 'testserver', 'SERVER_PORT': 80, 'SERVER_PROTOCOL': 'HTTP/1.1', } environ.update(self.defaults) environ.update(request) return WSGIRequest(environ) class WebsocketFactory(RequestFactory): def __init__(self, *args, **kwargs): self.protocol_version = kwargs.pop('websocket_version', 75) super(WebsocketFactory, self).__init__(*args, **kwargs) def request(self, **request): """ Returns a request simliar to one from a browser which wants to upgrade to a websocket connection. """ environ = { 'HTTP_COOKIE': self.cookies, 'PATH_INFO': '/', 'QUERY_STRING': '', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'SERVER_NAME': 'testserver', 'SERVER_PORT': 80, 'SERVER_PROTOCOL': 'HTTP/1.1', # WebSocket specific headers 'HTTP_CONNECTION': 'Upgrade', 'HTTP_UPGRADE': 'WebSocket', } if self.protocol_version == 76: raise NotImplementedError(u'This version is not yet supported.') environ.update(self.defaults) environ.update(request) return WSGIRequest(environ) django-websocket-0.3.0/django_websocket_tests/__init__.py0000664000175000017500000000000011420311657023127 0ustar gregorgregordjango-websocket-0.3.0/django_websocket_tests/models.py0000644000175000017500000000000011420110726022643 0ustar gregorgregordjango-websocket-0.3.0/django_websocket_tests/manage.py0000644000175000017500000000121111420314200022606 0ustar gregorgregor#!/usr/bin/env python import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from django.core.management import execute_manager try: import settings # Assumed to be in the same directory. except ImportError: import sys sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) sys.exit(1) if __name__ == "__main__": execute_manager(settings) django-websocket-0.3.0/MANIFEST.in0000644000175000017500000000023711420610520016023 0ustar gregorgregorinclude CHANGES include LICENSE include README recursive-include examples *.py recursive-include examples *.html recursive-include django_websocket_tests *.py django-websocket-0.3.0/README0000664000175000017500000001455211420617671015172 0ustar gregorgregor================ django-websocket ================ The **django-websocket** module provides an implementation of the WebSocket Protocol for django. It handles all the low-level details like establishing the connection through sending handshake reply, parsing messages from the browser etc... It integrates well into django since it provides easy hooks to receive WebSocket requests either for single views through decorators or for the whole site through a custom middleware. Usage ===== You can use the ``accept_websocket`` decorator if you want to handle websocket connections just for a single view - it will route standard HTTP requests to the view as well. Use ``require_websocket`` to only allow WebSocket connections but reject normal HTTP requests. You can use a middleware if you want to have WebSockets available for *all* URLs in your application. Add ``django_websocket.middleware.WebSocketMiddleware`` to your ``MIDDLEWARE_CLASSES`` setting. This will still reject websockets for normal views. You have to set the ``accept_websocket`` attribute on a view to allow websockets. To allow websockets for *every single view*, set the ``WEBSOCKET_ACCEPT_ALL`` setting to ``True``. The request objects passed to a view, decorated with ``accept_websocket`` or ``require_websocket`` will have the following attributes/methods attached. These attributes are always available if you use the middleware. ``request.is_websocket()`` -------------------------- Returns either ``True`` if the request has a valid websocket or ``False`` if its a normal HTTP request. Use this method in views that can accept both types of requests to distinguish between them. ``request.websocket`` --------------------- After a websocket is established, the request will have a ``websocket`` attribute which provides a simple API to communicate with the client. This attribute will be ``None`` if ``request.is_websocket()`` returns ``False``. It has the following public methods: ``WebSocket.wait()`` ~~~~~~~~~~~~~~~~~~~~ This will return exactly one message sent by the client. It will not return before a message is received or the conection is closed by the client. In this case the method will return ``None``. ``WebSocket.read()`` ~~~~~~~~~~~~~~~~~~~~ The ``read`` method will return either a new message if available or ``None`` if no new message was received from the client. It is a non-blocking alternative to the ``wait()`` method. ``WebSocket.count_messages()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Returns the number of queued messages. ``WebSocket.has_messages()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Returns ``True`` if new messages are available, else ``False``. ``WebSocket.send(message)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ This will send a single message to the client. ``WebSocket.__iter__()`` ~~~~~~~~~~~~~~~~~~~~~~~~ You can use the websocket as iterator. It will yield every new message sent by the client and stop iteration after the client has closed the connection. Error handling -------------- The library will return a Http 400 error (Bad Request) if the client requests a WebSocket connection, but the request is malformed or not supported by *django-websocket*. Examples ======== Receive one message from the client, send that message back to the client and close the connection (by returning from the view):: from django_websocket import require_websocket @require_websocket def echo_once(request): message = request.websocket.wait() request.websocket.send(message) Send websocket messages from the client as lowercase and provide same functionallity for normal GET requests:: from django.http import HttpResponse from django_websocket import accept_websocket def modify_message(message): return message.lower() @accept_websocket def lower_case(request): if not request.is_websocket(): message = request.GET['message'] message = modify_message(message) return HttpResponse(message) else: for message in request.websocket: message = modify_message(message) request.websocket.send(message) Disclaimer (what you should know when using django-websocket) ============================================================= Using in development -------------------- Django doesn't support a multithreaded development server yet. It is still not possible to open two concurrent requests. This makes working with WebSockets a bit tedious - since WebSockets will require an open request by their nature. This has the implication that you won't be able to have more than one WebSocket open at a time when using django's ``runserver`` command. It's also not possible to fire an AJAX request while a WebSocket is in use etc. **django-websocket** ships with a custom ``runserver`` command that works around these limitations. Add ``django_websocket`` to your ``INSTALLED_APPS`` settings to install it. Use your development server like you did before and provide the ``--multithreaded`` option to enable multithreaded behaviour:: python manage.py runserver --multithreaded Using in production ------------------- Be aware that **django-websocket** is just a toy for its author to play around with at the moment. It is not recommended to use in production without knowing what you do. There are no real tests made in the wild yet. But this doesn't mean that the project won't grow up in the future. There will be fixes to reported bugs and feature request are welcome to improve the API. Please write me an email or contact me somewhere else if you have experience with **django-websocket** in a real project or even in a production environment. Contribute ========== Every contribution in any form is welcome. Ask questions, report bugs, request new features, make rants or tell me any other critique you may have. One of the biggest contributions you can make is giving me a quick *Thank you* if you like this library or if it has saved you a bunch of time. But if you want to get your hands dirty: - Get the code from github: http://github.com/gregor-muellegger/django-websocket - Run tests with ``python setup.py test``. - Start coding :) - Send me a pull request or an email with a patch. Authors ======= - Gregor Müllegger (http://gremu.net/) Credits ------- Some low-level code for WebSocket implementation is borrowed from the `eventlet library`_. .. _`eventlet library`: http://eventlet.net/ django-websocket-0.3.0/django_websocket.egg-info/0000775000175000017500000000000011417140125021274 5ustar gregorgregordjango-websocket-0.3.0/django_websocket.egg-info/top_level.txt0000664000175000017500000000002111420620254024017 0ustar gregorgregordjango_websocket django-websocket-0.3.0/django_websocket.egg-info/zip-safe0000664000175000017500000000000111417140125022724 0ustar gregorgregor django-websocket-0.3.0/django_websocket.egg-info/dependency_links.txt0000664000175000017500000000000111420620254025342 0ustar gregorgregor django-websocket-0.3.0/django_websocket.egg-info/requires.txt0000664000175000017500000000001211420620254023665 0ustar gregorgregorsetuptoolsdjango-websocket-0.3.0/django_websocket.egg-info/SOURCES.txt0000664000175000017500000000156011420620254023162 0ustar gregorgregorCHANGES LICENSE MANIFEST.in README setup.py django_websocket/__init__.py django_websocket/decorators.py django_websocket/middleware.py django_websocket/websocket.py django_websocket.egg-info/PKG-INFO django_websocket.egg-info/SOURCES.txt django_websocket.egg-info/dependency_links.txt django_websocket.egg-info/requires.txt django_websocket.egg-info/top_level.txt django_websocket.egg-info/zip-safe django_websocket/management/__init__.py django_websocket/management/commands/__init__.py django_websocket/management/commands/runserver.py django_websocket_tests/__init__.py django_websocket_tests/manage.py django_websocket_tests/models.py django_websocket_tests/runtests.py django_websocket_tests/settings.py django_websocket_tests/tests.py django_websocket_tests/utils.py examples/__init__.py examples/manage.py examples/settings.py examples/urls.py examples/templates/index.htmldjango-websocket-0.3.0/django_websocket.egg-info/PKG-INFO0000664000175000017500000002342111420620254022373 0ustar gregorgregorMetadata-Version: 1.0 Name: django-websocket Version: 0.3.0 Summary: Websocket support for django. Home-page: http://pypi.python.org/pypi/django-websocket Author: Gregor Müllegger Author-email: gregor@muellegger.de License: BSD Description: ================ django-websocket ================ The **django-websocket** module provides an implementation of the WebSocket Protocol for django. It handles all the low-level details like establishing the connection through sending handshake reply, parsing messages from the browser etc... It integrates well into django since it provides easy hooks to receive WebSocket requests either for single views through decorators or for the whole site through a custom middleware. Usage ===== You can use the ``accept_websocket`` decorator if you want to handle websocket connections just for a single view - it will route standard HTTP requests to the view as well. Use ``require_websocket`` to only allow WebSocket connections but reject normal HTTP requests. You can use a middleware if you want to have WebSockets available for *all* URLs in your application. Add ``django_websocket.middleware.WebSocketMiddleware`` to your ``MIDDLEWARE_CLASSES`` setting. This will still reject websockets for normal views. You have to set the ``accept_websocket`` attribute on a view to allow websockets. To allow websockets for *every single view*, set the ``WEBSOCKET_ACCEPT_ALL`` setting to ``True``. The request objects passed to a view, decorated with ``accept_websocket`` or ``require_websocket`` will have the following attributes/methods attached. These attributes are always available if you use the middleware. ``request.is_websocket()`` -------------------------- Returns either ``True`` if the request has a valid websocket or ``False`` if its a normal HTTP request. Use this method in views that can accept both types of requests to distinguish between them. ``request.websocket`` --------------------- After a websocket is established, the request will have a ``websocket`` attribute which provides a simple API to communicate with the client. This attribute will be ``None`` if ``request.is_websocket()`` returns ``False``. It has the following public methods: ``WebSocket.wait()`` ~~~~~~~~~~~~~~~~~~~~ This will return exactly one message sent by the client. It will not return before a message is received or the conection is closed by the client. In this case the method will return ``None``. ``WebSocket.read()`` ~~~~~~~~~~~~~~~~~~~~ The ``read`` method will return either a new message if available or ``None`` if no new message was received from the client. It is a non-blocking alternative to the ``wait()`` method. ``WebSocket.count_messages()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Returns the number of queued messages. ``WebSocket.has_messages()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Returns ``True`` if new messages are available, else ``False``. ``WebSocket.send(message)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ This will send a single message to the client. ``WebSocket.__iter__()`` ~~~~~~~~~~~~~~~~~~~~~~~~ You can use the websocket as iterator. It will yield every new message sent by the client and stop iteration after the client has closed the connection. Error handling -------------- The library will return a Http 400 error (Bad Request) if the client requests a WebSocket connection, but the request is malformed or not supported by *django-websocket*. Examples ======== Receive one message from the client, send that message back to the client and close the connection (by returning from the view):: from django_websocket import require_websocket @require_websocket def echo_once(request): message = request.websocket.wait() request.websocket.send(message) Send websocket messages from the client as lowercase and provide same functionallity for normal GET requests:: from django.http import HttpResponse from django_websocket import accept_websocket def modify_message(message): return message.lower() @accept_websocket def lower_case(request): if not request.is_websocket(): message = request.GET['message'] message = modify_message(message) return HttpResponse(message) else: for message in request.websocket: message = modify_message(message) request.websocket.send(message) Disclaimer (what you should know when using django-websocket) ============================================================= Using in development -------------------- Django doesn't support a multithreaded development server yet. It is still not possible to open two concurrent requests. This makes working with WebSockets a bit tedious - since WebSockets will require an open request by their nature. This has the implication that you won't be able to have more than one WebSocket open at a time when using django's ``runserver`` command. It's also not possible to fire an AJAX request while a WebSocket is in use etc. **django-websocket** ships with a custom ``runserver`` command that works around these limitations. Add ``django_websocket`` to your ``INSTALLED_APPS`` settings to install it. Use your development server like you did before and provide the ``--multithreaded`` option to enable multithreaded behaviour:: python manage.py runserver --multithreaded Using in production ------------------- Be aware that **django-websocket** is just a toy for its author to play around with at the moment. It is not recommended to use in production without knowing what you do. There are no real tests made in the wild yet. But this doesn't mean that the project won't grow up in the future. There will be fixes to reported bugs and feature request are welcome to improve the API. Please write me an email or contact me somewhere else if you have experience with **django-websocket** in a real project or even in a production environment. Contribute ========== Every contribution in any form is welcome. Ask questions, report bugs, request new features, make rants or tell me any other critique you may have. One of the biggest contributions you can make is giving me a quick *Thank you* if you like this library or if it has saved you a bunch of time. But if you want to get your hands dirty: - Get the code from github: http://github.com/gregor-muellegger/django-websocket - Run tests with ``python setup.py test``. - Start coding :) - Send me a pull request or an email with a patch. Authors ======= - Gregor Müllegger (http://gremu.net/) Credits ------- Some low-level code for WebSocket implementation is borrowed from the `eventlet library`_. .. _`eventlet library`: http://eventlet.net/ Changelog ========= Release 0.3.0 ------------- - Added multithreaded development server. Release 0.2.0 ------------- - Changed name of attribute ``WebSocket.websocket_closed`` to ``WebSocket.closed``. - Changed behaviour of ``WebSocket.close()`` method. Doesn't close system socket - it's still needed by django! - You can run tests now with ``python setup.py test``. - Refactoring ``WebSocket`` class. - Adding ``WebSocket.read()`` which returns ``None`` if no new messages are available instead of blocking like ``WebSocket.wait()``. - Adding example project to play around with. - Adding ``WebSocket.has_messages()``. You can use it to check if new messages are ready to be processed. - Adding ``WebSocket.count_messages()``. - Removing ``BaseWebSocketMiddleware`` - is replaced by ``WebSocketMiddleware``. Don't need for a base middleware anymore. We can integrate everything in one now. Release 0.1.1 ------------- - Fixed a bug in ``BaseWebSocketMiddleware`` that caused an exception in ``process_response`` if ``setup_websocket`` failed. Thanks to cedric salaun for the report. Release 0.1.0 ------------- - Initial release Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Utilities django-websocket-0.3.0/django_websocket/0000775000175000017500000000000011420604730017603 5ustar gregorgregordjango-websocket-0.3.0/django_websocket/decorators.py0000644000175000017500000000217511420607167022334 0ustar gregorgregorfrom django.conf import settings from django.http import HttpResponse from django.utils.decorators import decorator_from_middleware from django_websocket.middleware import WebSocketMiddleware __all__ = ('accept_websocket', 'require_websocket') WEBSOCKET_MIDDLEWARE_INSTALLED = 'django_websocket.middleware.WebSocketMiddleware' in settings.MIDDLEWARE_CLASSES def _setup_websocket(func): from functools import wraps @wraps(func) def new_func(request, *args, **kwargs): response = func(request, *args, **kwargs) if response is None and request.is_websocket(): return HttpResponse() return response if not WEBSOCKET_MIDDLEWARE_INSTALLED: decorator = decorator_from_middleware(WebSocketMiddleware) new_func = decorator(new_func) return new_func def accept_websocket(func): func.accept_websocket = True func.require_websocket = getattr(func, 'require_websocket', False) func = _setup_websocket(func) return func def require_websocket(func): func.accept_websocket = True func.require_websocket = True func = _setup_websocket(func) return func django-websocket-0.3.0/django_websocket/middleware.py0000644000175000017500000000320511420607151022270 0ustar gregorgregorfrom django.conf import settings from django.http import HttpResponseBadRequest from django_websocket.websocket import setup_websocket, MalformedWebSocket WEBSOCKET_ACCEPT_ALL = getattr(settings, 'WEBSOCKET_ACCEPT_ALL', False) class WebSocketMiddleware(object): def process_request(self, request): try: request.websocket = setup_websocket(request) except MalformedWebSocket, e: request.websocket = None return HttpResponseBadRequest() finally: # provide ``request.is_websocket()`` interface, similiar to # ``request.is_ajax()``. if request.websocket is not None: request.is_websocket = lambda: True else: request.is_websocket = lambda: False def process_view(self, request, view_func, view_args, view_kwargs): # open websocket if its an accepted request if request.is_websocket(): # deny websocket request if view can't handle websocket if not WEBSOCKET_ACCEPT_ALL and \ not getattr(view_func, 'accept_websocket', False): return HttpResponseBadRequest() # everything is fine .. so prepare connection by sending handshake request.websocket.send_handshake() elif getattr(view_func, 'require_websocket', False): # websocket was required but not provided return HttpResponseBadRequest() def process_response(self, request, response): if request.is_websocket() and request.websocket._handshake_sent: request.websocket._send_closing_frame(True) return response django-websocket-0.3.0/django_websocket/management/0000775000175000017500000000000011420614204021714 5ustar gregorgregordjango-websocket-0.3.0/django_websocket/management/commands/0000775000175000017500000000000011420617076023527 5ustar gregorgregordjango-websocket-0.3.0/django_websocket/management/commands/runserver.py0000644000175000017500000000267311420617076026142 0ustar gregorgregor''' Monkey patching django's builtin ``runserver`` command to support multithreaded concurrent requests. This is necessary to have more than one WebSocket open at a time. The implementation monkey patches the code instead of subclassing the original WSGIServer since there is no easy way to inject the new, inherited class into the runserver command. ''' from django.core.management.commands.runserver import Command as _Runserver from django.core.servers.basehttp import WSGIServer from SocketServer import ThreadingMixIn from optparse import make_option class Command(_Runserver): option_list = _Runserver.option_list + ( make_option('--multithreaded', action='store_true', dest='multithreaded', default=False, help='Run development server with support for concurrent requests.'), ) def handle(self, *args, **options): multithreaded = options.pop('multithreaded') if multithreaded: # monkey patch WSGIServer to support concurrent requests skip_attrs = ('__doc__', '__module__') patch_attrs = dir(ThreadingMixIn) patch_attrs = [a for a in patch_attrs if a not in skip_attrs] for attr in patch_attrs: setattr(WSGIServer, attr, getattr(ThreadingMixIn, attr)) # persuade python to use mixin methods WSGIServer.__bases__ = WSGIServer.__bases__ + (ThreadingMixIn,) super(Command, self).handle(*args, **options) django-websocket-0.3.0/django_websocket/management/commands/__init__.py0000644000175000017500000000000011420614210025607 0ustar gregorgregordjango-websocket-0.3.0/django_websocket/management/__init__.py0000644000175000017500000000000011420614204024011 0ustar gregorgregordjango-websocket-0.3.0/django_websocket/websocket.py0000644000175000017500000002275611420604730022155 0ustar gregorgregorimport collections import select import string import struct try: from hashlib import md5 except ImportError: #pragma NO COVER from md5 import md5 from errno import EINTR from socket import error as SocketError class MalformedWebSocket(ValueError): pass def _extract_number(value): """ Utility function which, given a string like 'g98sd 5[]221@1', will return 9852211. Used to parse the Sec-WebSocket-Key headers. """ out = "" spaces = 0 for char in value: if char in string.digits: out += char elif char == " ": spaces += 1 return int(out) / spaces def setup_websocket(request): if request.META.get('HTTP_CONNECTION', None) == 'Upgrade' and \ request.META.get('HTTP_UPGRADE', None) == 'WebSocket': # See if they sent the new-format headers if 'HTTP_SEC_WEBSOCKET_KEY1' in request.META: protocol_version = 76 if 'HTTP_SEC_WEBSOCKET_KEY2' not in request.META: raise MalformedWebSocket() else: protocol_version = 75 # If it's new-version, we need to work out our challenge response if protocol_version == 76: key1 = _extract_number(request.META['HTTP_SEC_WEBSOCKET_KEY1']) key2 = _extract_number(request.META['HTTP_SEC_WEBSOCKET_KEY2']) # There's no content-length header in the request, but it has 8 # bytes of data. key3 = request.META['wsgi.input'].read(8) key = struct.pack(">II", key1, key2) + key3 handshake_response = md5(key).digest() location = 'ws://%s%s' % (request.get_host(), request.path) qs = request.META.get('QUERY_STRING') if qs: location += '?' + qs if protocol_version == 75: handshake_reply = ( "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" "Upgrade: WebSocket\r\n" "Connection: Upgrade\r\n" "WebSocket-Origin: %s\r\n" "WebSocket-Location: %s\r\n\r\n" % ( request.META.get('HTTP_ORIGIN'), location)) elif protocol_version == 76: handshake_reply = ( "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" "Upgrade: WebSocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Origin: %s\r\n" "Sec-WebSocket-Protocol: %s\r\n" "Sec-WebSocket-Location: %s\r\n" % ( request.META.get('HTTP_ORIGIN'), request.META.get('HTTP_SEC_WEBSOCKET_PROTOCOL', 'default'), location)) handshake_reply = str(handshake_reply) handshake_reply = '%s\r\n%s' % (handshake_reply, handshake_response) else: raise MalformedWebSocket("Unknown WebSocket protocol version.") socket = request.META['wsgi.input']._sock.dup() return WebSocket( socket, protocol=request.META.get('HTTP_WEBSOCKET_PROTOCOL'), version=protocol_version, handshake_reply=handshake_reply, ) return None class WebSocket(object): """ A websocket object that handles the details of serialization/deserialization to the socket. The primary way to interact with a :class:`WebSocket` object is to call :meth:`send` and :meth:`wait` in order to pass messages back and forth with the browser. """ _socket_recv_bytes = 4096 def __init__(self, socket, protocol, version=76, handshake_reply=None, handshake_sent=None): ''' Arguments: - ``socket``: An open socket that should be used for WebSocket communciation. - ``protocol``: not used yet. - ``version``: The WebSocket spec version to follow (default is 76) - ``handshake_reply``: Handshake message that should be sent to the client when ``send_handshake()`` is called. - ``handshake_sent``: Whether the handshake is already sent or not. Set to ``False`` to prevent ``send_handshake()`` to do anything. ''' self.socket = socket self.protocol = protocol self.version = version self.closed = False self.handshake_reply = handshake_reply if handshake_sent is None: self._handshake_sent = not bool(handshake_reply) else: self._handshake_sent = handshake_sent self._buffer = "" self._message_queue = collections.deque() def send_handshake(self): self.socket.sendall(self.handshake_reply) self._handshake_sent = True @classmethod def _pack_message(cls, message): """Pack the message inside ``00`` and ``FF`` As per the dataframing section (5.3) for the websocket spec """ if isinstance(message, unicode): message = message.encode('utf-8') elif not isinstance(message, str): message = str(message) packed = "\x00%s\xFF" % message return packed def _parse_message_queue(self): """ Parses for messages in the buffer *buf*. It is assumed that the buffer contains the start character for a message, but that it may contain only part of the rest of the message. Returns an array of messages, and the buffer remainder that didn't contain any full messages.""" msgs = [] end_idx = 0 buf = self._buffer while buf: frame_type = ord(buf[0]) if frame_type == 0: # Normal message. end_idx = buf.find("\xFF") if end_idx == -1: #pragma NO COVER break msgs.append(buf[1:end_idx].decode('utf-8', 'replace')) buf = buf[end_idx+1:] elif frame_type == 255: # Closing handshake. assert ord(buf[1]) == 0, "Unexpected closing handshake: %r" % buf self.closed = True break else: raise ValueError("Don't understand how to parse this type of message: %r" % buf) self._buffer = buf return msgs def send(self, message): ''' Send a message to the client. *message* should be convertable to a string; unicode objects should be encodable as utf-8. ''' packed = self._pack_message(message) self.socket.sendall(packed) def _socket_recv(self): ''' Gets new data from the socket and try to parse new messages. ''' delta = self.socket.recv(self._socket_recv_bytes) if delta == '': return False self._buffer += delta msgs = self._parse_message_queue() self._message_queue.extend(msgs) return True def _socket_can_recv(self, timeout=0.0): ''' Return ``True`` if new data can be read from the socket. ''' r, w, e = [self.socket], [], [] try: r, w, e = select.select(r, w, e, timeout) except select.error, err: if err.args[0] == EINTR: return False raise return self.socket in r def _get_new_messages(self): # read as long from socket as we need to get a new message. while self._socket_can_recv(): self._socket_recv() if self._message_queue: return def count_messages(self): ''' Returns the number of queued messages. ''' self._get_new_messages() return len(self._message_queue) def has_messages(self): ''' Returns ``True`` if new messages from the socket are available, else ``False``. ''' if self._message_queue: return True self._get_new_messages() if self._message_queue: return True return False def read(self, fallback=None): ''' Return new message or ``fallback`` if no message is available. ''' if self.has_messages(): return self._message_queue.popleft() return fallback def wait(self): ''' Waits for and deserializes messages. Returns a single message; the oldest not yet processed. ''' while not self._message_queue: # Websocket might be closed already. if self.closed: return None # no parsed messages, must mean buf needs more data new_data = self._socket_recv() if not new_data: return None return self._message_queue.popleft() def __iter__(self): ''' Use ``WebSocket`` as iterator. Iteration only stops when the websocket gets closed by the client. ''' while True: message = self.wait() if message is None: return yield message def _send_closing_frame(self, ignore_send_errors=False): ''' Sends the closing frame to the client, if required. ''' if self.version == 76 and not self.closed: try: self.socket.sendall("\xff\x00") except SocketError: # Sometimes, like when the remote side cuts off the connection, # we don't care about this. if not ignore_send_errors: raise self.closed = True def close(self): ''' Forcibly close the websocket. ''' self._send_closing_frame() django-websocket-0.3.0/django_websocket/__init__.py0000644000175000017500000000005211417124643021715 0ustar gregorgregorfrom django_websocket.decorators import * django-websocket-0.3.0/CHANGES0000644000175000017500000000220411420617542015267 0ustar gregorgregorChangelog ========= Release 0.3.0 ------------- - Added multithreaded development server. Release 0.2.0 ------------- - Changed name of attribute ``WebSocket.websocket_closed`` to ``WebSocket.closed``. - Changed behaviour of ``WebSocket.close()`` method. Doesn't close system socket - it's still needed by django! - You can run tests now with ``python setup.py test``. - Refactoring ``WebSocket`` class. - Adding ``WebSocket.read()`` which returns ``None`` if no new messages are available instead of blocking like ``WebSocket.wait()``. - Adding example project to play around with. - Adding ``WebSocket.has_messages()``. You can use it to check if new messages are ready to be processed. - Adding ``WebSocket.count_messages()``. - Removing ``BaseWebSocketMiddleware`` - is replaced by ``WebSocketMiddleware``. Don't need for a base middleware anymore. We can integrate everything in one now. Release 0.1.1 ------------- - Fixed a bug in ``BaseWebSocketMiddleware`` that caused an exception in ``process_response`` if ``setup_websocket`` failed. Thanks to cedric salaun for the report. Release 0.1.0 ------------- - Initial release django-websocket-0.3.0/setup.cfg0000664000175000017500000000007311420620256016115 0ustar gregorgregor[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 django-websocket-0.3.0/PKG-INFO0000664000175000017500000002342111420620256015373 0ustar gregorgregorMetadata-Version: 1.0 Name: django-websocket Version: 0.3.0 Summary: Websocket support for django. Home-page: http://pypi.python.org/pypi/django-websocket Author: Gregor Müllegger Author-email: gregor@muellegger.de License: BSD Description: ================ django-websocket ================ The **django-websocket** module provides an implementation of the WebSocket Protocol for django. It handles all the low-level details like establishing the connection through sending handshake reply, parsing messages from the browser etc... It integrates well into django since it provides easy hooks to receive WebSocket requests either for single views through decorators or for the whole site through a custom middleware. Usage ===== You can use the ``accept_websocket`` decorator if you want to handle websocket connections just for a single view - it will route standard HTTP requests to the view as well. Use ``require_websocket`` to only allow WebSocket connections but reject normal HTTP requests. You can use a middleware if you want to have WebSockets available for *all* URLs in your application. Add ``django_websocket.middleware.WebSocketMiddleware`` to your ``MIDDLEWARE_CLASSES`` setting. This will still reject websockets for normal views. You have to set the ``accept_websocket`` attribute on a view to allow websockets. To allow websockets for *every single view*, set the ``WEBSOCKET_ACCEPT_ALL`` setting to ``True``. The request objects passed to a view, decorated with ``accept_websocket`` or ``require_websocket`` will have the following attributes/methods attached. These attributes are always available if you use the middleware. ``request.is_websocket()`` -------------------------- Returns either ``True`` if the request has a valid websocket or ``False`` if its a normal HTTP request. Use this method in views that can accept both types of requests to distinguish between them. ``request.websocket`` --------------------- After a websocket is established, the request will have a ``websocket`` attribute which provides a simple API to communicate with the client. This attribute will be ``None`` if ``request.is_websocket()`` returns ``False``. It has the following public methods: ``WebSocket.wait()`` ~~~~~~~~~~~~~~~~~~~~ This will return exactly one message sent by the client. It will not return before a message is received or the conection is closed by the client. In this case the method will return ``None``. ``WebSocket.read()`` ~~~~~~~~~~~~~~~~~~~~ The ``read`` method will return either a new message if available or ``None`` if no new message was received from the client. It is a non-blocking alternative to the ``wait()`` method. ``WebSocket.count_messages()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Returns the number of queued messages. ``WebSocket.has_messages()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Returns ``True`` if new messages are available, else ``False``. ``WebSocket.send(message)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ This will send a single message to the client. ``WebSocket.__iter__()`` ~~~~~~~~~~~~~~~~~~~~~~~~ You can use the websocket as iterator. It will yield every new message sent by the client and stop iteration after the client has closed the connection. Error handling -------------- The library will return a Http 400 error (Bad Request) if the client requests a WebSocket connection, but the request is malformed or not supported by *django-websocket*. Examples ======== Receive one message from the client, send that message back to the client and close the connection (by returning from the view):: from django_websocket import require_websocket @require_websocket def echo_once(request): message = request.websocket.wait() request.websocket.send(message) Send websocket messages from the client as lowercase and provide same functionallity for normal GET requests:: from django.http import HttpResponse from django_websocket import accept_websocket def modify_message(message): return message.lower() @accept_websocket def lower_case(request): if not request.is_websocket(): message = request.GET['message'] message = modify_message(message) return HttpResponse(message) else: for message in request.websocket: message = modify_message(message) request.websocket.send(message) Disclaimer (what you should know when using django-websocket) ============================================================= Using in development -------------------- Django doesn't support a multithreaded development server yet. It is still not possible to open two concurrent requests. This makes working with WebSockets a bit tedious - since WebSockets will require an open request by their nature. This has the implication that you won't be able to have more than one WebSocket open at a time when using django's ``runserver`` command. It's also not possible to fire an AJAX request while a WebSocket is in use etc. **django-websocket** ships with a custom ``runserver`` command that works around these limitations. Add ``django_websocket`` to your ``INSTALLED_APPS`` settings to install it. Use your development server like you did before and provide the ``--multithreaded`` option to enable multithreaded behaviour:: python manage.py runserver --multithreaded Using in production ------------------- Be aware that **django-websocket** is just a toy for its author to play around with at the moment. It is not recommended to use in production without knowing what you do. There are no real tests made in the wild yet. But this doesn't mean that the project won't grow up in the future. There will be fixes to reported bugs and feature request are welcome to improve the API. Please write me an email or contact me somewhere else if you have experience with **django-websocket** in a real project or even in a production environment. Contribute ========== Every contribution in any form is welcome. Ask questions, report bugs, request new features, make rants or tell me any other critique you may have. One of the biggest contributions you can make is giving me a quick *Thank you* if you like this library or if it has saved you a bunch of time. But if you want to get your hands dirty: - Get the code from github: http://github.com/gregor-muellegger/django-websocket - Run tests with ``python setup.py test``. - Start coding :) - Send me a pull request or an email with a patch. Authors ======= - Gregor Müllegger (http://gremu.net/) Credits ------- Some low-level code for WebSocket implementation is borrowed from the `eventlet library`_. .. _`eventlet library`: http://eventlet.net/ Changelog ========= Release 0.3.0 ------------- - Added multithreaded development server. Release 0.2.0 ------------- - Changed name of attribute ``WebSocket.websocket_closed`` to ``WebSocket.closed``. - Changed behaviour of ``WebSocket.close()`` method. Doesn't close system socket - it's still needed by django! - You can run tests now with ``python setup.py test``. - Refactoring ``WebSocket`` class. - Adding ``WebSocket.read()`` which returns ``None`` if no new messages are available instead of blocking like ``WebSocket.wait()``. - Adding example project to play around with. - Adding ``WebSocket.has_messages()``. You can use it to check if new messages are ready to be processed. - Adding ``WebSocket.count_messages()``. - Removing ``BaseWebSocketMiddleware`` - is replaced by ``WebSocketMiddleware``. Don't need for a base middleware anymore. We can integrate everything in one now. Release 0.1.1 ------------- - Fixed a bug in ``BaseWebSocketMiddleware`` that caused an exception in ``process_response`` if ``setup_websocket`` failed. Thanks to cedric salaun for the report. Release 0.1.0 ------------- - Initial release Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Utilities django-websocket-0.3.0/setup.py0000664000175000017500000000337511420620140016006 0ustar gregorgregor#!/usr/bin/env python # -*- coding: utf-8 -*- from setuptools import setup class UltraMagicString(object): ''' Taken from http://stackoverflow.com/questions/1162338/whats-the-right-way-to-use-unicode-metadata-in-setup-py ''' def __init__(self, value): self.value = value def __str__(self): return self.value def __unicode__(self): return self.value.decode('UTF-8') def __add__(self, other): return UltraMagicString(self.value + str(other)) def split(self, *args, **kw): return self.value.split(*args, **kw) long_description = UltraMagicString('\n\n'.join(( file('README').read(), file('CHANGES').read(), ))) setup( name = u'django-websocket', version = u'0.3.0', url = u'http://pypi.python.org/pypi/django-websocket', license = u'BSD', description = u'Websocket support for django.', long_description = long_description, author = UltraMagicString('Gregor Müllegger'), author_email = u'gregor@muellegger.de', packages = [ 'django_websocket', 'django_websocket.management', 'django_websocket.management.commands'], classifiers = [ 'Development Status :: 3 - Alpha', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Utilities' ], zip_safe = True, install_requires = ['setuptools'], test_suite = 'django_websocket_tests.runtests.runtests', tests_require=[ 'django-test-utils', 'mock', ], ) django-websocket-0.3.0/LICENSE0000664000175000017500000000276311417140046015311 0ustar gregorgregorCopyright (c) 2010, Gregor Müllegger All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.