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

Артём Малышев (Positive Technologies) - Django Channels - ответ современному вебу

Артём Малышев (Positive Technologies) - Django Channels - ответ современному вебу

Доклад с Moscow Python Conf 2016 (http://conf.python.ru)
Видео: https://conf.python.ru/django-channels-otvet-sovremennomu-vebu/

Я senior python developer в компании Positive Technologies. Последние два года занимаюсь разработкой распределённых систем. Спикер PyCon Russia, MoscowPython и Rannts Meetup. В докладе я расскажу о назначение и устройстве Django Channels. Давно прошли времена, когда все сайты работали в режиме "запрос-ответ". Пользователи хотят интерактивности и отзывчивости, программисты хотят websocket'ы, HTTP/2 стремительно набирает популярность. Классические WSGI фреймворки перестают соответствовать реалиям жизни. Вы узнаете каким образом в Django реализована поддержка асинхронных протоколов, решение каких задач можно отдать этой технологии, а где лучше использовать tornado или aiohttp.

Moscow Python Meetup
PRO

October 12, 2016
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. 1
    DJANGO CHANNELS
    ARTEM MALYSHEV
    @PROOFIT404

    View Slide

  2. 2
    BRING DJANGO
    TO THE ASYNC
    NETWORKING

    View Slide

  3. 3
    BUT WE ARE ALREADY THERE
    twisted
    eventlet
    gevent
    tornado
    asyncio

    View Slide

  4. 4
    AND DJANGO
    IS SYNC IN
    ITS CORE

    View Slide

  5. 5
    WSGI
    def app(environ, callback):
    status, headers = '200 OK', []
    callback(status, headers)
    return ['Hello world!\n']

    View Slide

  6. 6

    View Slide

  7. 7
    OK, BUT IT IS
    2016
    DUDE

    View Slide

  8. 8
    PROGRESSIVE WEB APPS
    interactive communication
    responsive design
    push notifications
    works offline

    View Slide

  9. 9

    View Slide

  10. 10
    IT'S ALL DIFFERENT
    PERSISTENT
    STATEFUL
    CONNECTIONS

    View Slide

  11. 11

    View Slide

  12. 12

    View Slide

  13. 13

    View Slide

  14. 14

    View Slide

  15. 15
    ASGI

    View Slide

  16. 16
    ASGI IS
    a standard interface between
    network protocol servers
    and Python applications

    View Slide

  17. 17
    CHANNEL

    View Slide

  18. 18
    CHANNEL IS
    an ordered,
    first-in first-out queue
    with message expiry
    and at-most-once delivery
    to only one listener at a time

    View Slide

  19. 19
    CONSUMERS

    View Slide

  20. 20
    ACCEPT WEBSOCKET MESSAGE
    def ws_message(message):
    message.reply_channel.send({
    'text': message.content['text'],
    })

    View Slide

  21. 21
    ROUTING
    from channels.routing import route
    from myapp.consumers import ws_message
    channel_routing = [
    route('websocket.receive', ws_message),
    ]

    View Slide

  22. 22
    SETTINGS
    CHANNEL_LAYERS = {
    'default': {
    'BACKEND': 'asgiref.inmemory',
    'ROUTING': 'myproject.routing',
    },
    }

    View Slide

  23. 23
    ASGI APP
    import os
    from channels.asgi import get_channel_layer
    os.environ.setdefault(
    'DJANGO_SETTINGS_MODULE',
    'myproject.settings',
    )
    channel_layer = get_channel_layer()

    View Slide

  24. 24
    DEPLOY
    $ gunicorn myproject.wsgi
    $ daphne myproject.asgi:channel_layer
    $ django-admin runworker

    View Slide

  25. 25
    REPLY CHANNEL

    View Slide

  26. 26

    View Slide

  27. 27
    GROUPS

    View Slide

  28. 28
    CONSUMERS
    from channels import Group
    def ws_connect(message):
    Group('chat').add(message.reply_channel)
    def ws_disconnect(message):
    Group('chat').discard(message.reply_channel)
    def ws_message(message):
    Group('chat').send({
    'text': message.content['text'],
    })

    View Slide

  29. 29
    ROUTING
    from channels.routing import route
    from myapp.consumers import *
    channel_routing = [
    route('websocket.connect', ws_connect),
    route('websocket.disconnect', ws_disconnect),
    route('websocket.receive', ws_message),
    ]

    View Slide

  30. 30
    GENERIC CONSUMERS

    View Slide

  31. 31
    BASE CONSUMER
    from channels.generic import BaseConsumer
    class MyConsumer(BaseConsumer):
    method_mapping = {
    'channel.name.here': 'method_name',
    }
    def method_name(self, message, **kwargs):
    pass

    View Slide

  32. 32
    WEBSOCKET CONSUMER
    from channels.generic import WebsocketConsumer
    class MyConsumer(WebsocketConsumer):
    def connection_groups(self):
    return ['chat']
    def connect(self, message):
    pass
    def receive(self, text=None, bytes=None):
    self.send(text=text, bytes=bytes)

    View Slide

  33. 33
    ROUTING
    from channels import route_class
    from myapp import consumers
    channel_routing = [
    route_class(consumers.MyConsumer),
    ]

    View Slide

  34. 34
    ROUTING

    View Slide

  35. 35
    FILTERS
    // app.js
    s = new WebSocket('ws://localhost:8000/chat/')
    # routing.py
    route('websocket.connect', ws_connect,
    path=r'^/chat/$')

    View Slide

  36. 36
    INCLUDES
    from channels import route, include
    blog_routes = [
    route('websocket.connect', blog,
    path=r'^/stream/'),
    ]
    routing = [
    include(blog_routes, path=r'^/blog'),
    ]

    View Slide

  37. 37
    MULTIPLEXING

    View Slide

  38. 38
    DEFINE MAPPING
    from channels import WebsocketDemultiplexer
    class Demultiplexer(WebsocketDemultiplexer):
    mapping = {
    'intval': 'binding.intval',
    }

    View Slide

  39. 39
    ROUTING
    from channels import route_class, route
    from .consumers import Demultiplexer, ws_message
    channel_routing = [
    route_class(Demultiplexer, path='^/binding/'),
    route('binding.intval', ws_message),
    ]

    View Slide

  40. 40
    MESSAGE CONTENT
    {
    "stream": "intval",
    "payload": {
    ...
    }
    }

    View Slide

  41. 41
    SESSIONS

    View Slide

  42. 42
    CHANNEL SESSION
    from channels.sessions import channel_session
    @channel_session
    def ws_connect(message):
    room = message.content['path']
    message.channel_session['room'] = room
    Group('chat-%s' % room).add(
    message.reply_channel
    )

    View Slide

  43. 43
    HTTP SESSION
    from channels.sessions import http_session_user
    @http_session_user
    def ws_connect(message):
    message.http_session['room'] = room
    if message.user.username:
    ...

    View Slide

  44. 44
    MESSAGE ORDER

    View Slide

  45. 45
    ENFORCE ORDERING
    from channels.generic import WebsocketConsumer
    class MyConsumer(WebsocketConsumer):
    http_user = True
    slight_ordering = True
    strict_ordering = False
    def connection_groups(self, **kwargs):
    return ['chat']

    View Slide

  46. 46
    DATA BINDING

    View Slide

  47. 47
    BIND MODEL
    class IntegerValueBinding(WebsocketBinding):
    model = IntegerValue
    stream = 'intval'
    fields = ['name', 'value']
    def group_names(self, instance, action):
    return ['intval-updates']
    def has_permission(self, user, action, pk):
    return True

    View Slide

  48. 48
    REDIS LAYER

    View Slide

  49. 49
    REDIS LAYER
    SINC ON WORKERS
    ASYNC ON DAPHNE
    MSGPACK
    SHARDING

    View Slide

  50. 50
    CHANNEL IMPLEMENTATION
    >> SET "b6dc0dfce" "\x81\xa4text\xachello"
    >> RPUSH "websocket.send!sGOpfny" "b6dc0dfce"
    >> EXPIRE "b6dc0dfce" "60"
    >> EXPIRE "websocket.send!sGOpfny" "61"

    View Slide

  51. 51
    GROUP IMPLEMENTATION
    >> type group:chat
    zset
    >> ZRANGE group:chat 0 1 WITHSCORES
    1) "websocket.send!sGOpfny"
    2) "1476199781.8159261"

    View Slide

  52. 52
    PROBLEMS

    View Slide

  53. 53
    CALLBACK
    HELL

    View Slide

  54. 54
    CELERY

    View Slide

  55. 55
    CHANNELS
    IS NOT A
    TASK QUEUE

    View Slide

  56. 56
    CHANNELS CAN'T
    RETRY
    CANVAS
    ETA

    View Slide

  57. 57
    DJANGO
    FOR TODAY'S WEB

    View Slide

  58. 58
    QUESTIONS?

    View Slide