mirror of
				https://github.com/django/daphne.git
				synced 2025-10-31 15:57:25 +03:00 
			
		
		
		
	Rework getting started section to do groups after basic sending.
This commit is contained in:
		
							parent
							
								
									c4719f79bc
								
							
						
					
					
						commit
						a0dff726b2
					
				|  | @ -90,54 +90,106 @@ Now, that's not very exciting - raw HTTP responses are something Django has | |||
| been able to do for a long time. Let's try some WebSockets, and make a basic | ||||
| chat server! | ||||
| 
 | ||||
| Delete that consumer and its routing - we'll want the normal Django view layer to | ||||
| We'll start with a simple server that just echoes every message it gets sent | ||||
| back to the same client - no cross-client communication. It's not terribly | ||||
| useful, but it's a good way to start out writing Channels consumers. | ||||
| 
 | ||||
| Delete that previous consumer and its routing - we'll want the normal Django view layer to | ||||
| serve HTTP requests from now on, which happens if you don't specify a consumer | ||||
| for ``http.request`` - and make this WebSocket consumer instead:: | ||||
| 
 | ||||
|     # In consumers.py | ||||
|     from channels import Group | ||||
| 
 | ||||
|     def ws_add(message): | ||||
|         Group("chat").add(message.reply_channel) | ||||
|     def ws_message(message): | ||||
|         # ASGI WebSocket packet-received and send-packet message types | ||||
|         # both have a "text" key for their textual data.  | ||||
|         message.reply_channel.send({ | ||||
|             "text": message.content['text'], | ||||
|         }) | ||||
| 
 | ||||
| Hook it up to the ``websocket.connect`` channel like this:: | ||||
| Hook it up to the ``websocket.receive`` channel like this:: | ||||
| 
 | ||||
|     # In routing.py | ||||
|     from myproject.myapp.consumers import ws_add | ||||
|     from myproject.myapp.consumers import ws_message | ||||
| 
 | ||||
|     channel_routing = { | ||||
|         "websocket.connect": ws_add, | ||||
|         "websocket.receive": ws_message, | ||||
|     } | ||||
| 
 | ||||
| Now, let's look at what this is doing. It's tied to the | ||||
| ``websocket.connect`` channel, which means that it'll get a message | ||||
| whenever a new WebSocket connection is opened by a client. | ||||
| ``websocket.receive`` channel, which means that it'll get a message | ||||
| whenever a WebSocket packet is sent to us by a client. | ||||
| 
 | ||||
| When it gets that message, it takes the ``reply_channel`` attribute from it, which | ||||
| is the unique response channel for that client, and adds it to the ``chat`` | ||||
| group, which means we can send messages to all connected chat clients. | ||||
| is the unique response channel for that client, and sends the same content | ||||
| back to the client using its ``send()`` method. | ||||
| 
 | ||||
