django-websocket-0.3.0/ 0000775 0001750 0001750 00000000000 11420620256 014274 5 ustar gregor gregor django-websocket-0.3.0/examples/ 0000775 0001750 0001750 00000000000 11420350107 016105 5 ustar gregor gregor django-websocket-0.3.0/examples/settings.py 0000644 0001750 0001750 00000006666 11420350153 020334 0 ustar gregor gregor # 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.py 0000644 0001750 0001750 00000001634 11420350107 017446 0 ustar gregor gregor from 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__.py 0000644 0001750 0001750 00000000000 11420327735 020215 0 ustar gregor gregor django-websocket-0.3.0/examples/templates/ 0000775 0001750 0001750 00000000000 11420332604 020105 5 ustar gregor gregor django-websocket-0.3.0/examples/templates/index.html 0000664 0001750 0001750 00000001601 11420332604 022100 0 ustar gregor gregor
django-websocket
Received Messages
django-websocket-0.3.0/examples/manage.py 0000644 0001750 0001750 00000001042 11420327735 017715 0 ustar gregor gregor #!/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/ 0000775 0001750 0001750 00000000000 11420605715 021031 5 ustar gregor gregor django-websocket-0.3.0/django_websocket_tests/tests.py 0000644 0001750 0001750 00000011500 11420606757 022547 0 ustar gregor gregor # -*- 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.py 0000644 0001750 0001750 00000006410 11420312633 023234 0 ustar gregor gregor 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 = ''
# 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.py 0000644 0001750 0001750 00000001073 11420313203 023255 0 ustar gregor gregor #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.py 0000644 0001750 0001750 00000004310 11420605715 022537 0 ustar gregor gregor from 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__.py 0000664 0001750 0001750 00000000000 11420311657 023127 0 ustar gregor gregor django-websocket-0.3.0/django_websocket_tests/models.py 0000644 0001750 0001750 00000000000 11420110726 022643 0 ustar gregor gregor django-websocket-0.3.0/django_websocket_tests/manage.py 0000644 0001750 0001750 00000001211 11420314200 022606 0 ustar gregor gregor #!/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.in 0000644 0001750 0001750 00000000237 11420610520 016023 0 ustar gregor gregor include CHANGES
include LICENSE
include README
recursive-include examples *.py
recursive-include examples *.html
recursive-include django_websocket_tests *.py
django-websocket-0.3.0/README 0000664 0001750 0001750 00000014552 11420617671 015172 0 ustar gregor gregor ================
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/ 0000775 0001750 0001750 00000000000 11417140125 021274 5 ustar gregor gregor django-websocket-0.3.0/django_websocket.egg-info/top_level.txt 0000664 0001750 0001750 00000000021 11420620254 024017 0 ustar gregor gregor django_websocket
django-websocket-0.3.0/django_websocket.egg-info/zip-safe 0000664 0001750 0001750 00000000001 11417140125 022724 0 ustar gregor gregor
django-websocket-0.3.0/django_websocket.egg-info/dependency_links.txt 0000664 0001750 0001750 00000000001 11420620254 025342 0 ustar gregor gregor
django-websocket-0.3.0/django_websocket.egg-info/requires.txt 0000664 0001750 0001750 00000000012 11420620254 023665 0 ustar gregor gregor setuptools django-websocket-0.3.0/django_websocket.egg-info/SOURCES.txt 0000664 0001750 0001750 00000001560 11420620254 023162 0 ustar gregor gregor CHANGES
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.html django-websocket-0.3.0/django_websocket.egg-info/PKG-INFO 0000664 0001750 0001750 00000023421 11420620254 022373 0 ustar gregor gregor Metadata-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/ 0000775 0001750 0001750 00000000000 11420604730 017603 5 ustar gregor gregor django-websocket-0.3.0/django_websocket/decorators.py 0000644 0001750 0001750 00000002175 11420607167 022334 0 ustar gregor gregor from 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.py 0000644 0001750 0001750 00000003205 11420607151 022270 0 ustar gregor gregor from 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/ 0000775 0001750 0001750 00000000000 11420614204 021714 5 ustar gregor gregor django-websocket-0.3.0/django_websocket/management/commands/ 0000775 0001750 0001750 00000000000 11420617076 023527 5 ustar gregor gregor django-websocket-0.3.0/django_websocket/management/commands/runserver.py 0000644 0001750 0001750 00000002673 11420617076 026142 0 ustar gregor gregor '''
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__.py 0000644 0001750 0001750 00000000000 11420614210 025607 0 ustar gregor gregor django-websocket-0.3.0/django_websocket/management/__init__.py 0000644 0001750 0001750 00000000000 11420614204 024011 0 ustar gregor gregor django-websocket-0.3.0/django_websocket/websocket.py 0000644 0001750 0001750 00000022756 11420604730 022155 0 ustar gregor gregor import 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__.py 0000644 0001750 0001750 00000000052 11417124643 021715 0 ustar gregor gregor from django_websocket.decorators import *
django-websocket-0.3.0/CHANGES 0000644 0001750 0001750 00000002204 11420617542 015267 0 ustar gregor gregor 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
django-websocket-0.3.0/setup.cfg 0000664 0001750 0001750 00000000073 11420620256 016115 0 ustar gregor gregor [egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
django-websocket-0.3.0/PKG-INFO 0000664 0001750 0001750 00000023421 11420620256 015373 0 ustar gregor gregor Metadata-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.py 0000664 0001750 0001750 00000003375 11420620140 016006 0 ustar gregor gregor #!/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/LICENSE 0000664 0001750 0001750 00000002763 11417140046 015311 0 ustar gregor gregor Copyright (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.