mirror of
https://github.com/django/daphne.git
synced 2025-04-21 17:22:03 +03:00
Merge pull request #26 from maikhoepfel/patch-1
Wording changes to docs
This commit is contained in:
commit
17e42a85fb
|
@ -5,13 +5,13 @@ Django's traditional view of the world revolves around requests and responses;
|
|||
a request comes in, Django is fired up to serve it, generates a response to
|
||||
send, and then Django goes away and waits for the next request.
|
||||
|
||||
That was fine when the internet was all driven by simple browser interactions,
|
||||
That was fine when the internet was driven by simple browser interactions,
|
||||
but the modern Web includes things like WebSockets and HTTP2 server push,
|
||||
which allow websites to communicate outside of this traditional cycle.
|
||||
|
||||
And, beyond that, there are plenty of non-critical tasks that applications
|
||||
could easily offload until after a response as been sent - like saving things
|
||||
into a cache, or thumbnailing newly-uploaded images.
|
||||
into a cache or thumbnailing newly-uploaded images.
|
||||
|
||||
Channels changes the way Django runs to be "event oriented" - rather than
|
||||
just responding to requests, instead Django responses to a wide array of events
|
||||
|
@ -38,7 +38,7 @@ alternative is *at-least-once*, where normally one consumer gets the message
|
|||
but when things crash it's sent to more than one, which is not the trade-off
|
||||
we want.
|
||||
|
||||
There are a couple of other limitations - messages must be JSON-serialisable,
|
||||
There are a couple of other limitations - messages must be JSON serializable,
|
||||
and not be more than 1MB in size - but these are to make the whole thing
|
||||
practical, and not too important to think about up front.
|
||||
|
||||
|
@ -59,13 +59,13 @@ channel, they're writing into the same channel.
|
|||
How do we use channels?
|
||||
-----------------------
|
||||
|
||||
That's what a channel is, but how is Django using them? Well, inside Django
|
||||
you can write a function to consume a channel, like so::
|
||||
So how is Django using those channels? Inside Django
|
||||
you can write a function to consume a channel::
|
||||
|
||||
def my_consumer(message):
|
||||
pass
|
||||
|
||||
And then assign a channel to it like this in the channel routing::
|
||||
And then assign a channel to it in the channel routing::
|
||||
|
||||
channel_routing = {
|
||||
"some-channel": "myapp.consumers.my_consumer",
|
||||
|
@ -76,14 +76,11 @@ consumer function with a message object (message objects have a "content"
|
|||
attribute which is always a dict of data, and a "channel" attribute which
|
||||
is the channel it came from, as well as some others).
|
||||
|
||||
Django can do this as rather than run in a request-response mode, Channels
|
||||
changes Django so that it runs in a worker mode - it listens on all channels
|
||||
that have consumers assigned, and when a message arrives on one, runs the
|
||||
relevant consumer.
|
||||
|
||||
In fact, this is illustrative of the new way Django runs to enable Channels to
|
||||
work. Rather than running in just a single process tied to a WSGI server,
|
||||
Django runs in three separate layers:
|
||||
Instead of having Django run in the traditional request-response mode,
|
||||
Channels changes Django so that it runs in a worker mode - it listens on
|
||||
all channels that have consumers assigned, and when a message arrives on
|
||||
one, it runs the relevant consumer. So rather than running in just a
|
||||
single process tied to a WSGI server, Django runs in three separate layers:
|
||||
|
||||
* Interface servers, which communicate between Django and the outside world.
|
||||
This includes a WSGI adapter as well as a separate WebSocket server - we'll
|
||||
|
@ -104,8 +101,8 @@ message and can write out zero to many other channel messages.
|
|||
|
||||
Now, let's make a channel for requests (called ``http.request``),
|
||||
and a channel per client for responses (e.g. ``http.response.o4F2h2Fd``),
|
||||
with the response channel a property (``reply_channel``) of the request message.
|
||||
Suddenly, a view is merely another example of a consumer::
|
||||
where the response channel is a property (``reply_channel``) of the request
|
||||
message. Suddenly, a view is merely another example of a consumer::
|
||||
|
||||
# Listens on http.request
|
||||
def my_consumer(message):
|
||||
|
@ -120,22 +117,20 @@ In fact, this is how Channels works. The interface servers transform connections
|
|||
from the outside world (HTTP, WebSockets, etc.) into messages on channels,
|
||||
and then you write workers to handle these messages.
|
||||
|
||||
This may seem like it's still not very well designed to handle push-style
|
||||
code - where you use HTTP2's server-sent events or a WebSocket to notify
|
||||
clients of changes in real time (messages in a chat, perhaps, or live updates
|
||||
in an admin as another user edits something).
|
||||
|
||||
However, the key here is that you can run code (and so send on channels) in
|
||||
response to any event - and that includes ones you create. You can trigger
|
||||
on model saves, on other incoming messages, or from code paths inside views
|
||||
and forms.
|
||||
and forms. That approach comes in handy for push-style
|
||||
code - where you use HTML5's server-sent events or a WebSocket to notify
|
||||
clients of changes in real time (messages in a chat, perhaps, or live updates
|
||||
in an admin as another user edits something).
|
||||
|
||||
.. _channel-types:
|
||||
|
||||
Channel Types
|
||||
-------------
|
||||
|
||||
Now, if you think about it, there are actually two major uses for channels in
|
||||
There are actually two major uses for channels in
|
||||
this model. The first, and more obvious one, is the dispatching of work to
|
||||
consumers - a message gets added to a channel, and then any one of the workers
|
||||
can pick it up and run the consumer.
|
||||
|
@ -240,15 +235,15 @@ Next Steps
|
|||
----------
|
||||
|
||||
That's the high-level overview of channels and groups, and how you should
|
||||
starting thinking about them - remember, Django provides some channels
|
||||
start thinking about them. Remember, Django provides some channels
|
||||
but you're free to make and consume your own, and all channels are
|
||||
network-transparent.
|
||||
|
||||
One thing channels are not, however, is guaranteed delivery. If you want tasks
|
||||
you're sure will complete, use a system designed for this with retries and
|
||||
persistence like Celery, or you'll need to make a management command that
|
||||
checks for completion and re-submits a message to the channel if nothing
|
||||
is completed (rolling your own retry logic, essentially).
|
||||
One thing channels do not, however, is guarantee delivery. If you need
|
||||
certainty that tasks will complete, use a system designed for this with
|
||||
retries and persistence (e.g. Celery), or alternatively make a management
|
||||
command that checks for completion and re-submits a message to the channel
|
||||
if nothing is completed (rolling your own retry logic, essentially).
|
||||
|
||||
We'll cover more about what kind of tasks fit well into Channels in the rest
|
||||
of the documentation, but for now, let's progress to :doc:`getting-started`
|
||||
|
|
|
@ -30,7 +30,7 @@ Make a new project, a new app, and put this in a ``consumers.py`` file in the ap
|
|||
message.reply_channel.send(response.channel_encode())
|
||||
|
||||
The most important thing to note here is that, because things we send in
|
||||
messages must be JSON-serialisable, the request and response messages
|
||||
messages must be JSON serializable, the request and response messages
|
||||
are in a key-value format. There are ``channel_decode()`` and
|
||||
``channel_encode()`` methods on both Django's request and response classes,
|
||||
but here we just use the message's ``content`` attribute directly for simplicity
|
||||
|
@ -69,12 +69,14 @@ If you start up ``python manage.py runserver`` and go to
|
|||
you get the Hello World response, so things are working. If you don't see
|
||||
a response, check you :doc:`installed Channels correctly <installation>`.
|
||||
|
||||
Now, that's not very exciting - raw HTTP responses are something Django can
|
||||
do any time. Let's try some WebSockets, and make a basic chat server!
|
||||
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
|
||||
serve HTTP requests from now on - and make this WebSocket consumer instead::
|
||||
|
||||
# In consumers.py
|
||||
from channels import Group
|
||||
|
||||
def ws_add(message):
|
||||
|
@ -82,6 +84,7 @@ serve HTTP requests from now on - and make this WebSocket consumer instead::
|
|||
|
||||
Hook it up to the ``websocket.connect`` channel like this::
|
||||
|
||||
# In routing.py
|
||||
channel_routing = {
|
||||
"websocket.connect": "myproject.myapp.consumers.ws_add",
|
||||
}
|
||||
|
@ -102,19 +105,21 @@ connections you have open at any one time.
|
|||
|
||||
The solution to this is that the WebSocket interface servers will send
|
||||
periodic "keepalive" messages on the ``websocket.keepalive`` channel,
|
||||
so we can hook that up to re-add the channel (it's safe to add the channel to
|
||||
a group it's already in - similarly, it's safe to discard a channel from a
|
||||
group it's not in)::
|
||||
so we can hook that up to re-add the channel::
|
||||
|
||||
# In consumers.py
|
||||
from channels import Group
|
||||
|
||||
# Connected to websocket.keepalive
|
||||
def ws_keepalive(message):
|
||||
Group("chat").add(message.reply_channel)
|
||||
|
||||
It's safe to add the channel to a group it's already in - similarly, it's
|
||||
safe to discard a channel from a group it's not in.
|
||||
Of course, this is exactly the same code as the ``connect`` handler, so let's
|
||||
just route both channels to the same consumer::
|
||||
|
||||
# In routing.py
|
||||
channel_routing = {
|
||||
"websocket.connect": "myproject.myapp.consumers.ws_add",
|
||||
"websocket.keepalive": "myproject.myapp.consumers.ws_add",
|
||||
|
@ -124,6 +129,7 @@ And, even though channels will expire out, let's add an explicit ``disconnect``
|
|||
handler to clean up as people disconnect (most channels will cleanly disconnect
|
||||
and get this called)::
|
||||
|
||||
# In consumers.py
|
||||
from channels import Group
|
||||
|
||||
# Connected to websocket.disconnect
|
||||
|
@ -135,6 +141,7 @@ Now, that's taken care of adding and removing WebSocket send channels for the
|
|||
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::
|
||||
|
||||
# In consumers.py
|
||||
from channels import Group
|
||||
|
||||
# Connected to websocket.connect and websocket.keepalive
|
||||
|
@ -158,9 +165,9 @@ And what our routing should look like in ``routing.py``::
|
|||
"websocket.disconnect": "myproject.myapp.consumers.ws_disconnect",
|
||||
}
|
||||
|
||||
With all that code in your ``consumers.py`` file, you now have a working
|
||||
set of a logic for a chat server. All you need to do now is get it deployed,
|
||||
and as we'll see, that's not too hard.
|
||||
With all that code, you now have a working set of a logic for a chat server.
|
||||
All you need to do now is get it deployed, and as we'll see, that's not too
|
||||
hard.
|
||||
|
||||
Running with Channels
|
||||
---------------------
|
||||
|
@ -168,7 +175,7 @@ Running with Channels
|
|||
Because Channels takes Django into a multi-process model, you can no longer
|
||||
just run one process if you want to serve more than one protocol type.
|
||||
|
||||
There are multiple kinds of "interface server", and each one will service a
|
||||
There are multiple kinds of "interface servers", and each one will service a
|
||||
different type of request - one might do WSGI requests, one might handle
|
||||
WebSockets, or you might have one that handles both.
|
||||
|
||||
|
@ -233,7 +240,7 @@ Persisting Data
|
|||
Echoing messages is a nice simple example, but it's
|
||||
skirting around the real design pattern - persistent state for connections.
|
||||
Let's consider a basic chat site where a user requests a chat room upon initial
|
||||
connection, as part of the query string (e.g. ``http://host/websocket?room=abc``).
|
||||
connection, as part of the query string (e.g. ``wss://host/websocket?room=abc``).
|
||||
|
||||
The ``reply_channel`` attribute you've seen before is our unique pointer to the
|
||||
open WebSocket - because it varies between different clients, it's how we can
|
||||
|
@ -253,6 +260,7 @@ just like a normal Django session.
|
|||
Let's use it now to build a chat server that expects you to pass a chatroom
|
||||
name in the path of your WebSocket request (we'll ignore auth for now - that's next)::
|
||||
|
||||
# In consumers.py
|
||||
from channels import Group
|
||||
from channels.decorators import channel_session
|
||||
|
||||
|
@ -282,7 +290,7 @@ name in the path of your WebSocket request (we'll ignore auth for now - that's n
|
|||
|
||||
If you play around with it from the console (or start building a simple
|
||||
JavaScript chat client that appends received messages to a div), you'll see
|
||||
that you can now request which chat room you want in the initial request.
|
||||
that you can set a chat room with the initial request.
|
||||
|
||||
Authentication
|
||||
--------------
|
||||
|
@ -336,9 +344,10 @@ loads the user from the *channel* session rather than the *HTTP* session,
|
|||
and a function called ``transfer_user`` which replicates a user from one session
|
||||
to another.
|
||||
|
||||
Bringing that all together, let's make a chat server one where users can only
|
||||
Bringing that all together, let's make a chat server where users can only
|
||||
chat to people with the same first letter of their username::
|
||||
|
||||
# In consumers.py
|
||||
from channels import Channel, Group
|
||||
from channels.decorators import channel_session
|
||||
from channels.auth import http_session_user, channel_session_user, transfer_user
|
||||
|
@ -375,7 +384,7 @@ Django session ID as part of the URL, like this::
|
|||
|
||||
You can get the current session key in a template with ``{{ request.session.session_key }}``.
|
||||
Note that Channels can't work with signed cookie sessions - since only HTTP
|
||||
responses can set cookies, it needs a backend it can write to separately to
|
||||
responses can set cookies, it needs a backend it can write to to separately to
|
||||
store state.
|
||||
|
||||
|
||||
|
@ -393,7 +402,7 @@ easily integrate the send into the save flow of the model, rather than the
|
|||
message receive - that way, any new message saved will be broadcast to all
|
||||
the appropriate clients, no matter where it's saved from.
|
||||
|
||||
We'll even take some performance considerations into account - We'll make our
|
||||
We'll even take some performance considerations into account: We'll make our
|
||||
own custom channel for new chat messages and move the model save and the chat
|
||||
broadcast into that, meaning the sending process/consumer can move on
|
||||
immediately and not spend time waiting for the database save and the
|
||||
|
@ -402,10 +411,12 @@ immediately and not spend time waiting for the database save and the
|
|||
Let's see what that looks like, assuming we
|
||||
have a ChatMessage model with ``message`` and ``room`` fields::
|
||||
|
||||
# In consumers.py
|
||||
from channels import Channel
|
||||
from channels.decorators import channel_session
|
||||
from .models import ChatMessage
|
||||
|
||||
# Connected to chat-messages
|
||||
def msg_consumer(message):
|
||||
# Save to model
|
||||
ChatMessage.objects.create(
|
||||
|
@ -482,6 +493,7 @@ decorator, but generally you'll want to use it for most session-based WebSocket
|
|||
and other "continuous protocol" things. Here's an example, improving our
|
||||
first-letter-of-username chat from earlier::
|
||||
|
||||
# In consumers.py
|
||||
from channels import Channel, Group
|
||||
from channels.decorators import channel_session, linearize
|
||||
from channels.auth import http_session_user, channel_session_user, transfer_user
|
||||
|
|
Loading…
Reference in New Issue
Block a user