A talk I gave at Django Under The Hood 2016.
View Slide
Andrew GodwinHi, I'mDjango core developerSenior Software Engineer atUsed to complain about migrations a lot
It's magic.
The Problem1
The Web is changing.
WebSockets
WebSocketsWebRTCLong-pollingMQTTServer-Sent Events
Python is synchronous.Django is synchronous.
Synchronous code is easier to write.Single-process async is not enough.
Proven design patternNot too hard to reason about
What could fit these constraints?
Loose Coupling2
Not too tied to WebSocketsNot too tied to Django
Well-defined, minimal interfaces
Easy to swap out or rewrite
The Message BusHTTPServerMessage BusWSockServerDjangoProject
What do you send?How do you send it?
ASGI
nonblocking sendblocking receiveadd to groupdiscard from groupsend to group
JSON-compatible,dictionary-basedmessages ontonamed channels
Concrete Ideas3
Develop using concrete examples
WebSocketconnectreceivedisconnectaccept/rejectsend
WebSocketwebsocket.connectwebsocket.send!abc1234websocket.receivewebsocket.disconnect
At-most-onceFirst In First OutBackpressure via capacityNot stickyNo guaranteed orderingNo serial processing
HTTP& WSChannel LayerDjangoWorkerHTTP& WSDjangoWorkerDjangoWorkerDjangoWorker
{"text": "Hello, world!","path": "/chat/socket/","reply_channel": "websocket.send!9m12in2p",}
Developed and spec'dHTTP WebSocketRough draftsIRC Email SlackPlease, no.Minecraft Mainframe Terminal
{"reply_channel": "http.response!g23vD2x5","method": "GET","http_version": "2","path": "/chat/socket/","query_string": "foo=bar","headers": [["cookie", "abcdef..."]],}
"order" key on receive messagesConnection acceptance
DaphneHTTP/WebSocket ServerChannelsDjango integrationasgi-redisRedis backendasgi-ipcLocal memory backendasgirefShared code and libs
Django-ish4
It can take several triesto get a nice API.
Consumers based on ViewsCallable that takes an objectDecorators for functionalityClass-based generics
@channel_sessiondef chat_receive(message):name = message.channel_session["name"]message.reply_channel.send({"text": "OK"})Group("chat").send({"text": "%s: %s" % (name, message["text"]),})Message.objects.create(name=name,content=message["text"],)
Routing based on URLsList of regex-based matchesIncludes with prefix stripping on pathsMore standardised interface
routing = [route("websocket.receive",consumers.chat_receive,path=r"^/chat/socket/$",),include("stats.routing", path="^/stats/"),route_class(ConsumerClass, path="^/v1/"),]
Sessions are the only stateSessions hang off reply channels not cookiesUses same sessions backendsAvailable on the consumer's argumentCan also access long-term cookie sessions
@enforce_orderingdef receive_consumer(message):Log.objects.create(...)
session = session_for_reply_channel(message.reply_channel.name)if not session.exists(session.session_key):try:session.save(must_create=True)except CreateError:# Session wasn't uniqueraise ConsumeLater()message.channel_session = session
No MiddlewareNew-style middleware half worksNo ability to capture sendsDecorators replace most cases
View/HTTP Django still thereCan intermingle or just use one typeView system is just a consumer now
def view_consumer(message):replies = AsgiHandler()(message)for reply in replies:while True:try:message.reply_channel.send(reply)except ChannelFull:time.sleep(0.05)else:break
Signals and commandsrunserver works as expectedSignals for handling lifecyclestaticfiles configured for development
Beyond5
Generalised async communication
Service messagingSecurity/CPU separationSync & Async / Py2 & Py3
Diversity of implementationsMore web serversMore channel layers
More optimisationMore efficient bulk sendsLess network traffic on receive
More maintainersMore viewpoints, more time
1.0 coming soonStable APIs for everything except binding
Thanks.Andrew Godwin@andrewgodwinchannels.readthedocs.iogithub.com/andrewgodwin/channels-examples