mirror of
https://github.com/django/daphne.git
synced 2025-06-06 06:03:24 +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
|
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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user