mirror of
https://github.com/django/daphne.git
synced 2025-04-20 00:32:09 +03:00
Few more WS tweaks
This commit is contained in:
parent
75ee13ff9c
commit
d9681df69a
85
README.rst
Normal file
85
README.rst
Normal file
|
@ -0,0 +1,85 @@
|
|||
django-channels
|
||||
===============
|
||||
|
||||
This is a work-in-progress code branch of Django implemented as a third-party
|
||||
app, which aims to bring some asynchrony to Django and expand the options
|
||||
for code beyond the request-response model.
|
||||
|
||||
The proposal itself is detailed in `a very long Gist <https://gist.github.com/andrewgodwin/b3f826a879eb84a70625>`_
|
||||
and there is discussion about it on `django-developers <https://groups.google.com/forum/#!forum/django-developers>`_.
|
||||
|
||||
If you wish to use this in your own project, there's basic integration
|
||||
instructions below - but be warned! This is not stable and may change massively
|
||||
at any time!
|
||||
|
||||
Integration
|
||||
-----------
|
||||
|
||||
Make sure you're running Django 1.8. This doesn't work with 1.7 (yet?)
|
||||
|
||||
If you want to use WebSockets (and that's kind of the point) you'll need
|
||||
``autobahn`` and ``twisted`` packages too. Python 3/asyncio support coming soon.
|
||||
|
||||
``pip install django-channels`` and then add ``channels`` to the **TOP**
|
||||
of your ``INSTALLED_APPS`` list (if it is not at the top you won't get the
|
||||
new runserver command).
|
||||
|
||||
You now have a ``runserver`` that actually runs a WSGI interface and a
|
||||
worker in two different threads, ``runworker`` to run separate workers,
|
||||
and ``runwsserver`` to run a Twisted-based WebSocket server.
|
||||
|
||||
You should place consumers in either your ``views.py`` or a ``consumers.py``.
|
||||
Here's an example of WebSocket consumers for basic chat::
|
||||
|
||||
import redis
|
||||
from channels import Channel
|
||||
|
||||
redis_conn = redis.Redis("localhost", 6379)
|
||||
|
||||
@Channel.consumer("django.websockets.connect")
|
||||
def ws_connect(path, send_channel, **kwargs):
|
||||
redis_conn.sadd("chatroom", send_channel)
|
||||
|
||||
@Channel.consumer("django.websocket.receive")
|
||||
def ws_receive(channel, send_channel, content, binary, **kwargs):
|
||||
# Ignore binary messages
|
||||
if binary:
|
||||
return
|
||||
# Re-dispatch message
|
||||
for channel in redis_conn.smembers("chatroom"):
|
||||
Channel(channel).send(content=content, binary=False)
|
||||
|
||||
@Channel.consumer("django.websocket.disconnect")
|
||||
def ws_disconnect(channel, send_channel, **kwargs):
|
||||
redis_conn.srem("chatroom", send_channel)
|
||||
# NOTE: this does not clean up server crash disconnects,
|
||||
# you'd want expiring keys here in real life.
|
||||
|
||||
Alternately, you can just push some code outside of a normal view into a worker
|
||||
thread::
|
||||
|
||||
|
||||
from django.shortcuts import render
|
||||
from channels import Channel
|
||||
|
||||
def my_view(request):
|
||||
# Dispatch a task to run outside the req/response cycle
|
||||
Channel("a_task_channel").send(value=3)
|
||||
# Return a response
|
||||
return render(request, "test.html")
|
||||
|
||||
@Channel.consumer("a_task_channel")
|
||||
def some_task(channel, value):
|
||||
print "My value was %s from channel %s" % (value, channel)
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
The ``runserver`` this command provides currently does not support static
|
||||
media serving, streamed responses or autoreloading.
|
||||
|
||||
In addition, this library is a preview and basically might do anything to your
|
||||
code, or change drastically at any time.
|
||||
|
||||
If you have opinions, please provide feedback via the appropriate
|
||||
`django-developers thread <https://groups.google.com/forum/#!forum/django-developers>`_.
|
|
@ -1,3 +1,5 @@
|
|||
__version__ = "0.1"
|
||||
|
||||
# Load backends, using settings if available (else falling back to a default)
|
||||
DEFAULT_CHANNEL_BACKEND = "default"
|
||||
from .backends import BackendManager
|
||||
|
|
|
@ -27,16 +27,24 @@ class RedisChannelBackend(BaseChannelBackend):
|
|||
return redis.Redis(host=self.host, port=self.port)
|
||||
|
||||
def send(self, channel, message):
|
||||
# Write out message into expiring key (avoids big items in list)
|
||||
key = uuid.uuid4()
|
||||
self.connection.set(
|
||||
key,
|
||||
json.dumps(message),
|
||||
ex = self.expiry + 10,
|
||||
)
|
||||
# Add key to list
|
||||
self.connection.rpush(
|
||||
self.prefix + channel,
|
||||
key,
|
||||
)
|
||||
# Set list to expire when message does (any later messages will bump this)
|
||||
self.connection.expire(
|
||||
self.prefix + channel,
|
||||
self.expiry + 10,
|
||||
)
|
||||
# TODO: Prune expired messages from same list (in case nobody consumes)
|
||||
|
||||
def receive_many(self, channels):
|
||||
if not channels:
|
||||
|
|
|
@ -73,12 +73,33 @@ Sent when a datagram is received on the WebSocket.
|
|||
Contains the same keys as WebSocket Connection, plus:
|
||||
|
||||
* content: String content of the datagram
|
||||
* binary: If the content is to be interpreted as text or binary
|
||||
|
||||
|
||||
WebSocket Close
|
||||
---------------
|
||||
WebSocket Client Close
|
||||
----------------------
|
||||
|
||||
Sent when the WebSocket is closed by either the client or the server.
|
||||
|
||||
Contains the same keys as WebSocket Connection, including send_channel,
|
||||
though nothing should be sent on it.
|
||||
|
||||
|
||||
WebSocket Send
|
||||
--------------
|
||||
|
||||
Sent by a Django consumer to send a message back over the WebSocket to
|
||||
the client.
|
||||
|
||||
Contains the keys:
|
||||
|
||||
* content: String content of the datagram
|
||||
* binary: If the content is to be interpreted as text or binary
|
||||
|
||||
|
||||
WebSocket Server Close
|
||||
----------------------
|
||||
|
||||
Sent by a Django consumer to close the client's WebSocket.
|
||||
|
||||
Contains no keys.
|
||||
|
|
|
@ -14,7 +14,9 @@ class InterfaceProtocol(WebSocketServerProtocol):
|
|||
|
||||
def onConnect(self, request):
|
||||
self.channel_backend = channel_backends[DEFAULT_CHANNEL_BACKEND]
|
||||
self.request = request
|
||||
self.request_info = {
|
||||
"path": request.path,
|
||||
}
|
||||
|
||||
def onOpen(self):
|
||||
# Make sending channel
|
||||
|
@ -23,6 +25,7 @@ class InterfaceProtocol(WebSocketServerProtocol):
|
|||
# Send news that this channel is open
|
||||
Channel("django.websocket.connect").send(
|
||||
send_channel = self.send_channel,
|
||||
**self.request_info
|
||||
)
|
||||
|
||||
def onMessage(self, payload, isBinary):
|
||||
|
@ -31,24 +34,36 @@ class InterfaceProtocol(WebSocketServerProtocol):
|
|||
send_channel = self.send_channel,
|
||||
content = payload,
|
||||
binary = True,
|
||||
**self.request_info
|
||||
)
|
||||
else:
|
||||
Channel("django.websocket.receive").send(
|
||||
send_channel = self.send_channel,
|
||||
content = payload.decode("utf8"),
|
||||
binary = False,
|
||||
**self.request_info
|
||||
)
|
||||
|
||||
def onChannelSend(self, content, binary=False, **kwargs):
|
||||
def serverSend(self, content, binary=False, **kwargs):
|
||||
"""
|
||||
Server-side channel message to send a message.
|
||||
"""
|
||||
if binary:
|
||||
self.sendMessage(content, binary)
|
||||
else:
|
||||
self.sendMessage(content.encode("utf8"), binary)
|
||||
|
||||
def serverClose(self):
|
||||
"""
|
||||
Server-side channel message to close the socket
|
||||
"""
|
||||
self.sendClose()
|
||||
|
||||
def onClose(self, wasClean, code, reason):
|
||||
del self.factory.protocols[self.send_channel]
|
||||
Channel("django.websocket.disconnect").send(
|
||||
send_channel = self.send_channel,
|
||||
**self.request_info
|
||||
)
|
||||
|
||||
|
||||
|
@ -68,7 +83,10 @@ class InterfaceFactory(WebSocketServerFactory):
|
|||
return self.protocols.keys()
|
||||
|
||||
def dispatch_send(self, channel, message):
|
||||
self.protocols[channel].onChannelSend(**message)
|
||||
if message.get("close", False):
|
||||
self.protocols[channel].serverClose()
|
||||
else:
|
||||
self.protocols[channel].serverSend(**message)
|
||||
|
||||
|
||||
class WebsocketTwistedInterface(object):
|
||||
|
|
5
setup.py
5
setup.py
|
@ -1,11 +1,12 @@
|
|||
from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name='django-channel',
|
||||
name='django-channels',
|
||||
version="0.1",
|
||||
url='http://github.com/andrewgodwin/django-channel',
|
||||
url='http://github.com/andrewgodwin/django-channels',
|
||||
author='Andrew Godwin',
|
||||
author_email='andrew@aeracode.org',
|
||||
description="Brings event-driven capabilities to Django with a channel system. Django 1.8 and up only.",
|
||||
license='BSD',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
|
|
Loading…
Reference in New Issue
Block a user