mirror of
https://github.com/django/daphne.git
synced 2025-04-20 00:32:09 +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*.
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
be the character ``!`` - e.g. ``!django.wsgi.response.f5G3fE21f``. Normal
|
||||
channels have no special prefix, but along with the rest of the response
|
||||
denotes a *response channel* by having the first character of the channel name
|
||||
be the character ``!`` - e.g. ``!django.wsgi.response.f5G3fE21f``. *Normal
|
||||
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 - _``,
|
||||
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
|
||||
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
|
||||
===============
|
||||
|
||||
(If you haven't yet, make sure you :doc:`install Channels <installation>`
|
||||
and read up on :doc:`the concepts behind Channels <concepts>`)
|
||||
(If you haven't yet, make sure you :doc:`install Channels <installation>`)
|
||||
|
||||
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