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

PyConTW 2018 Easy way to build a real time and ...

Jason
June 02, 2018
550

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

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. nginx Django Web uWSGI, Gunicorn WSGI (WSGI Server) (Proxy Server)

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

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

    ䷱磪ᛔ癲 socket牐襑ᥝֵአ獨Ոጱ socket 蟴ݳ Django ಍胼ྋଉ螀ᤈ牐 socket 磪盄ग़牧 ֕ฎਙ㮉஠殾螞盌Ӟ㮆憒塅 WSGIҁWeb ֑๐瑊樑 螇瑊Օᶎ҂蝡㮆ಅ磪 socket ᮷螞ਝጱ憒塅疰ฎ WSGI牐
  5. 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.
  6. 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
  7. # 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'
  8. # 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()
  9. # 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__
  10. 2011 ଙ WebSockets ᤩ IETF ਧ傶秂伛 RFC 6455 RFC7936 愆獅憒塅

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

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

    engines with WebSocket servers: gevent, asyncio - Support WSGI Server: uWSGI
  13. 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
  14. 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
  15. 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.
  16. Asyncio Tulip project Released as asyncio standard library module in

    Python 3.4 Protocol Coroutine scheduler Guido van Rossumҁইຎమℂ毣ԧ薹ݢ犥፡蝡缰 2013 keynote ᄍ 拻҂
  17. # 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
  18. Asynchronous Threads Callbacks / Promise gevent / eventlet / stackless

    generators with ‘yield from’ async / await
  19. 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/
  20. 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/
  21. 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/
  22. 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
  23. Event-loop Javascript’s Event-loop sample ֦狶অ盅ݞ౯ (by TP) A 咳ኞ, 䁆ᤈ

    B https://pjchender.blogspot.com/2017/08/javascript-learn-event-loop-stack-queue.html
  24. 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
  25. selector = DefaultSelector() c_n_jobs = 0 class Future: def __init__(self):

    self.callbacks = None def resolve(self): self.callbacks() def __await__(self): yield self
  26. 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
  27. 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
  28. async/await • Readability • Better than callbacks or Promises; •

    Easier to reason about that with threads or gevent code; • Promotes better patterns: message passing.
  29. Python history • 2000, Python 1.5 Threading • 2002, Twisted

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

    Python 3.x 2014, Python 3.4 asyncio 2015, Python 3.5 async/await
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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)
  36. But… • Too many moving pieces • No asyncio support

    • Easy to shoot yourself in the foot —Andrew Godwin PyConUS 2018
  37. 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)
  38. 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)
  39. Daphne and ASGI Daphne — the HTTP and WebSocket termination

    server ASGI (Asynchronous Server Gateway Interface) —asgiref
  40. 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/
  41. 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
  42. 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
  43. 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()
  44. 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
  45. 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/
  46. 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
  47. 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!!
  48. 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())
  49. 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
  50. Channels layer Allow you to talk between different instances of

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