| Of course, if you've read through :doc:`concepts`, you'll know that channels | ||||
| added to groups expire out if their messages expire (every channel layer has | ||||
| a message expiry time, usually between 30 seconds and a few minutes, and it's | ||||
| often configurable). | ||||
| Let's test it! Run ``runserver``, open a browser and put the following into the | ||||
| JavaScript console to open a WebSocket and send some data down it (you might | ||||
| need to change the socket address if you're using a development VM or similar):: | ||||
| 
 | ||||
| However, we'll still get disconnection messages most of the time when a | ||||
| WebSocket disconnects; the expiry/garbage collection of group membership is | ||||
| mostly there for when a disconnect message gets lost (channels are not | ||||
| guaranteed delivery, just mostly reliable). Let's add an explicit disconnect | ||||
| handler:: | ||||
|     // Note that the path doesn't matter for routing; any WebSocket | ||||
|     // connection gets bumped over to WebSocket consumers | ||||
|     socket = new WebSocket("ws://127.0.0.1:8000/chat/"); | ||||
|     socket.onmessage = function(e) { | ||||
|         alert(e.data); | ||||
|     } | ||||
|     socket.onopen = function() { | ||||
|         socket.send("hello world"); | ||||
|     } | ||||
| 
 | ||||
| You should see an alert come back immediately saying "hello world" - your | ||||
| message has round-tripped through the server and come back to trigger the alert. | ||||
| 
 | ||||
| Groups | ||||
| ------ | ||||
| 
 | ||||
| Now, let's make our echo server into an actual chat server, so people can talk | ||||
| to each other. To do this, we'll use Groups, one of the :doc:`core concepts <concepts>` | ||||
| of Channels, and our fundamental way of doing multi-cast messaging. | ||||
| 
 | ||||
| To do this, we'll hook up the ``websocket.connect`` and ``websocket.disconnect`` | ||||
| channels to add and remove our clients from the Group as they connect and | ||||
| disconnect, like this:: | ||||
| 
 | ||||
|     # In consumers.py | ||||
|     from channels import Group | ||||
| 
 | ||||
|     # Connected to websocket.connect | ||||
|     def ws_add(message): | ||||
|         Group("chat").add(message.reply_channel) | ||||
| 
 | ||||
|     # Connected to websocket.disconnect | ||||
|     def ws_disconnect(message): | ||||
|         Group("chat").discard(message.reply_channel) | ||||
| 
 | ||||
| Of course, if you've read through :doc:`concepts`, you'll know that channels | ||||
| added to groups expire out if their messages expire (every channel layer has | ||||
| a message expiry time, usually between 30 seconds and a few minutes, and it's | ||||
| often configurable) - but the ``disconnect`` handler will get called nearly all | ||||
| of the time anyway. | ||||
| 
 | ||||
| .. _note: | ||||
|     Channels' design is predicated on expecting and working around failure; | ||||
|     it assumes that some small percentage of messages will never get delivered, | ||||
|     and so all the core functionality is designed to *expect failure* so that | ||||
|     when a message doesn't get delivered, it doesn't ruin the whole system. | ||||
| 
 | ||||
|     We suggest you design your applications the same way - rather than relying | ||||
|     on 100% guaranteed delivery, which Channels won't give you, look at each | ||||
|     failure case and program something to expect and handle it - be that retry | ||||
|     logic, partial content handling, or just having something not work that one | ||||
|     time. HTTP requests are just as fallible, and most people's reponse to that | ||||
|     is a generic error page! | ||||
| 
 | ||||
| .. _websocket-example: | ||||
| 
 | ||||
| Now, that's taken care of adding and removing WebSocket send channels for the | ||||
| ``chat`` group; all we need to do now is take care of message sending. For now, | ||||
| we're not going to store a history of messages or anything and just replay | ||||
| any message sent in to all connected clients. Here's all the code:: | ||||
| ``chat`` group; all we need to do now is take care of message sending. Instead | ||||
| of echoing the message back to the client like we did above, we'll instead send | ||||
| it to the whole ``Group``, which means any client who's been added to it will | ||||
| get the message. Here's all the code:: | ||||
| 
 | ||||
|     # In consumers.py | ||||
|     from channels import Group | ||||
|  | @ -148,8 +200,6 @@ any message sent in to all connected clients. Here's all the code:: | |||
| 
 | ||||
|     # Connected to websocket.receive | ||||
|     def ws_message(message): | ||||
|         # ASGI WebSocket packet-received and send-packet message types | ||||
|         # both have a "text" key for their textual data.  | ||||
|         Group("chat").send({ | ||||
|             "text": "[user] %s" % message.content['text'], | ||||
|         }) | ||||
|  | @ -169,8 +219,8 @@ And what our routing should look like in ``routing.py``:: | |||
|     } | ||||
| 
 | ||||
| With all that code, you now have a working set of a logic for a chat server. | ||||
| Let's test it! Run ``runserver``, open a browser and put the following into the | ||||
| JavaScript console to open a WebSocket and send some data down it:: | ||||
| Test time! Run ``runserver``, open a browser and use that same JavaScript | ||||
| code in the developer console as before:: | ||||
| 
 | ||||
|     // Note that the path doesn't matter right now; any WebSocket | ||||
|     // connection gets bumped over to WebSocket consumers | ||||
|  | @ -182,16 +232,15 @@ JavaScript console to open a WebSocket and send some data down it:: | |||
|         socket.send("hello world"); | ||||
|     } | ||||
| 
 | ||||
| You should see an alert come back immediately saying "hello world" - your | ||||
| message has round-tripped through the server and come back to trigger the alert. | ||||
| You can open another tab and do the same there if you like, and both tabs will | ||||
| receive the message and show an alert, as any incoming message is sent to the | ||||
| You should see an alert come back immediately saying "hello world" - but this | ||||
| time, you can open another tab and do the same there, and both tabs will | ||||
| receive the message and show an alert. Any incoming message is sent to the | ||||
| ``chat`` group by the ``ws_message`` consumer, and both your tabs will have | ||||
| been put into the ``chat`` group when they connected. | ||||
| 
 | ||||
| Feel free to put some calls to ``print`` in your handler functions too, if you | ||||
| like, so you can understand when they're called. You can also use ``pdb`` and | ||||
| other methods you'd use to debug normal Django projects. | ||||
| other similar methods you'd use to debug normal Django projects. | ||||
| 
 | ||||
| 
 | ||||
| Running with Channels | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user