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

PyConTW 2018 Easy way to build a real time and asynchronous web or app with Django Channels

Eafba9ded7e4beb39ae653a9a36ffc43?s=47 Jason
June 02, 2018
420

PyConTW 2018 Easy way to build a real time and asynchronous web or app with Django Channels

Eafba9ded7e4beb39ae653a9a36ffc43?s=128

Jason

June 02, 2018
Tweet

Transcript

  1. Easy way to build a real time and asynchronous web

    or app with Django Channels PyConTW 2018
  2. Sample Code https://github.com/chairco/2018_PyConTW_Talk

  3. Jason @ChaircoChen

  4. None
  5. None
  6. None
  7. None
  8. None
  9. None
  10. This talk will around “3” topics WebSockets Asynchronous channels Final,

    we will talk about Django Channels
  11. Tradition polling and Real-time web, How is working? - WSGI

    and HTTP - WebSockets and HTTP2
  12. Tradition web — Polling

  13. None
  14. nginx Django Web uWSGI, Gunicorn WSGI (WSGI Server) (Proxy Server)

    (Your Code) Python’s WSGI (Web Server Gateway Interface) —PEP 3333
  15. nginx դቘ֑๐瑊: 揗揣覌眲虻რ咳蝑ҁjs, css, media҂牧㵕眲藶穩 旉咳膏奾ຎࢧ薟牐 uWSGI, WSGI Server: 矑ݑ

    nginx 藶穩旉咳㪔蒂ቘ盅咳妔 Django App 犥现矑硩 Django 䛑አࢧ㯽懱௳旉妔 nginx牐 Django App, 䛑አ纷ୗ: 硩ک藶穩盅蒂ቘ碍硁㪔Ӭ蝱ᤈ჉礕䌘䛑殷ᶎ 妔 uWSGI ๐率瑊牐
  16. What’s WSGI︖ Web framework ጱ๜搡ฎӞ㮆 socket ๐率ᒒ矑硩አಁ藶穩牧ےૡ虻 碘盅ࢧ㯽妔ਮಁᒒҁDjango҂ ֺ֕ই Django

    ䷱磪ᛔ癲 socket牐襑ᥝֵአ獨Ոጱ socket 蟴ݳ Django ಍胼ྋଉ螀ᤈ牐 socket 磪盄ग़牧 ֕ฎਙ㮉஠殾螞盌Ӟ㮆憒塅 WSGIҁWeb ֑๐瑊樑 螇瑊Օᶎ҂蝡㮆ಅ磪 socket ᮷螞ਝጱ憒塅疰ฎ WSGI牐
  17. Client Server Request Response Process A request comes in, Django

    is fired up to serve it, generates a response to send, and then Django goes away and waits for the next request.
  18. Django + wsgiref working process 1. socket 矑ݑ client’s request

    狶 http request 薹ຉҁheader, bady҂ 2. wsgiref ಩薹ຉԏ盅藶穩ፘ橕懱௳旉㯽蝑妔 Django 3. Django ໛礍樄ত Middleware牏Routing牏view牏ORM牏template 磧 奰 return ਁԀ妔 socket 4. socket send (Django 叨ڊጱ string )牧ࢧ㯽 client
  19. https://brianmcdonnell.github.io/pycon_ie_2013/#/4

  20. # pycontw2018/wsgi.py import os from django.core.wsgi import get_wsgi_application os.environ.setdefault( "DJANGO_SETTINGS_MODULE",

    "pycontw2018.settings.local" ) application = get_wsgi_application() # settings/local.py, project: pycontw2018 WSGI_APPLICATION = 'pycontw2018.wsgi.application'
  21. # django/core/wsgi.py, return WSGIHandler each request import django from django.core.handlers.wsgi

    import WSGIHandler def get_wsgi_application(): django.setup(set_prefix=False) return WSGIHandler()
  22. # django/django/core/handlers/wsgi.py class WSGIHandler(base.BaseHandler): request_class = WSGIRequest def __init__(self, *args,

    **kwargs): super().__init__(*args, **kwargs) self.load_middleware() def __call__(self, environ, start_response): set_script_prefix(get_script_name(environ)) signals.request_started.send(sender=self.__class__, environ=environ) request = self.request_class(environ) response = self.get_response(request) response._handler_class = self.__class__
  23. https://www.reddit.com/r/django/comments/2vjyrc/djangos_requestresponse_cycle_a_visual_guide/

  24. Client Server Request Response Process

  25. Long polling via AJAX ( or Comet) https://www.fullstackpython.com/websockets.html

  26. 總之, WSGI 幫我們省掉很多麻煩 但...

  27. WSGI 螀֢ฎݶྍጱ Single thread, 蝚螂 uWSGI or Gunicorn ୌ缏ๅग़ Threads

  28. Modern web — WebSockets

  29. WebSockets https://www.fullstackpython.com/websockets.html

  30. 2011 ଙ WebSockets ᤩ IETF ਧ傶秂伛 RFC 6455 RFC7936 愆獅憒塅

    2014 ଙጱ HTML 5 ਧ嬝ԧ Websockets 㶧ਧ TCP 磪ԧ獊櫕ૡҁFull-duplex҂
  31. Client Server Connect Data Process牫 Data Data ֵአᘏ膏֑๐瑊蝱ᤈԧᒫӞ稞౮ۑԻൎ(Handshake)牧 疰ฎ client

    咳ڊӞ㮆 SYN 矑茐֑๐瑊咳ࢧ ACK 矑茐疰胼ୌ缏ܨ碻ጱ虻碘㯽蜍
  32. None
  33. Problem from different angles - Frameworks/web: Twisted, Tornado - Async

    engines with WebSocket servers: gevent, asyncio - Support WSGI Server: uWSGI
  34. Web Framework / Web servers

  35. Twisted Network engine framework -Glapy Support async web server InlineCallbacks:

    Reactor Futures: Deferred Twisted is the mother of async in Python (by Yury Selivanov) https://twistedmatrix.com/documents/8.2.0/core/howto/async.html
  36. Tornado Nice in Python 2.x Futures: Future http://www.tornadoweb.org/en/stable/guide/async.html

  37. Async Engine at network

  38. gevent A unofficial async library, release from Python 2.x libev

    (Event-loop, Callback), greenlet (micro-thread, Coroutine) Monkey-patching (socket牏ssl牏threading牏select) greenlet not support Jython and IronPython Why not gevent — Glyph
  39. Asyncio • It’s infrastructure for writing single-threaded concurrent code using

    coroutines, multiplexing I/O access over sockets and other resources, running network clients and servers, and other related primitives.
  40. Asyncio Tulip project Released as asyncio standard library module in

    Python 3.4 Protocol Coroutine scheduler Guido van Rossumҁইຎమℂ毣ԧ薹ݢ犥፡蝡缰 2013 keynote ᄍ 拻҂
  41. Asyncio • Python 3.5, new syntax: “async”, “await” replace “asyncio.coroutine”,

    “yield from”, so Coroutine not just a Generators.
  42. WSGI Server

  43. uWSGI c 承᥺䋿֢ ඪൔ cgi牏fastcgi牏socket uwsgi protocol դ磦 http protocol

    狶玱ݻդቘ
  44. gunicorn Implement from Ruby Unicorn Python 䋿֢ 蟂ᗟࣁ Heroku 妿ଉ䨝አک

  45. • Synchronous • Asynchronous - Non-blocking I/O - Callback -

    Event loop - Coroutine
  46. Synchronous Not Efficiency, ᴥलୗ㻌Ӟᤈ纷ҁ盌ଧጱӞ㮆Ӟ㮆蒂ቘ, slow҂ c10K(c100K) Problem

  47. # sync def get(path): s = socket.socket() s.connect(('localhost', 5000)) request

    = 'GET %s HTTP/1.0\r\n\r\n' % path s.send(request.encode()) chunks = [] while True: chunk = s.recv(1000) if chunk: chunks.append(chunk) else: body = (b''.join(chunks)).decode() print(body.split('\n')[0]) return
  48. Asynchronous Threads Callbacks / Promise gevent / eventlet / stackless

    generators with ‘yield from’ async / await
  49. Asynchronous • ᴥलୗग़ᤈ纷 (ྯ㮆藶穩樄Ӟ㮆 process, ֕᩼㬵᩼ग़ cpu context-switch 疰᩼ṛ) •

    ᴥलୗԪग़ᤈ纷ग़䁆ᤈ姼 (樄ग़㮆ᤈ纷膏䁆ᤈ姼牧蝨౮ dead lock, race condition, 疪ٌ蒂ቘوݶ䩚ᥜ碻蝡犚㺔氂疰䨝ๅ瑥᯿牧犖犋অ䌃) • 覍ᴥलୗԪկ詴㵕 (㻌Ӟ蝅瑹䲒礚磪篷翕᪠ IO Ԫկ咳ኞ牧፜݄ԧ context-switch, process copy 缛牧砰䌃蕦褾牧ܻࢩฎӞկԪ眐ᤩ藉咳牧磪Ԫ眐䷱螭蒂ቘ疰ض懿 袅牧ӥ稞藉咳ٚ㬵礬硁ض獮制眲㬵蒂ቘ) ex: Twisted • 覍ᴥलୗ Coroutine (猟ฎᴥलୗӞ䰬ፗ薪牧䟖㻌Ӟ蝅瑹䲒礚Ԫկ牧֕ےӤ Coroutine) ex: gevent, asyncio http://blog.ez2learn.com/2010/07/17/talk-about-coroutine-and-gevent/
  50. – Unyielding Glyph, 2014 Problem:「Threads Are Bad」

  51. Asynchronous • ᴥलୗग़ᤈ纷 (ྯ㮆藶穩樄Ӟ㮆 process, ֕᩼㬵᩼ग़ cpu context-switch 疰᩼ṛ) •

    ᴥलୗԪग़ᤈ纷ग़䁆ᤈ姼 (樄ग़㮆ᤈ纷膏䁆ᤈ姼牧蝨౮ dead lock, race condition, 疪ٌ蒂ቘوݶ䩚ᥜ碻蝡犚㺔氂疰䨝ๅ瑥᯿牧犖犋অ䌃) • 覍ᴥलୗԪկ詴㵕 (㻌Ӟ蝅瑹䲒礚磪篷翕᪠ IO Ԫկ咳ኞ牧፜݄ԧ context-switch, process copy 缛牧砰䌃蕦褾牧ܻࢩฎӞկԪ眐ᤩ藉咳牧磪Ԫ眐䷱螭蒂ቘ疰ض懿 袅牧ӥ稞藉咳ٚ㬵礬硁ض獮制眲㬵蒂ቘ) ex: Twisted • 覍ᴥलୗ Coroutine (猟ฎᴥलୗӞ䰬ፗ薪牧䟖㻌Ӟ蝅瑹䲒礚Ԫկ牧֕ےӤ Coroutine) ex: gevent, asyncio http://blog.ez2learn.com/2010/07/17/talk-about-coroutine-and-gevent/
  52. Non-blocking • Event-driven • Socket Multiplexing • UNIX: select, poll,

    epoll. Windows, Solaris: IOCP. BSD/OSX: kqueue
  53. from selectors import DefaultSelector, EVENT_WRITE, EVENT_READ selector = DefaultSelector() def

    get_non_blocking(path): s = socket.socket() s.setblocking(False) try: s.connect(('localhost', 5000)) except BlockingIOError: pass Non-blocking https://emptysqua.re/blog/links-for-how-python-coroutines-work/
  54. Callback Future/Promise yield, yield from async/await

  55. def get_callback(path): ... callback = lambda: connected(s, path) ... callback()

    def connected(s, path): ... callback = lambda: readable(s, chunks) ... callback() def readable(s, chunks): ... if chunk: ... callback = lambda: readable(s, chunks) ... callback() else: ... return
  56. Guido van Rossum 對 Callback 的⼀些意⾒ https://www.youtube.com/watch?v=1coLC-MUCJc&index=8&t=1450s&list=PL8dd-lO2RqiCFY0gTpDTCF_eIdHUz6qgK

  57. Callback Hell, Pyramid of Doom

  58. Event-loop Javascript’s Event-loop sample ֦狶অ盅ݞ౯ (by TP) A 咳ኞ, 䁆ᤈ

    B https://pjchender.blogspot.com/2017/08/javascript-learn-event-loop-stack-queue.html
  59. n_jobs = 0 def get_eventloop(path): global n_jobs n_jobs += 1

    ... callback = lambda: connected_event(s, path) # closure selector.register(s.fileno(), EVENT_WRITE, data=callback) def connected_event(s, path): ... callback = lambda: readable_event(s, chunks) def readable_event(s, chunks): global n_jobs ... if chunk: ... else: n_jobs -= 1
  60. get_eventloop('/foo') get_eventloop('/bar') while n_jobs: events = selector.select() for key, mask

    in events: cb = key.data cb()
  61. Coroutine Future Generator Task

  62. selector = DefaultSelector() c_n_jobs = 0 class Future: def __init__(self):

    self.callbacks = None def resolve(self): self.callbacks() def __await__(self): yield self
  63. class Task: def __init__(self, coro): self.coro = coro self.step() def

    step(self): try: f = self.coro.send(None) except StopIteration: return f.callbacks = self.step
  64. async def get_coroutines(path): global c_n_jobs c_n_jobs += 1 ... f

    = Future() selector.register(s.fileno(), EVENT_WRITE, data=f) await f selector.unregister(s.fileno()) request = 'GET %s HTTP/1.0\r\n\r\n' % path s.send(request.encode()) chunks = [] while True: f = Future() selector.register(s.fileno(), EVENT_READ, data=f) await f selector.unregister(s.fileno()) chunk = s.recv(1000) if chunk: chunks.append(chunk) else: break body = (b''.join(chunks)).decode() print(body.split('\n')[0]) c_n_jobs -= 1
  65. Task(get_coroutines('/foo')) Task(get_coroutines('/bar')) while c_n_jobs: events = selector.select() for key, mask

    in events: fut = key.data fut.resolve()
  66. async/await • Readability • Better than callbacks or Promises; •

    Easier to reason about that with threads or gevent code; • Promotes better patterns: message passing.
  67. Django Channels

  68. None
  69. Python history • 2000, Python 1.5 Threading • 2002, Twisted

    1.0 • 2003, WSGI (PEP333) • 2008, Multiprocessing • 2008, Django 1.0
  70. Python history 2009, Eventlet / gevent 2010, WSGI (PEP3333)— Support

    Python 3.x 2014, Python 3.4 asyncio 2015, Python 3.5 async/await
  71. Django Channels and how to build a web with those

    Channels 1.x v.s Channels 2.x Daphne ASGI (Asynchronous Server Gateway Interface) Channels layer
  72. Channels 0.1 Adds async protocol support to Django Headline protocol

    is WebSockets Also allows background jobs
  73. Channels 1.x Python 2.7 compatible Twisted for Webserver Synchronous Django

    code (Can’t write async code too)
  74. Channels 1.x Channel - FIFO queue with send and receive_many

    operations, named with a string Group - Named set of channels with add/remove/send operations Messages - Representations for HTTP and WebSocket session https://speakerdeck.com/mosky/elegant-concurrency
  75. Channels 1.x API specification for channel layer backends Message formats

    for HTTP and WebSocket https://channels-docs-zhtw.readthedocs.io/en/1.1.5/asgi.html
  76. Channels 1.x Redis - Reference network layer POSIX IPC -

    For single-machine installs In-memory For testing or single-process installs All are network-transparent
  77. None
  78. https://speakerdeck.com/andrewgodwin/taking-django-async Clients WebServer (Twisted) Channel Layer (Redis or Other) Django

    (Sync worker Process)
  79. https://speakerdeck.com/andrewgodwin/taking-django-async Clients WebServer (Twisted) Channel Layer (Redis or Other) Django

    (Sync worker Process) WebServer (Twisted) Django (Sync worker Process) WebServer (Twisted) Django (Sync worker Process)
  80. But… • Too many moving pieces • No asyncio support

    • Easy to shoot yourself in the foot —Andrew Godwin PyConUS 2018
  81. Channels 2.x Asyncio-native Python 3.5 and up Supports async coroutines

    and sync threads (asyncio + sync support)
  82. https://speakerdeck.com/andrewgodwin/taking-django-async Channels 1.x Clients WebServer (Twisted) Channel Layer (Redis or

    Other) Django (Sync worker Process)
  83. https://speakerdeck.com/andrewgodwin/taking-django-async Channels 2.x Clients Channel Layer (Redis or Other) WebServer

    (Asyncio-native) Django (coroutines/threads)
  84. https://speakerdeck.com/andrewgodwin/taking-django-async Channels 1.x Clients WebServer (Twisted) Channel Layer (Redis or

    Other) Django (Sync worker Process) WebServer (Twisted) Django (Sync worker Process) WebServer (Twisted) Django (Sync worker Process)
  85. https://speakerdeck.com/andrewgodwin/taking-django-async Channels 2.x Clients Channel Layer (Redis or Other) WebServer

    (Twisted) Django (Sync worker Process) WebServer (Twisted) Django (Sync worker Process) WebServer (Twisted) Django (Sync worker Process)
  86. Daphne and ASGI Daphne — the HTTP and WebSocket termination

    server ASGI (Asynchronous Server Gateway Interface) —asgiref
  87. Daphne Twisted, WebSockets

  88. ASGI “ASGI” = WSGI + “async”

  89. Running code in-process Perhaps the biggest change of all is

    that Channels 2 runs your handling code in process with the HTTP (or other) server, rather than having separate worker processes and dishing it out over the network. https://www.aeracode.org/2017/10/18/channels-2-october/
  90. ASGI Two different components: protocol server, application Applications are instantiated

    objects: events not function Unlike WSGI: connection Scope, Events https://github.com/chairco/2018_PyConTW_Talk/blob/master/docs/asgi-zh_TW.rst
  91. Protocol def application(environ, start_response): ... start_response(...) yield data https://channels.readthedocs.io/en/latest/introduction.html#scopes-and-events

  92. Protocol class Application: def __init__(self, scope): ... async def __call__(self,

    receive, send): event = await receive() ... await send(data) https://channels.readthedocs.io/en/latest/introduction.html#scopes-and-events
  93. Application class ChatConsumer(WebsocketConsumer): def connect(self): self.room_name = self.scope['url_route']['kwargs']['room_name'] self.room_group_name =

    'chats_%s' % self.room_name # Join room group async_to_sync(self.channel_layer.group_add)( self.room_group_name, self.channel_name ) self.accept()
  94. https://speakerdeck.com/andrewgodwin/taking-django-async ORM (database kerfuffling) Views (logic and presentation) Django Middleware

    (logic and presentation) URL Routing (path to view mapping) ASGI Handler (ASGI-to-request translator) ASGI Middleware (auth, sessions, etc.) Views (logic and presentation) ASGI Routing (path protocol/etc mapping) synchronous
  95. –Andrew Godwin 「Python & Async Simplified」

  96. Calling sync code from sync code. This is just a

    normal function call - like time.sleep(10). Nothing risky or special about this. Calling async code from async code. You have to use await here, so you would do await asyncio.sleep(10) Calling sync code from async code. You can do this, but as I said above, it will block the whole process and make things mysteriously slow, and you shouldn't. Instead, you need to give the sync code its own thread. Calling async code from sync code. Trying to even use await inside a synchronous function is a syntax error in Python, so to do this you need to make an event loop for the code to run inside. https://www.aeracode.org/2018/02/19/python-async-simplified/
  97. sync_to_async async_to_sync

  98. Synchronous code has run on threads Like calling the ORM,

    Rendering templates, Handing off to Django views loop = asyncio.get_event_loop() future = loop.run_in_executor( self.threadpool, func, ) return await future
  99. sync_to_async async_to_sync

  100. Async code run on the event loop def sync(pk): return

    await pycontw2018.objects.get(pk=pk).id async def async_func(): await sync Syntax error!!
  101. Async code run on the event loop loop = asyncio.new_event_loop()

    asyncio.set_event_loop(loop) result = loop.run_until_complete(async_to_sync())
  102. Async code run on the event loop Make a Future

    Jump to the main thread and add coroutine Tie the coroutine end to triggering the Future Block the thread on the Future
  103. sync_to_async = SyncToAsync async_to_sync = AsyncToSync https://github.com/django/asgiref/blob/master/asgiref/sync.py

  104. ؃ই螭犋ฎᮎ讕ԧ薹ݢ犥㷢ᘍ David Beazley ጱ live coding ୽粙 The Other Async

    (Threads + Async = ❤)
  105. https://github.com/encode/uvicorn

  106. Channels layer Allow you to talk between different instances of

    an application (communications system) https://speakerdeck.com/mosky/elegant-concurrency
  107. Thanks