1.0.0 Release Notes¶
Channels 1.0.0 brings together a number of design changes, including some breaking changes, into our first fully stable release, and also brings the databinding code out of alpha phase. It was released on 2017/01/08.
The result is a faster, easier to use, and safer Channels, including one major change that will fix almost all problems with sessions and connect/receive ordering in a way that needs no persistent storage.
It was unfortunately not possible to make all of the changes backwards compatible, though most code should not be too affected and the fixes are generally quite easy.
You must also update Daphne to at least 1.0.0 to have this release of Channels work correctly.
Major Features¶
Channels 1.0 introduces a couple of new major features.
WebSocket accept/reject flow¶
Rather than be immediately accepted, WebSockets now pause during the handshake
while they send over a message on websocket.connect
, and your application
must either accept or reject the connection before the handshake is completed
and messages can be received.
You must update Daphne to at least 1.0.0 to make this work correctly.
This has several advantages:
You can now reject WebSockets before they even finish connecting, giving appropriate error codes to browsers and not letting the browser-side socket ever get into a connected state and send messages.
Combined with Consumer Atomicity (below), it means there is no longer any need for the old “slight ordering” mode, as the connect consumer must run to completion and accept the socket before any messages can be received and forwarded onto
websocket.receive
.Any
send
message sent to the WebSocket will implicitly accept the connection, meaning only a limited set ofconnect
consumers need changes (see Backwards Incompatible Changes below)
Consumer Atomicity¶
Consumers will now buffer messages you try to send until the consumer completes and then send them once it exits and the outbound part of any decorators have been run (even if an exception is raised).
This makes the flow of messages much easier to reason about - consumers can now be reasoned about as atomic blocks that run and then send messages, meaning that if you send a message to start another consumer you’re guaranteed that the sending consumer has finished running by the time it’s acted upon.
If you want to send messages immediately rather than at the end of the consumer,
you can still do that by passing the immediately
argument:
Channel("thumbnailing-tasks").send({"id": 34245}, immediately=True)
This should be mostly backwards compatible, and may actually fix race conditions in some apps that were pre-existing.
Databinding Group/Action Overhaul¶
Previously, databinding subclasses had to implement
group_names(instance, action)
to return what groups to send an instance’s
change to of the type action
. This had flaws, most notably when what was
actually just a modification to the instance in question changed its
permission status so more clients could see it; to those clients, it should
instead have been “created”.
Now, Channels just calls group_names(instance)
, and you should return what
groups can see the instance at the current point in time given the instance
you were passed. Channels will actually call the method before and after changes,
comparing the groups you gave, and sending out create, update or delete messages
to clients appropriately.
Existing databinding code will need to be adapted; see the “Backwards Incompatible Changes” section for more.
Demultiplexer Overhaul¶
Demuliplexers have changed to remove the behaviour where they re-sent messages
onto new channels without special headers, and instead now correctly split out
incoming messages into sub-messages that still look like websocket.receive
messages, and directly dispatch these to the relevant consumer.
They also now forward all websocket.connect
and websocket.disconnect
messages to all of their sub-consumers, so it’s much easier to compose things
together from code that also works outside the context of multiplexing.
For more, read the updated /generic
docs.
Delay Server¶
A built-in delay server, launched with manage.py rundelay, now ships if you
wish to use it. It needs some extra initial setup and uses a database for
persistence; see /delay
for more information.
Minor Changes¶
Serializers can now specify fields as
__all__
to auto-include all fields, andexclude
to remove certain unwanted fields.runserver
respectsFORCE_SCRIPT_NAME
Websockets can now be closed with a specific code by calling
close(status=4000)
enforce_ordering
no longer has aslight
mode (because of the accept flow changes), and is more efficient with session saving.runserver
respects--nothreading
and only launches one worker, takes a--http-timeout
option if you want to override it from the default60
,A new
@channel_and_http_session
decorator rehydrates the HTTP session out of the channel session if you want to access it inside receive consumers.Streaming responses no longer have a chance of being cached.
request.META['SERVER_PORT']
is now always a string.http.disconnect
now has apath
key so you can route it.Test client now has a
send_and_consume
method.
Backwards Incompatible Changes¶
Connect Consumers¶
If you have a custom consumer for websocket.connect
, you must ensure that
it either:
Sends at least one message onto the
reply_channel
that generates a WebSocket frame (eitherbytes
ortext
is set), either directly or via a group.Sends a message onto the
reply_channel
that is{"accept": True}
, to accept a connection without sending data.Sends a message onto the
reply_channel
that is{"close": True}
, to reject a connection mid-handshake.
Many consumers already do the former, but if your connect consumer does not send anything you MUST now send an accept message or the socket will remain in the handshaking phase forever and you’ll never get any messages.
All built-in Channels consumers (e.g. in the generic consumers) have been upgraded to do this.
You must update Daphne to at least 1.0.0 to make this work correctly.
Databinding group_names¶
If you have databinding subclasses, you will have implemented
group_names(instance, action)
, which returns the groups to use based on the
instance and action provided.
Now, instead, you must implement group_names(instance)
, which returns the
groups that can see the instance as it is presented for you; the action
results will be worked out for you. For example, if you want to only show
objects marked as “admin_only” to admins, and objects without it to everyone,
previously you would have done:
def group_names(self, instance, action):
if instance.admin_only:
return ["admins"]
else:
return ["admins", "non-admins"]
Because you did nothing based on the action
(and if you did, you would
have got incomplete messages, hence this design change), you can just change
the signature of the method like this:
def group_names(self, instance):
if instance.admin_only:
return ["admins"]
else:
return ["admins", "non-admins"]
Now, when an object is updated to have admin_only = True
, the clients
in the non-admins
group will get a delete
message, while those in
the admins
group will get an update
message.
Demultiplexers¶
Demultiplexers have changed from using a mapping
dict, which mapped stream
names to channels, to using a consumers
dict which maps stream names
directly to consumer classes.
You will have to convert over to using direct references to consumers, change
the name of the dict, and then you can remove any channel routing for the old
channels that were in mapping
from your routes.
Additionally, the Demultiplexer now forwards messages as they would look from
a direct connection, meaning that where you previously got a decoded object
through you will now get a correctly-formatted websocket.receive
message
through with the content as a text
key, JSON-encoded. You will also
now have to handle websocket.connect
and websocket.disconnect
messages.
Both of these issues can be solved using the JsonWebsocketConsumer
generic
consumer, which will decode for you and correctly separate connection and
disconnection handling into their own methods.