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 of connect 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 persistance; see /delay for more information.

Minor Changes

  • Serializers can now specify fields as __all__ to auto-include all fields, and exclude to remove certain unwanted fields.
  • runserver respects FORCE_SCRIPT_NAME
  • Websockets can now be closed with a specific code by calling close(status=4000)
  • enforce_ordering no longer has a slight 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 default 60,
  • 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 a path 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 (either bytes or text 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.