Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Sample Code https://github.com/chairco/2018_PyConTW_Talk

Slide 3

Slide 3 text

Jason @ChaircoChen

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

This talk will around “3” topics WebSockets Asynchronous channels Final, we will talk about Django Channels

Slide 11

Slide 11 text

Tradition polling and Real-time web, How is working? - WSGI and HTTP - WebSockets and HTTP2

Slide 12

Slide 12 text

Tradition web — Polling

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

nginx Django Web uWSGI, Gunicorn WSGI (WSGI Server) (Proxy Server) (Your Code) Python’s WSGI (Web Server Gateway Interface) —PEP 3333

Slide 15

Slide 15 text

nginx դቘ֑๐瑊: 揗揣覌眲虻რ咳蝑ҁjs, css, media҂牧㵕眲藶穩 旉咳膏奾ຎࢧ薟牐 uWSGI, WSGI Server: 矑ݑ nginx 藶穩旉咳㪔蒂ቘ盅咳妔 Django App 犥现矑硩 Django 䛑አࢧ㯽懱௳旉妔 nginx牐 Django App, 䛑አ纷ୗ: 硩ک藶穩盅蒂ቘ碍硁㪔Ӭ蝱ᤈ჉礕䌘䛑殷ᶎ 妔 uWSGI ๐率瑊牐

Slide 16

Slide 16 text

What’s WSGI︖ Web framework ጱ๜搡ฎӞ㮆 socket ๐率ᒒ矑硩አಁ藶穩牧ےૡ虻 碘盅ࢧ㯽妔ਮಁᒒҁDjango҂ ֺ֕ই Django ䷱磪ᛔ癲 socket牐襑ᥝֵአ獨Ոጱ socket 蟴ݳ Django ಍胼ྋଉ螀ᤈ牐 socket 磪盄ग़牧 ֕ฎਙ㮉஠殾螞盌Ӟ㮆憒塅 WSGIҁWeb ֑๐瑊樑 螇瑊Օᶎ҂蝡㮆ಅ磪 socket ᮷螞ਝጱ憒塅疰ฎ WSGI牐

Slide 17

Slide 17 text

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.

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

https://brianmcdonnell.github.io/pycon_ie_2013/#/4

Slide 20

Slide 20 text

# 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'

Slide 21

Slide 21 text

# 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()

Slide 22

Slide 22 text

# 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__

Slide 23

Slide 23 text

https://www.reddit.com/r/django/comments/2vjyrc/djangos_requestresponse_cycle_a_visual_guide/

Slide 24

Slide 24 text

Client Server Request Response Process

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Modern web — WebSockets

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

2011 ଙ WebSockets ᤩ IETF ਧ傶秂伛 RFC 6455 RFC7936 愆獅憒塅 2014 ଙጱ HTML 5 ਧ嬝ԧ Websockets 㶧ਧ TCP 磪ԧ獊櫕ૡҁFull-duplex҂

Slide 31

Slide 31 text

Client Server Connect Data Process牫 Data Data ֵአᘏ膏֑๐瑊蝱ᤈԧᒫӞ稞౮ۑԻൎ(Handshake)牧 疰ฎ client 咳ڊӞ㮆 SYN 矑茐֑๐瑊咳ࢧ ACK 矑茐疰胼ୌ缏ܨ碻ጱ虻碘㯽蜍

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

Problem from different angles - Frameworks/web: Twisted, Tornado - Async engines with WebSocket servers: gevent, asyncio - Support WSGI Server: uWSGI

Slide 34

Slide 34 text

Web Framework / Web servers

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Tornado Nice in Python 2.x Futures: Future http://www.tornadoweb.org/en/stable/guide/async.html

Slide 37

Slide 37 text

Async Engine at network

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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.

Slide 40

Slide 40 text

Asyncio Tulip project Released as asyncio standard library module in Python 3.4 Protocol Coroutine scheduler Guido van Rossumҁইຎమℂ毣ԧ薹ݢ犥፡蝡缰 2013 keynote ᄍ 拻҂

Slide 41

Slide 41 text

Asyncio • Python 3.5, new syntax: “async”, “await” replace “asyncio.coroutine”, “yield from”, so Coroutine not just a Generators.

Slide 42

Slide 42 text

WSGI Server

Slide 43

Slide 43 text

uWSGI c 承᥺䋿֢ ඪൔ cgi牏fastcgi牏socket uwsgi protocol դ磦 http protocol 狶玱ݻդቘ

Slide 44

Slide 44 text

gunicorn Implement from Ruby Unicorn Python 䋿֢ 蟂ᗟࣁ Heroku 妿ଉ䨝አک

