Rework getting started section to do groups after basic sending.

This commit is contained in:
Andrew Godwin 2016-03-06 11:28:52 -08:00
parent c4719f79bc
commit a0dff726b2

View File

@ -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 been able to do for a long time. Let's try some WebSockets, and make a basic
chat server! 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 serve HTTP requests from now on, which happens if you don't specify a consumer
for ``http.request`` - and make this WebSocket consumer instead:: for ``http.request`` - and make this WebSocket consumer instead::
# In consumers.py # In consumers.py
from channels import Group from channels import Group
def ws_add(message): def ws_message(message):
Group("chat").add(message.reply_channel) # 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 # In routing.py
from myproject.myapp.consumers import ws_add from myproject.myapp.consumers import ws_message
channel_routing = { channel_routing = {
"websocket.connect": ws_add, "websocket.receive": ws_message,
} }
Now, let's look at what this is doing. It's tied to the 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 ``websocket.receive`` channel, which means that it'll get a message
whenever a new WebSocket connection is opened by a client. 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 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`` is the unique response channel for that client, and sends the same content
group, which means we can send messages to all connected chat clients. back to the client using its ``send()`` method.
Of course, if you've read through :doc:`concepts`, you'll know that channels Let's test it! Run ``runserver``, open a browser and put the following into the
added to groups expire out if their messages expire (every channel layer has JavaScript console to open a WebSocket and send some data down it (you might
a message expiry time, usually between 30 seconds and a few minutes, and it's need to change the socket address if you're using a development VM or similar)::
often configurable).
However, we'll still get disconnection messages most of the time when a // Note that the path doesn't matter for routing; any WebSocket
WebSocket disconnects; the expiry/garbage collection of group membership is // connection gets bumped over to WebSocket consumers
mostly there for when a disconnect message gets lost (channels are not socket = new WebSocket("ws://127.0.0.1:8000/chat/");
guaranteed delivery, just mostly reliable). Let's add an explicit disconnect socket.onmessage = function(e) {
handler:: 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 # Connected to websocket.disconnect
def ws_disconnect(message): def ws_disconnect(message):
Group("chat").discard(message.reply_channel) 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: .. _websocket-example:
Now, that's taken care of adding and removing WebSocket send channels for the 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, ``chat`` group; all we need to do now is take care of message sending. Instead
we're not going to store a history of messages or anything and just replay of echoing the message back to the client like we did above, we'll instead send
any message sent in to all connected clients. Here's all the code:: 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 # In consumers.py
from channels import Group 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 # Connected to websocket.receive
def ws_message(message): 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({ Group("chat").send({
"text": "[user] %s" % message.content['text'], "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. 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 Test time! Run ``runserver``, open a browser and use that same JavaScript
JavaScript console to open a WebSocket and send some data down it:: code in the developer console as before::
// Note that the path doesn't matter right now; any WebSocket // Note that the path doesn't matter right now; any WebSocket
// connection gets bumped over to WebSocket consumers // 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"); socket.send("hello world");
} }
You should see an alert come back immediately saying "hello world" - your You should see an alert come back immediately saying "hello world" - but this
message has round-tripped through the server and come back to trigger the alert. time, you can open another tab and do the same there, and both tabs will
You can open another tab and do the same there if you like, and both tabs will receive the message and show an alert. Any incoming message is sent to the
receive the message and show an alert, as any incoming message is sent to the
``chat`` group by the ``ws_message`` consumer, and both your tabs will have ``chat`` group by the ``ws_message`` consumer, and both your tabs will have
been put into the ``chat`` group when they connected. been put into the ``chat`` group when they connected.
Feel free to put some calls to ``print`` in your handler functions too, if you 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 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 Running with Channels