mirror of
				https://github.com/django/daphne.git
				synced 2025-11-04 09:37:32 +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