Slide 45

Slide 45 text

• Synchronous • Asynchronous - Non-blocking I/O - Callback - Event loop - Coroutine

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

# 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

Slide 48

Slide 48 text

Asynchronous Threads Callbacks / Promise gevent / eventlet / stackless generators with ‘yield from’ async / await

Slide 49

Slide 49 text

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/

Slide 50

Slide 50 text

– Unyielding Glyph, 2014 Problem:「Threads Are Bad」

Slide 51

Slide 51 text

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/

Slide 52

Slide 52 text

Non-blocking • Event-driven • Socket Multiplexing • UNIX: select, poll, epoll. Windows, Solaris: IOCP. BSD/OSX: kqueue

Slide 53

Slide 53 text

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/

Slide 54

Slide 54 text

Callback Future/Promise yield, yield from async/await

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Guido van Rossum 對 Callback 的⼀些意⾒ https://www.youtube.com/watch?v=1coLC-MUCJc&index=8&t=1450s&list=PL8dd-lO2RqiCFY0gTpDTCF_eIdHUz6qgK

Slide 57

Slide 57 text

Callback Hell, Pyramid of Doom

Slide 58

Slide 58 text

Event-loop Javascript’s Event-loop sample ֦狶অ盅ݞ౯ (by TP) A 咳ኞ, 䁆ᤈ B https://pjchender.blogspot.com/2017/08/javascript-learn-event-loop-stack-queue.html

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

get_eventloop('/foo') get_eventloop('/bar') while n_jobs: events = selector.select() for key, mask in events: cb = key.data cb()

Slide 61

Slide 61 text

Coroutine Future Generator Task

Slide 62

Slide 62 text

selector = DefaultSelector() c_n_jobs = 0 class Future: def __init__(self): self.callbacks = None def resolve(self): self.callbacks() def __await__(self): yield self

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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()

Slide 66

Slide 66 text

async/await • Readability • Better than callbacks or Promises; • Easier to reason about that with threads or gevent code; • Promotes better patterns: message passing.

Slide 67

Slide 67 text

Django Channels

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

Python history • 2000, Python 1.5 Threading • 2002, Twisted 1.0 • 2003, WSGI (PEP333) • 2008, Multiprocessing • 2008, Django 1.0

Slide 70

Slide 70 text

Python history 2009, Eventlet / gevent 2010, WSGI (PEP3333)— Support Python 3.x 2014, Python 3.4 asyncio 2015, Python 3.5 async/await

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

Channels 0.1 Adds async protocol support to Django Headline protocol is WebSockets Also allows background jobs

Slide 73

Slide 73 text

Channels 1.x Python 2.7 compatible Twisted for Webserver Synchronous Django code (Can’t write async code too)

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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)

Slide 80

Slide 80 text

But… • Too many moving pieces • No asyncio support • Easy to shoot yourself in the foot —Andrew Godwin PyConUS 2018

Slide 81

Slide 81 text

Channels 2.x Asyncio-native Python 3.5 and up Supports async coroutines and sync threads (asyncio + sync support)

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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)

Slide 85

Slide 85 text

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)

Slide 86

Slide 86 text

Daphne and ASGI Daphne — the HTTP and WebSocket termination server ASGI (Asynchronous Server Gateway Interface) —asgiref

Slide 87

Slide 87 text

Daphne Twisted, WebSockets

Slide 88

Slide 88 text

ASGI “ASGI” = WSGI + “async”

Slide 89

Slide 89 text

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/

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

Protocol def application(environ, start_response): ... start_response(...) yield data https://channels.readthedocs.io/en/latest/introduction.html#scopes-and-events

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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()

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

–Andrew Godwin 「Python & Async Simplified」

Slide 96

Slide 96 text

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/

Slide 97

Slide 97 text

sync_to_async async_to_sync

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

sync_to_async async_to_sync

Slide 100

Slide 100 text

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!!

Slide 101

Slide 101 text

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())

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

sync_to_async = SyncToAsync async_to_sync = AsyncToSync https://github.com/django/asgiref/blob/master/asgiref/sync.py

Slide 104

Slide 104 text

؃ই螭犋ฎᮎ讕ԧ薹ݢ犥㷢ᘍ David Beazley ጱ live coding ୽粙 The Other Async (Threads + Async = ❤)

Slide 105

Slide 105 text

https://github.com/encode/uvicorn

Slide 106

Slide 106 text

Channels layer Allow you to talk between different instances of an application (communications system) https://speakerdeck.com/mosky/elegant-concurrency

Slide 107

Slide 107 text

Thanks