mirror of
https://github.com/django/daphne.git
synced 2025-07-14 18:02:17 +03:00
Update docs a bit more
This commit is contained in:
parent
217afe0348
commit
60f0680ec2
|
@ -26,7 +26,7 @@ What is a channel?
|
||||||
|
|
||||||
The core of Channels is, unsurprisingly, a datastructure called a *channel*.
|
The core of Channels is, unsurprisingly, a datastructure called a *channel*.
|
||||||
What is a channel? It is an *ordered*, *first-in first-out queue* with
|
What is a channel? It is an *ordered*, *first-in first-out queue* with
|
||||||
*at-most-once delivery* to *only one listener at a time*.
|
*message expiry* and *at-most-once delivery* to *only one listener at a time*.
|
||||||
|
|
||||||
You can think of it as analagous to a task queue - messages are put onto
|
You can think of it as analagous to a task queue - messages are put onto
|
||||||
the channel by *producers*, and then given to just one of the *consumers*
|
the channel by *producers*, and then given to just one of the *consumers*
|
||||||
|
@ -147,9 +147,9 @@ the message - but response channels would have to have their messages sent
|
||||||
to the channel server they're listening on.
|
to the channel server they're listening on.
|
||||||
|
|
||||||
For this reason, Channels treats these as two different *channel types*, and
|
For this reason, Channels treats these as two different *channel types*, and
|
||||||
denotes a response channel by having the first character of the channel name
|
denotes a *response channel* by having the first character of the channel name
|
||||||
be the character ``!`` - e.g. ``!django.wsgi.response.f5G3fE21f``. Normal
|
be the character ``!`` - e.g. ``!django.wsgi.response.f5G3fE21f``. *Normal
|
||||||
channels have no special prefix, but along with the rest of the response
|
channels* have no special prefix, but along with the rest of the response
|
||||||
channel name, they must contain only the characters ``a-z A-Z 0-9 - _``,
|
channel name, they must contain only the characters ``a-z A-Z 0-9 - _``,
|
||||||
and be less than 200 characters long.
|
and be less than 200 characters long.
|
||||||
|
|
||||||
|
@ -167,6 +167,85 @@ keep track of which response channels of those you wish to send to.
|
||||||
|
|
||||||
Say I had a live blog where I wanted to push out updates whenever a new post is
|
Say I had a live blog where I wanted to push out updates whenever a new post is
|
||||||
saved, I would register a handler for the ``post_save`` signal and keep a
|
saved, I would register a handler for the ``post_save`` signal and keep a
|
||||||
set of channels to send updates to::
|
set of channels (here, using Redis) to send updates to::
|
||||||
|
|
||||||
(todo)
|
|
||||||
|
redis_conn = redis.Redis("localhost", 6379)
|
||||||
|
|
||||||
|
@receiver(post_save, sender=BlogUpdate)
|
||||||
|
def send_update(sender, instance, **kwargs):
|
||||||
|
# Loop through all response channels and send the update
|
||||||
|
for send_channel in redis_conn.smembers("readers"):
|
||||||
|
Channel(send_channel).send(
|
||||||
|
id=instance.id,
|
||||||
|
content=instance.content,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Channel.consumer("django.websocket.connect")
|
||||||
|
def ws_connect(path, send_channel, **kwargs):
|
||||||
|
# Add to reader set
|
||||||
|
redis_conn.sadd("readers", send_channel)
|
||||||
|
|
||||||
|
While this will work, there's a small problem - we never remove people from
|
||||||
|
the ``readers`` set when they disconnect. We could add a consumer that
|
||||||
|
listens to ``django.websocket.disconnect`` to do that, but we'd also need to
|
||||||
|
have some kind of expiry in case an interface server is forced to quit or
|
||||||
|
loses power before it can send disconnect signals - your code will never
|
||||||
|
see any disconnect notification but the response channel is completely
|
||||||
|
invalid and messages you send there will never get consumed and just expire.
|
||||||
|
|
||||||
|
Because the basic design of channels is stateless, the channel server has no
|
||||||
|
concept of "closing" a channel if an interface server goes away - after all,
|
||||||
|
channels are meant to hold messages until a consumer comes along (and some
|
||||||
|
types of interface server, e.g. an SMS gateway, could theoretically serve
|
||||||
|
any client from any interface server).
|
||||||
|
|
||||||
|
That means that we need to follow a keepalive model, where the interface server
|
||||||
|
(or, if you want even better accuracy, the client browser/connection) sends
|
||||||
|
a periodic message saying it's still connected (though only for persistent
|
||||||
|
connection types like WebSockets; normal HTTP doesn't need this as it won't
|
||||||
|
stay connected for more than its own timeout).
|
||||||
|
|
||||||
|
Now, we could go back into our example above and add an expiring set and keep
|
||||||
|
track of expiry times and so forth, but this is such a common pattern that
|
||||||
|
we don't need to; Channels has it built in, as a feature called Groups::
|
||||||
|
|
||||||
|
@receiver(post_save, sender=BlogUpdate)
|
||||||
|
def send_update(sender, instance, **kwargs):
|
||||||
|
Group("liveblog").send(
|
||||||
|
id=instance.id,
|
||||||
|
content=instance.content,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Channel.consumer("django.websocket.connect")
|
||||||
|
@Channel.consumer("django.websocket.keepalive")
|
||||||
|
def ws_connect(path, send_channel, **kwargs):
|
||||||
|
# Add to reader group
|
||||||
|
Group("liveblog").add(send_channel)
|
||||||
|
|
||||||
|
Not only do groups have their own ``send()`` method (which backends can provide
|
||||||
|
an efficient implementation of), they also automatically manage expiry of
|
||||||
|
the group members. You'll have to re-call ``Group.add()`` every so often to
|
||||||
|
keep existing members from expiring, but that's easy, and can be done in the
|
||||||
|
same handler for both ``connect`` and ``keepalive``, as you can see above.
|
||||||
|
|
||||||
|
Groups are generally only useful for response channels (ones starting with
|
||||||
|
the character ``!``), as these are unique-per-client.
|
||||||
|
|
||||||
|
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
|
||||||
|
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).
|
||||||
|
|
||||||
|
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`
|
||||||
|
and writing some code.
|
||||||
|
|
|
@ -1,6 +1,49 @@
|
||||||
Getting Started
|
Getting Started
|
||||||
===============
|
===============
|
||||||
|
|
||||||
(If you haven't yet, make sure you :doc:`install Channels <installation>`
|
(If you haven't yet, make sure you :doc:`install Channels <installation>`)
|
||||||
and read up on :doc:`the concepts behind Channels <concepts>`)
|
|
||||||
|
|
||||||
|
Now, let's get to writing some consumers. If you've not read it already,
|
||||||
|
you should read :doc:`concepts`, as it covers the basic description of what
|
||||||
|
channels and groups are, and lays out some of the important implementation
|
||||||
|
patterns and caveats.
|
||||||
|
|
||||||
|
First Consumers
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Now, by default, Django will run things through Channels but it will also
|
||||||
|
tie in the URL router and view subsystem to the default ``django.wsgi.request``
|
||||||
|
channel if you don't provide another consumer that listens to it - remember,
|
||||||
|
only one consumer can listen to any given channel.
|
||||||
|
|
||||||
|
As a very basic example, let's write a consumer that overrides the built-in
|
||||||
|
handling and handles every HTTP request directly. Make a new project, a new
|
||||||
|
app, and put this in a ``consumers.py`` file in the app::
|
||||||
|
|
||||||
|
from channels import Channel
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
@Channel.consumer("django.wsgi.request")
|
||||||
|
def http_consumer(response_channel, path, **kwargs):
|
||||||
|
response = HttpResponse("Hello world! You asked for %s" % path)
|
||||||
|
Channel(response_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
|
||||||
|
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 take two of the request variables directly as keyword
|
||||||
|
arguments for simplicity.
|
||||||
|
|
||||||
|
If you start up ``python manage.py runserver`` and go to
|
||||||
|
``http://localhost:8000``, you'll see that, rather than a default Django page,
|
||||||
|
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!
|
||||||
|
|
||||||
|
Delete that consumer from above - we'll need the normal Django view layer to
|
||||||
|
serve templates later - and make this WebSocket consumer instead::
|
||||||
|
|
||||||
|
# todo
|
||||||
|
|
Loading…
Reference in New Issue
Block a user