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)
 | 
					# Load backends, using settings if available (else falling back to a default)
 | 
				
			||||||
DEFAULT_CHANNEL_BACKEND = "default"
 | 
					DEFAULT_CHANNEL_BACKEND = "default"
 | 
				
			||||||
from .backends import BackendManager
 | 
					from .backends import BackendManager
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,16 +27,24 @@ class RedisChannelBackend(BaseChannelBackend):
 | 
				
			||||||
        return redis.Redis(host=self.host, port=self.port)
 | 
					        return redis.Redis(host=self.host, port=self.port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def send(self, channel, message):
 | 
					    def send(self, channel, message):
 | 
				
			||||||
 | 
					        # Write out message into expiring key (avoids big items in list)
 | 
				
			||||||
        key = uuid.uuid4()
 | 
					        key = uuid.uuid4()
 | 
				
			||||||
        self.connection.set(
 | 
					        self.connection.set(
 | 
				
			||||||
            key,
 | 
					            key,
 | 
				
			||||||
            json.dumps(message),
 | 
					            json.dumps(message),
 | 
				
			||||||
            ex = self.expiry + 10,
 | 
					            ex = self.expiry + 10,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        # Add key to list
 | 
				
			||||||
        self.connection.rpush(
 | 
					        self.connection.rpush(
 | 
				
			||||||
            self.prefix + channel,
 | 
					            self.prefix + channel,
 | 
				
			||||||
            key,
 | 
					            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):
 | 
					    def receive_many(self, channels):
 | 
				
			||||||
        if not 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:
 | 
					Contains the same keys as WebSocket Connection, plus:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* content: String content of the datagram
 | 
					* 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.
 | 
					Sent when the WebSocket is closed by either the client or the server.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Contains the same keys as WebSocket Connection, including send_channel,
 | 
					Contains the same keys as WebSocket Connection, including send_channel,
 | 
				
			||||||
though nothing should be sent on it.
 | 
					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):
 | 
					    def onConnect(self, request):
 | 
				
			||||||
        self.channel_backend = channel_backends[DEFAULT_CHANNEL_BACKEND]
 | 
					        self.channel_backend = channel_backends[DEFAULT_CHANNEL_BACKEND]
 | 
				
			||||||
        self.request = request
 | 
					        self.request_info = {
 | 
				
			||||||
 | 
					            "path": request.path,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def onOpen(self):
 | 
					    def onOpen(self):
 | 
				
			||||||
        # Make sending channel
 | 
					        # Make sending channel
 | 
				
			||||||
| 
						 | 
					@ -23,6 +25,7 @@ class InterfaceProtocol(WebSocketServerProtocol):
 | 
				
			||||||
        # Send news that this channel is open
 | 
					        # Send news that this channel is open
 | 
				
			||||||
        Channel("django.websocket.connect").send(
 | 
					        Channel("django.websocket.connect").send(
 | 
				
			||||||
            send_channel = self.send_channel,
 | 
					            send_channel = self.send_channel,
 | 
				
			||||||
 | 
					            **self.request_info
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def onMessage(self, payload, isBinary):
 | 
					    def onMessage(self, payload, isBinary):
 | 
				
			||||||
| 
						 | 
					@ -31,24 +34,36 @@ class InterfaceProtocol(WebSocketServerProtocol):
 | 
				
			||||||
                send_channel = self.send_channel,
 | 
					                send_channel = self.send_channel,
 | 
				
			||||||
                content = payload,
 | 
					                content = payload,
 | 
				
			||||||
                binary = True,
 | 
					                binary = True,
 | 
				
			||||||
 | 
					                **self.request_info
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            Channel("django.websocket.receive").send(
 | 
					            Channel("django.websocket.receive").send(
 | 
				
			||||||
                send_channel = self.send_channel,
 | 
					                send_channel = self.send_channel,
 | 
				
			||||||
                content = payload.decode("utf8"),
 | 
					                content = payload.decode("utf8"),
 | 
				
			||||||
                binary = False,
 | 
					                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:
 | 
					        if binary:
 | 
				
			||||||
            self.sendMessage(content, binary)
 | 
					            self.sendMessage(content, binary)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.sendMessage(content.encode("utf8"), binary)
 | 
					            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):
 | 
					    def onClose(self, wasClean, code, reason):
 | 
				
			||||||
        del self.factory.protocols[self.send_channel]
 | 
					        del self.factory.protocols[self.send_channel]
 | 
				
			||||||
        Channel("django.websocket.disconnect").send(
 | 
					        Channel("django.websocket.disconnect").send(
 | 
				
			||||||
            send_channel = self.send_channel,
 | 
					            send_channel = self.send_channel,
 | 
				
			||||||
 | 
					            **self.request_info
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,7 +83,10 @@ class InterfaceFactory(WebSocketServerFactory):
 | 
				
			||||||
        return self.protocols.keys()
 | 
					        return self.protocols.keys()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def dispatch_send(self, channel, message):
 | 
					    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):
 | 
					class WebsocketTwistedInterface(object):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								setup.py
									
									
									
									
									
								
							| 
						 | 
					@ -1,11 +1,12 @@
 | 
				
			||||||
from setuptools import find_packages, setup
 | 
					from setuptools import find_packages, setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
setup(
 | 
					setup(
 | 
				
			||||||
    name='django-channel',
 | 
					    name='django-channels',
 | 
				
			||||||
    version="0.1",
 | 
					    version="0.1",
 | 
				
			||||||
    url='http://github.com/andrewgodwin/django-channel',
 | 
					    url='http://github.com/andrewgodwin/django-channels',
 | 
				
			||||||
    author='Andrew Godwin',
 | 
					    author='Andrew Godwin',
 | 
				
			||||||
    author_email='andrew@aeracode.org',
 | 
					    author_email='andrew@aeracode.org',
 | 
				
			||||||
 | 
					    description="Brings event-driven capabilities to Django with a channel system. Django 1.8 and up only.",
 | 
				
			||||||
    license='BSD',
 | 
					    license='BSD',
 | 
				
			||||||
    packages=find_packages(),
 | 
					    packages=find_packages(),
 | 
				
			||||||
    include_package_data=True,
 | 
					    include_package_data=True,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user