Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building real time applications with Django and...

Building real time applications with Django and Channels 2 @ PyCon Italia

A primer on Django Channels

Since the introduction of Channels, real time web has become much easier to work with in Django: discover how you can easily create yours with Django!

Iacopo Spalletti

April 20, 2018
Tweet

More Decks by Iacopo Spalletti

Other Decks in Programming

Transcript

  1. A QUICK PRIMER ON CHANNELS A QUICK PRIMER ON CHANNELS

    BUILDING REAL TIME BUILDING REAL TIME APPLICATIONS WITH APPLICATIONS WITH DJANGO DJANGO PyCon Italia Firenze - 20 Aprile
  2. HELLO, I AM IACOPO HELLO, I AM IACOPO Founder and

    CTO @NephilaIt Djangonaut and django CMS core developer @yakkys @[email protected] https://keybase.io/yakky https://github.com/yakky
  3. REALTIME WEB REALTIME WEB No more pure request-response web Lot

    of tools (nice!) Lots of complexity (booo!) Why not doing it the Django-way?
  4. SO, CHANNELS SO, CHANNELS Framework to use Django in non-HTTP

    world websockets any protocol (bots, IoT, ...)
  5. SO, CHANNELS SO, CHANNELS CHANNELS VERSIONS CHANNELS VERSIONS Channels 2

    is a few weeks old Channels 1 served for almost three years Will focus on 2 (2.1, really) (Check previous version of this talk)
  6. CONCEPTS CONCEPTS PROTOCOL SERVER PROTOCOL SERVER Implements the ASGI specs

    for a specific protocol Bridge between the network and the application (e.g.: Daphne for HTTP/websockets)
  7. CHANNELS 1 → 2 CHANNELS 1 → 2 HIGHLIGHTS HIGHLIGHTS

    Fully exposed async Based on asyncio Mix of sync / async code Single process In-process protocol termination No more separate worker required Middlewares ASGI-all-the-way-down
  8. CHANNELS 1 → 2 CHANNELS 1 → 2 UPGRADING UPGRADING

    Major changes Channel-as-IPC only Stateful consumers Custom events architecture
  9. CHANNELS 1 → 2 CHANNELS 1 → 2 UPGRADING UPGRADING

    Rewrite needed Routing Consumers (partial) Tests Check channels1 vs channels2 branches
  10. DIVE IN DIVE IN CHANNEL LAYERS CHANNEL LAYERS CHANNEL_LAYERS =

    { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { 'hosts': [('localhost', 6379)], }, }, }
  11. DIVE IN DIVE IN ROUTING ROUTING # equivalent to my_project.urls

    # tipically used to include application routing application = ProtocolTypeRouter({ 'websocket': AuthMiddlewareStack( URLRouter([ path('status/', documents_routing), ]) ), })
  12. ROUTING ROUTING ROUTER TYPES ROUTER TYPES # routing is established

    on connection start application = ProtocolTypeRouter({ 'websocket': AuthMiddlewareStack( URLRouter([ path('status/', documents_routing), ]) ), }) Middlewares Routers A routing, is forever
  13. ROUTING ROUTING APPLICATION ROUTING APPLICATION ROUTING channel_routing = URLRouter([ path('users/',

    UserCounterConsumer), path('documents/', DocumentListConsumer), path('document/<str:slug>/<str:phase>/', DocumentDetailConsum ])
  14. CONSUMERS CONSUMERS GENERIC WEBSOCKET GENERIC WEBSOCKET class SomeConsumer(WebsocketConsumer): """ Generic

    Websocket consumer """ groups = ['some_group', 'other'] def connect(self): ... def disconnect(self, code): ... def receive(self, text_data=None, bytes_data=None): ...
  15. CONSUMERS CONSUMERS APPLICATION EVENTS APPLICATION EVENTS class UserCounterConsumer(JsonWebsocketConsumer): def connect(self):

    # this put a message in group users of the channel layer # type is the event to be generated async_to_sync(self.channel_layer.group_send)( 'users', {'type': 'users.count', 'value': 5} ) def users_count(self, event): # this catches the 'users.count' event / message # and send it to its connected client # it runs on all instances connected to the 'users' group self.send_json(content=event['value'])
  16. CONSUMERS CONSUMERS COUNTING USERS COUNTING USERS class UserCounterConsumer(JsonWebsocketConsumer): groups =

    'users', def connect(self): """ Increment users on connect and notify other consumers super().connect() if self.scope['user'].is_authenticated: increment_users(message.user) msg = {'users': count_users(), 'type': 'users.count'} async_to_sync(self.channel_layer.group_send)('users', msg def users_count(self, event): """ Notify connected user """ self.send_json(content=event['message']) def disconnect(self, code): """ Decrement users on disconnect and notify users """ ...
  17. COUNTING USERS COUNTING USERS FRONTEND FRONTEND if (users_count_path) { //

    Library is instantiated and connected to the endpoint const users_count = new channels.WebSocketBridge(); users_count.connect(users_count_path); users_count.listen((data) => { if (data.users) { document.getElementById( 'users-counter' ).textContent = data.users; } }); }
  18. CONCURRENCY MONITOR CONCURRENCY MONITOR SINGLE DOCUMENT SINGLE DOCUMENT class DocumentDetailConsumer(DocumentListConsumer):

    @property def groups(self): return [self.slug, Document.Status.list] def connect(self): self._update_document_count( self.scope['user'].get_full_name() ) super(DocumentDetailConsumer, self).connect() async_to_sync(self.channel_layer.group_send)( self.slug, { 'type': 'document.status', 'message': self.get_status() }) def document_status(self, event): self.send_json(content=event['message'])
  19. CONSUMER CONSUMER DETAIL #2 DETAIL #2 def connect(): ... async_to_sync(...)(

    self.slug, { 'type': 'document.status', 'message': self.get_status() }) def document_status(self, event): self.send_json(content=event['message'])
  20. CONCURRENCY MONITOR CONCURRENCY MONITOR DOCUMENTS LIST DOCUMENTS LIST class DocumentListConsumer(JsonWebsocketConsumer):

    @property def groups(self): return Document.Status.list, def connect(self): super(DocumentListConsumer, self).connect() async_to_sync(self.channel_layer.group_send)( self.slug, { 'type': 'document.status', 'message': self.get_status_packet() }) def document_status(self, event): self.send_json(content=event['message'])
  21. CONCURRENCY MONITOR CONCURRENCY MONITOR FRONTEND FRONTEND // Library is instantiated

    and connected to the endpoint const document_status = new channels.WebSocketBridge(); document_status.connect(document_path); // whenever a message is received, the documents badges is upda document_status.listen((data) => { if (data.document) { // we only have one document - detail view update_document_detail(data); } else { // all documents - list view update_document_list(data); } });
  22. CHANNELS FROM OUTSIDE CHANNELS FROM OUTSIDE CHANNELS CHANNELS class Document(TimeStampedModel):

    ... def save(self, *args, **kwargs): channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)(self.slug, { 'type': 'document.saved', 'message': 'document saved' }) return super(Document, self).save(*args, **kwargs) class DocumentDetailConsumer(DocumentListConsumer): ... def document_saved(self, event): do_something(event)