Python Tornado框架三(源碼結(jié)構(gòu))

Tornado 是由 Facebook 開(kāi)源的一個(gè)服務(wù)器“套裝”种呐,適合于做 python 的 web 或者使用其本身提供的可擴(kuò)展的功能,完成了不完整的 wsgi 協(xié)議弃甥,可用于做快速的 web 開(kāi)發(fā)爽室,封裝了 epoll 性能較好。文章主要以分析 tornado 的網(wǎng)絡(luò)部分即異步事件處理與上層的 IOstream 類(lèi)提供的異步IO淆攻,其他的模塊如 web 的 tornado.web 以后慢慢留作分析阔墩。

下面開(kāi)始我們的Tornado之旅,看源代碼之前必定需要有一份源碼了瓶珊,大家可以去官網(wǎng)下載一份啸箫。這里分析的是4.4.2。

視頻分享鏈接:

https://study.163.com/course/introduction/1209485881.htm?share=2&shareId=400000000535031

https://study.163.com/course/introduction/1209407824.htm?share=2&shareId=400000000535031

https://study.163.com/course/introduction/1209401891.htm?share=2&shareId=400000000535031

Tornado 的源碼組織如下

?

tornado網(wǎng)絡(luò)部分最核心的兩個(gè)模塊就是ioloop.py與iostream.py伞芹,我們主要分析的就是這兩個(gè)部分忘苛。

ioloop.py 主要的是將底層的epoll或者說(shuō)是其他的IO多路復(fù)用封裝作異步事件來(lái)處理。

iostream.py主要是對(duì)于下層的異步事件的進(jìn)一步封裝唱较,為其封裝了更上一層的buffer(IO)事件扎唾。

我們先來(lái)看看ioloop(文檔地址:http://www.tornadoweb.org/en/stable/ioloop.html)

We use epoll (Linux) or kqueue (BSD and Mac OS X) if they are available, or else we fall back on select(). If you are implementing a system that needs to handle thousands of simultaneous connections, you should use a system that supports either epoll or kqueue.

Example usage for a simple TCP server:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

import errno

import functools

import ioloop

import socket

def connection_ready(sock, fd, events):

????while True:

????????try:

????????????connection, address = sock.accept()

????????except socket.error, e:

????????????if e.args[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):

????????????????raise

????????????return

????????connection.setblocking(0)

????????handle_connection(connection, address)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sock.setblocking(0)

sock.bind(("", port))

sock.listen(128)

# 創(chuàng)建一個(gè)ioloop 實(shí)例

io_loop = ioloop.IOLoop.instance()

# connection_ready 的第一個(gè)參數(shù)為 sock,即 socket 的返回值

callback = functools.partial(connection_ready, sock)

# 注冊(cè)函數(shù),第一個(gè)參數(shù)是將 sock 轉(zhuǎn)換為標(biāo)準(zhǔn)的描述符南缓,第二個(gè)為回調(diào)函數(shù)稽屏,第三個(gè)是事件類(lèi)型

io_loop.add_handler(sock.fileno(), callback, io_loop.READ)

io_loop.start()

可以看到在注釋前都是使用了傳統(tǒng)的創(chuàng)建服務(wù)器的方式,不用多介紹西乖,注意就是把套接口設(shè)置為非阻塞方式狐榔。

創(chuàng)建ioloop實(shí)例,這里是使用了ioloop.IOLoop中的 instance()靜態(tài)方法获雕,以 @classmethod 方式包裝薄腻。

在后面的add_handler中,程序?yàn)槲覀兊谋O(jiān)聽(tīng)套接口注冊(cè)了一個(gè)回調(diào)函數(shù)和一個(gè)事件類(lèi)型届案。工作方式是這樣庵楷,在注冊(cè)了相應(yīng)的事件類(lèi)型和回調(diào)函數(shù)以后,程序開(kāi)始啟動(dòng),如果在相應(yīng)的套接口上有事件發(fā)生(注冊(cè)的事件類(lèi)型)那么調(diào)用相應(yīng)的回調(diào)函數(shù)尽纽。

當(dāng)監(jiān)聽(tīng)套接口有可讀事件發(fā)生咐蚯,意味著來(lái)了一個(gè)新連接,在回調(diào)函數(shù)中就可以對(duì)這個(gè)套接口accept弄贿,并調(diào)用相應(yīng)的處理函數(shù)春锋,其實(shí)應(yīng)該是處理函數(shù)也設(shè)置為異步的,將相應(yīng)的連接套接口也加入到事件循環(huán)并注冊(cè)相應(yīng)的回調(diào)函數(shù)差凹,只是這里沒(méi)有展示出來(lái)期奔。

在使用非阻塞方式的accept時(shí)候常常返回EAGAIN,EWOULDBLOCK 錯(cuò)誤,這里采取的方式是放棄這個(gè)連接危尿。

設(shè)計(jì)模型:

在深入到模塊進(jìn)行分析之前呐萌,首先來(lái)看看Tornado的設(shè)計(jì)模型

?

從上面的圖可以看出谊娇,Tornado不僅僅是一個(gè)WEB框架肺孤,它還完整地實(shí)現(xiàn)了HTTP服務(wù)器和客戶(hù)端,在此基礎(chǔ)上提供WEB服務(wù)济欢。它可以分為四層:

最底層的EVENT層處理IO事件渠旁;

TCP層實(shí)現(xiàn)了TCP服務(wù)器,負(fù)責(zé)數(shù)據(jù)傳輸船逮;

HTTP/HTTPS層基于HTTP協(xié)議實(shí)現(xiàn)了HTTP服務(wù)器和客戶(hù)端;

最上層為WEB框架粤铭,包含了處理器挖胃、模板、數(shù)據(jù)庫(kù)連接梆惯、認(rèn)證酱鸭、本地化等等WEB框架需要具備的功能。

理解Tornado的核心框架之后垛吗,就能便于我們后續(xù)的理解凹髓。

Tornado核心文件解讀

為了方便,約定$root指帶tornado的根目錄怯屉∥狄ǎ總的來(lái)說(shuō),要用Tornado完成一個(gè)網(wǎng)站的構(gòu)建锨络,其實(shí)主要需要以下幾個(gè)文件:

$root/tornado/web.py

$root/tornado/httpserver.py

$root/tornado/tcpserver.py

$root/tornado/ioloop.py

$root/tornado/iostream.py

$root/tornado/platfrom/epoll.py

$root/app.py

另外可能還需要一些功能庫(kù)的支持而需要引入的文件就不列舉了赌躺,比如util和httputil之類(lèi)的。來(lái)看看每個(gè)文件的作用羡儿。

app.py 是自己寫(xiě)的礼患,內(nèi)容就如 tornado 的 readme 文檔里給的示例一樣,定義路由規(guī)則和 handler,然后創(chuàng)建 application缅叠,發(fā)起 server 監(jiān)聽(tīng)悄泥,服務(wù)器就算跑起來(lái)了。

緊接著就是 web.py肤粱。其中定義了 Application 和 RequestHandler 類(lèi)弹囚,在 app.py 里直接就用到了。Application 是個(gè)單例狼犯,總攬全局路由余寥,創(chuàng)建服務(wù)器負(fù)責(zé)監(jiān)聽(tīng),并把服務(wù)器傳回來(lái)的請(qǐng)求進(jìn)行轉(zhuǎn)發(fā)(__call__)悯森。RequestHandler 是個(gè)功能很豐富的類(lèi)宋舷,基本上 web 開(kāi)發(fā)需要的它都具備了,比如redirect瓢姻,flush祝蝠,close,header幻碱,cookie绎狭,render(模板),xsrf褥傍,etag等等

從 web 跟蹤到 httpserver.py 和 tcpserver.py儡嘶。這兩個(gè)文件主要是實(shí)現(xiàn) http 協(xié)議,解析 header 和 body恍风, 生成request蹦狂,回調(diào)給 appliaction,一個(gè)經(jīng)典意義上的 http 服務(wù)器(written in python)朋贬。眾所周知凯楔,這是個(gè)很考究性能的一塊(IO),所以它和其它很多塊都連接到了一起锦募,比如 IOLoop摆屯,IOStream,HTTPConnection 等等糠亩。這里 HTTPConnection 是實(shí)現(xiàn)了 http 協(xié)議的部分虐骑,它關(guān)注 Connection 嘛,這是 http 才有的赎线。至于監(jiān)聽(tīng)端口富弦,IO事件,讀寫(xiě)緩沖區(qū)氛驮,建立連接之類(lèi)都是在它的下層--tcp里需要考慮的腕柜,所以,tcpserver 才是和它們打交道的地方,到時(shí)候分析起來(lái)估計(jì)很麻煩

先說(shuō)這個(gè)IOStream盏缤。顧名思義砰蠢,就是負(fù)責(zé)IO的。說(shuō)到IO唉铜,就得提緩沖區(qū)和IO事件台舱。緩沖區(qū)的處理都在它自個(gè)兒類(lèi)里,IO事件的異步處理就要靠 IOLoop 了潭流。

然后是IOLoop竞惋。如果你用過(guò) select/poll/epoll/libevent 的話(huà),對(duì)它的處理模型應(yīng)該相當(dāng)熟悉灰嫉。簡(jiǎn)言之拆宛,就是一個(gè)大大的循環(huán)绒北,循環(huán)里等待事件芦鳍,然后處理事件假抄。這是開(kāi)發(fā)高性能服務(wù)器的常見(jiàn)模型忿族,tornado 的異步能力就是在這個(gè)類(lèi)里得到保證的

最后是 epoll.py。其實(shí)這個(gè)文件也沒(méi)干啥箭启,就是聲明了一下服務(wù)器使用 epoll秉剑。選擇 select/poll/epoll/kqueue 其中的一種作為事件分發(fā)模型澜汤,是在 tornado 里自動(dòng)根據(jù)操作系統(tǒng)的類(lèi)型而做的選擇炎滞,所以這幾種接口是一樣的(當(dāng)然效率不一樣)敢艰,出于簡(jiǎn)化,直接就epoll吧^_^

PS册赛。如果你是一個(gè)細(xì)節(jié)控钠导,可能會(huì)注意到 tornado 里的回調(diào) callback 函數(shù)都不是直接使用的,而是使用 stack_context.wrap 進(jìn)行了封裝击奶。但據(jù)我觀察,封裝前后沒(méi)多大差別(指邏輯流程)责掏,函數(shù)的參數(shù)也不變柜砾。但根據(jù)它代碼里的注釋?zhuān)@個(gè)封裝還是相當(dāng)有用的:

use this whenever saving a callback to be executed later in a different execution context (either in a different thread or asynchronously in the same thread).

所以,我猜换衬,是使用了獨(dú)有的context來(lái)保證在不同環(huán)境也能很好的執(zhí)行痰驱。猜測(cè)而已,我也沒(méi)細(xì)想瞳浦,以后有時(shí)間再看好担映,最有用一個(gè)簡(jiǎn)單的流程來(lái)做結(jié)。d?

本小節(jié)介紹TornadoHTTP服務(wù)器的基本流程叫潦,分別分析httpserver, ioloop, iostream模塊的代碼來(lái)剖析Tornado底層I/O的內(nèi)部實(shí)現(xiàn)蝇完。

httpserver.py中給出了一個(gè)簡(jiǎn)單的http服務(wù)器的demo,代碼如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

from tornado import httpserver

from tornado import ioloop


def handle_request(request):

???message = "You requested %s\n" % request.uri

???request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % (

?????????????????len(message), message))

???request.finish()


http_server = httpserver.HTTPServer(handle_request)

http_server.bind(8888)

http_server.start()

ioloop.IOLoop.instance().start()

?該http服務(wù)器主要使用到IOLoop, IOStream, HTTPServer, HTTPConnection幾大模塊,分別在代碼ioloop.py, iostream.py, httpserver.py中實(shí)現(xiàn)短蜕。工作的流程如下圖所示

?

服務(wù)器的工作流程:首先按照socket->bind->listen順序創(chuàng)建listen socket監(jiān)聽(tīng)客戶(hù)端氢架,并將每個(gè)listen socket的fd注冊(cè)到IOLoop的單例實(shí)例中;當(dāng)listen socket可讀時(shí)回調(diào)_handle_events處理客戶(hù)端請(qǐng)求朋魔;在與客戶(hù)端通信的過(guò)程中使用IOStream封裝了讀岖研、寫(xiě)緩沖區(qū),實(shí)現(xiàn)與客戶(hù)端的異步讀寫(xiě)警检。

HTTPServer分析

HTTPServer在httpserver.py中實(shí)現(xiàn)孙援,繼承自TCPServer(netutil.py中實(shí)現(xiàn)),是一個(gè)無(wú)阻塞扇雕、單線(xiàn)程HTTP服務(wù)器拓售。支持HTTP/1.1協(xié)議keep-alive連接,但不支持chunked encoding洼裤。服務(wù)器支持'X-Real-IP'和'X-Scheme'頭以及SSL傳輸邻辉,支持多進(jìn)程為prefork模式實(shí)現(xiàn)。在源代碼的注釋中對(duì)以上描述比較詳細(xì)的說(shuō)明腮鞍,這里就不再細(xì)說(shuō)值骇。

HTTPServer和TCPServer的類(lèi)結(jié)構(gòu):

1

2

3

4

5

6

7

8

9

class TCPServer(object):

????def __init__(self, io_loop=None, ssl_options=None):

????def listen(self, port, address=""):

????def add_sockets(self, sockets):

????def bind(self, port, address=None, family=socket.AF_UNSPEC, backlog=128):

????def start(self, num_processes=1):

????def stop(self):

????def handle_stream(self, stream, address):

????def _handle_connection(self, connection, address):

?文章開(kāi)始部分創(chuàng)建HTTPServer的過(guò)程:首先需要定義處理request的回調(diào)函數(shù),在tornado中通常使用tornado.web.Application封裝移国。然后構(gòu)造HTTPServer實(shí)例吱瘩,注冊(cè)回調(diào)函數(shù)。接下來(lái)監(jiān)聽(tīng)端口迹缀,啟動(dòng)服務(wù)器使碾。最后啟動(dòng)IOLoop。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

def listen(self, port, address=""):

????sockets = bind_sockets(port, address=address)

????self.add_sockets(sockets)

def bind_sockets(port, address=None, family=socket.AF_UNSPEC, backlog=128):

????# 省略sockets創(chuàng)建祝懂,address票摇,flags處理部分代碼

????for res in set(socket.getaddrinfo(address, port, family, socket.SOCK_STREAM,

??????????????????????????????????0, flags)):

????????af, socktype, proto, canonname, sockaddr = res

????????# 創(chuàng)建socket

????????sock = socket.socket(af, socktype, proto)

????????# 設(shè)置socket屬性,代碼省略


????????sock.bind(sockaddr)

????????sock.listen(backlog)

????????sockets.append(sock)

????return sockets

def add_sockets(self, sockets):

????if self.io_loop is None:

????????self.io_loop = IOLoop.instance()

????for sock in sockets:

????????self._sockets[sock.fileno()] = sock

????????add_accept_handler(sock, self._handle_connection,

???????????????????????????io_loop=self.io_loop)


def add_accept_handler(sock, callback, io_loop=None):

????if io_loop is None:

????????io_loop = IOLoop.instance()

????def accept_handler(fd, events):

????????while True:

????????????try:

????????????????connection, address = sock.accept()

????????????except socket.error, e:

????????????????if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):

????????????????????return

????????????????raise

????????????# 當(dāng)有連接被accepted時(shí)callback會(huì)被調(diào)用

????????????callback(connection, address)

????io_loop.add_handler(sock.fileno(), accept_handler, IOLoop.READ)


def _handle_connection(self, connection, address):

????# SSL部分省略

????try:

????????stream = IOStream(connection, io_loop=self.io_loop)

????????self.handle_stream(stream, address)

????except Exception:

????????logging.error("Error in connection callback", exc_info=True)

這里分析HTTPServer通過(guò)listen函數(shù)啟動(dòng)監(jiān)聽(tīng)砚蓬,這種方法是單進(jìn)程模式矢门。另外可以通過(guò)先后調(diào)用bind和start(num_processes=1)函數(shù)啟動(dòng)監(jiān)聽(tīng)同時(shí)創(chuàng)建多進(jìn)程服務(wù)器實(shí)例,后文有關(guān)于此的詳細(xì)描述灰蛙。

bind_sockets在啟動(dòng)監(jiān)聽(tīng)端口過(guò)程中調(diào)用祟剔,getaddrinfo返回服務(wù)器的所有網(wǎng)卡信息, 每塊網(wǎng)卡上都要?jiǎng)?chuàng)建監(jiān)聽(tīng)客戶(hù)端的請(qǐng)求并返回創(chuàng)建的sockets。創(chuàng)建socket過(guò)程中綁定地址和端口摩梧,同時(shí)設(shè)置了fcntl.FD_CLOEXEC(創(chuàng)建子進(jìn)程時(shí)關(guān)閉打開(kāi)的socket)和socket.SO_REUSEADDR(保證某一socket關(guān)閉后立即釋放端口物延,實(shí)現(xiàn)端口復(fù)用)標(biāo)志位。sock.listen(backlog=128)默認(rèn)設(shè)定等待被處理的連接最大個(gè)數(shù)為128仅父。

返回的每一個(gè)socket都加入到IOLoop中同時(shí)添加回調(diào)函數(shù)_handle_connection叛薯,IOLoop添加對(duì)相應(yīng)socket的IOLoop.READ事件監(jiān)聽(tīng)浑吟。_handle_connection在接受客戶(hù)端的連接處理結(jié)束之后會(huì)被調(diào)用,調(diào)用時(shí)傳入連接和ioloop對(duì)象初始化IOStream對(duì)象案训,用于對(duì)客戶(hù)端的異步讀寫(xiě)买置;然后調(diào)用handle_stream,傳入創(chuàng)建的IOStream對(duì)象初始化一個(gè)HTTPConnection對(duì)象强霎,HTTPConnection封裝了IOStream的一些操作忿项,用于處理HTTPRequest并返回。至此HTTP Server的創(chuàng)建城舞、啟動(dòng)轩触、注冊(cè)回調(diào)函數(shù)的過(guò)程分析結(jié)束。

HTTPConnection分析

該類(lèi)用于處理http請(qǐng)求家夺。在HTTPConnection初始化時(shí)對(duì)self.request_callback賦值為一個(gè)可調(diào)用的對(duì)象(該對(duì)象用于對(duì)http請(qǐng)求的具體處理和應(yīng)答)脱柱。該類(lèi)首先讀取http請(qǐng)求中header的結(jié)束符b("\r\n\r\n"),然后回調(diào)self._on_headers函數(shù)拉馋。request_callback的相關(guān)實(shí)現(xiàn)在以后的系列中有詳細(xì)介紹榨为。

1

2

3

4

5

6

7

8

9

10

def __init__(self, stream, address, request_callback, no_keep_alive=False,

?????????????????xheaders=False):

????self.request_callback = request_callback

????# some configuration code

????self._header_callback = stack_context.wrap(self._on_headers)

????self.stream.read_until(b("\r\n\r\n"), self._header_callback)

def _on_headers(self, data):

????# some codes

????self.request_callback(self._request)

多進(jìn)程HTTPServer

Tornado的HTTPServer是單進(jìn)程單線(xiàn)程模式,同時(shí)提供了創(chuàng)建多進(jìn)程服務(wù)器的接口煌茴,具體實(shí)現(xiàn)是在主進(jìn)程啟動(dòng)HTTPServer時(shí)通過(guò)process.fork_processes(num_processes)產(chǎn)生新的服務(wù)器子進(jìn)程随闺,所有進(jìn)程之間共享端口。fork_process的方法在process.py中實(shí)現(xiàn)蔓腐,十分簡(jiǎn)潔矩乐。對(duì)fork_process詳細(xì)的分析,可以參考番外篇:Tornado的多進(jìn)程管理分析回论。

FriendFeed使用nginx提供負(fù)載均衡散罕、反向代理服務(wù)并作為靜態(tài)文件服務(wù)器,在后端服務(wù)器上可以部署多個(gè)Tornado實(shí)例傀蓉。一般可以通過(guò)Supervisor控制Tornado app欧漱,然后再通過(guò)nginx對(duì)Tornado的輸出進(jìn)行反向代理。 具體可以參考下這篇文章:Supervisord進(jìn)程管理工具的安裝使用葬燎。

Tornado RequestHandler和Application類(lèi)

前面一小節(jié)提到了需要了解 web.py 這個(gè)文件误甚,這個(gè)文件最關(guān)鍵的地方是定義了 Application 和 RequestHandler 類(lèi)。我們?cè)倏纯?Tornado 的 Hello World萨蚕,我們?cè)倬?jiǎn)一下靶草,下面是最簡(jiǎn)單的實(shí)例化并啟動(dòng)Application的方式:

1

2

3

4

5

6

7

8

import ioloop

import web

application = web.Application([

????(r'/', MainHandler),

])

application.listen(8888)

ioloop.IOLoop.instance().start()

從代碼里可以看到的是:應(yīng)用里定義了 URI 路由和對(duì)應(yīng)的處理類(lèi)蹄胰,并以此構(gòu)建了application對(duì)象岳遥,然后讓這個(gè)對(duì)象監(jiān)聽(tīng)在8888端口,最后由 ioloop 單例進(jìn)入循環(huán)裕寨,不斷分發(fā)事件浩蓉。

這里的URI路由就是r"/"派继,對(duì)應(yīng)處理類(lèi)就是 MainHandler,它們被放在同一個(gè) tuple 里形成了關(guān)聯(lián)捻艳〖菘撸可以看到,application 是接受一個(gè)列表的认轨,因此可以定義多個(gè)全局路由對(duì)應(yīng)不同處理,往列表里 append 就是了嘁字。

如果只是在 tornado 的框架基礎(chǔ)上進(jìn)行開(kāi)發(fā),那就只需要不斷定義不同的處理類(lèi)衷恭,并把對(duì)應(yīng)路由與其關(guān)聯(lián)即可纯续。

tornado.web 里的 RequestHandler 和 Application 類(lèi)

Tornado 使用 web 模塊的 Application 做URI轉(zhuǎn)發(fā),然后通過(guò)RequestHandler處理請(qǐng)求窗看。 Application 提供了一個(gè) listen 方法作為 HTTPServer 中的 listen 的封裝。

初始化 Application 時(shí)烤芦,一般將處理器直接傳入析校,它會(huì)調(diào)用 add_handlers 添加這些處理器,初始化還包括 transforms (分塊遂唧、壓縮等)吊奢、UI模塊盖彭、靜態(tài)文件處理器的初始化。 add_handlers 方法負(fù)責(zé)添加URI和處理器的映射页滚。

Application 實(shí)現(xiàn) URI 轉(zhuǎn)發(fā)時(shí)使用了一個(gè)技巧召边,它實(shí)現(xiàn)了 __call__ 方法,并將 Application 的實(shí)例傳遞給 HTTPServer 裹驰,當(dāng)監(jiān)聽(tīng)到請(qǐng)求時(shí)隧熙,它通過(guò)調(diào)用 Application 實(shí)例觸發(fā) __call__ 。 __call__ 方法中完成具體的URI轉(zhuǎn)發(fā)工作幻林,并調(diào)用已注冊(cè)的處理器的 _execute 方法贞盯,處理請(qǐng)求音念。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

def __call__(self, request):

????transforms = [t(request) for t in self.transforms]

????handler = None

????args = []

????kwargs = {}

????handlers = self._get_host_handlers(request) # 取得請(qǐng)求的host的一組處理器

????if not handlers:

????????handler = RedirectHandler(

????????????self, request, url="http://" + self.default_host + "/")

????else:

????????for spec in handlers:

????????????match = spec.regex.match(request.path)? # 匹配請(qǐng)求的URI

????????????if match:

????????????????handler = spec.handler_class(self, request, **spec.kwargs) # 實(shí)例化

????????????????if spec.regex.groups:?? # 取得參數(shù)

????????????????????...

????????????????????if spec.regex.groupindex:

????????????????????????kwargs = dict(

????????????????????????????(str(k), unquote(v))

????????????????????????????for (k, v) in match.groupdict().iteritems())

????????????????????else:

????????????????????????args = [unquote(s) for s in match.groups()]

????????????????break

????????if not handler:???? # 無(wú)匹配

????????????handler = ErrorHandler(self, request, status_code=404)

????...

????handler._execute(transforms, *args, **kwargs)?? # 處理請(qǐng)求

????return handler

?RequestHandler 完成具體的請(qǐng)求,開(kāi)發(fā)者需要繼承它躏敢,并根據(jù)需要闷愤,覆蓋 head 、 get 件余、 post 讥脐、 delete 、 patch 啼器、 put 攘烛、 options 等方法,定義處理對(duì)應(yīng)請(qǐng)求的業(yè)務(wù)邏輯镀首。

RequestHandler 提供了很多鉤子坟漱,包括 initialize 、 prepare 更哄、 on_finish 成翩、 on_connection_close 麻敌、 set_default_headers 等等赢赊。

下面是 _execute 的處理流程:

RequestHandler 中涉及到很多 HTTP 相關(guān)的技術(shù)释移,包括 Header、Status熏纯、Cookie樟澜、Etag、Content-Type萍膛、鏈接參數(shù)蝗罗、重定向、長(zhǎng)連接等等桩匪,還有和用戶(hù)身份相關(guān)的XSRF和CSRF等等傻昙。這方面的知識(shí)可以參考《HTTP權(quán)威指南》妆档。

Tornado默認(rèn)實(shí)現(xiàn)了幾個(gè)常用的處理器:

ErrorHandler :生成指定狀態(tài)碼的錯(cuò)誤響應(yīng)贾惦。

RedirectHandler :重定向請(qǐng)求须板。

StaticFileHandler :處理靜態(tài)文件請(qǐng)求。

FallbackHandler :使可以在Tornado中混合使用其他HTTP服務(wù)器济蝉。

上面提到了 transform 贺嫂,Tornado 使用這種機(jī)制來(lái)對(duì)輸出做分塊和壓縮的轉(zhuǎn)換,默認(rèn)給出了 GZipContentEncoding 和 ChunkedTransferEncoding 踱稍。也可以實(shí)現(xiàn)自定義的轉(zhuǎn)換,只要實(shí)現(xiàn) transform_first_chunk 和 transform_chunk 接口即可楔敌,它們由 RequestHandler 中的 flush 調(diào)用。

總的來(lái)說(shuō)勺卢,Application對(duì)象提供如下幾個(gè)接口

__init__ 接受路由-處理器列表黑忱,制定路由轉(zhuǎn)發(fā)規(guī)則

listen 建立服務(wù)器并監(jiān)聽(tīng)端口杨何,是對(duì)httpserver的封裝調(diào)用

add_handlers 添加路由轉(zhuǎn)發(fā)規(guī)則(包括主機(jī)名匹配)

add_transform 添加輸出過(guò)濾器。例如gzip埃跷,chunk

__call__ 服務(wù)器連接的網(wǎng)關(guān)處理接口弥雹,一般是被服務(wù)器調(diào)用

最簡(jiǎn)單的應(yīng)該算是 add_transform 了。將一個(gè)類(lèi)添加到列表里就結(jié)束了厕吉。它會(huì)在輸出時(shí)被調(diào)用头朱,比較簡(jiǎn)單,略過(guò)不提烁巫。

然后是 listen磁餐。它接受端口,地址亦歉,其它參數(shù)水由。也很簡(jiǎn)單砂客,用自身和參數(shù)構(gòu)造 http 服務(wù)器,并讓服務(wù)器監(jiān)聽(tīng)在端口-地址上彤恶。其中涉及到底層 socket 的使用和 ioloop 事件綁定,放在以后再說(shuō)术徊≡蹋總之,可以認(rèn)為產(chǎn)生了如下效果:在特定端口-地址上創(chuàng)建并監(jiān)聽(tīng) socket株憾,并注冊(cè)了該 socket 的可讀事件到自身的__call__方法(亦即墙歪,每逢一個(gè)新連接到來(lái)時(shí),__call__就會(huì)被調(diào)用)

接下來(lái)看 __call__ 方法毕源。這是 python 的一個(gè)語(yǔ)法特性霎褐,這個(gè)函數(shù)使得 Application 可以直接被當(dāng)成函數(shù)來(lái)使用。

這里有一個(gè)問(wèn)題省艳,為什么不直接定義一個(gè)函數(shù)例如 call 并在 listen 方法里把 self.call 傳給服務(wù)器做回調(diào)函數(shù),而是使用 self 呢枣购?它們不都是函數(shù)嗎?有什么區(qū)別呢分瘾?

區(qū)別還是有的。首先上岗,如果使用self.call方法肴掷,那么它就是一個(gè)純粹的函數(shù)台夺,那么 application 的內(nèi)部成員就不能用了(比如路由規(guī)則表)颤介。而使用 self(也不是self.__call__)傳遞給服務(wù)器做回調(diào),當(dāng)這個(gè)對(duì)象被當(dāng)作函數(shù)調(diào)用時(shí)赞赖,__call__會(huì)被自動(dòng)調(diào)用滚朵,此時(shí)對(duì)象上下文就被保留下來(lái)了。python 里好像經(jīng)常這么搞前域。辕近。。

好话侄,來(lái)看看__call__的參數(shù):request年堆,HttpRequest對(duì)象,在 httputil 里被定義,在這里被用到的是它的 host 和 path 成員萧芙,用在路由匹配。忽略錯(cuò)誤情況,這個(gè)方法的執(zhí)行流程如下:_get_host_handler(request) 得到該 host 對(duì)應(yīng)的路徑路由列表姨夹,默認(rèn)情況下吼鱼,任何 host 都會(huì)被匹配(原因詳見(jiàn)__init__)玩敏,返回的列表直接就是傳遞給構(gòu)造 application 時(shí)的那個(gè) tuple 列表,當(dāng)然,對(duì)象變了嘴秸,蛋內(nèi)容是一樣的。然后,對(duì)于路徑路由列表中的每一個(gè)對(duì)象帕翻,用 request.path 來(lái)匹配,如果匹配上了粥鞋,就生成 RequestHandler 對(duì)象筹燕,并根據(jù)正則表達(dá)式解析路徑里的參數(shù),可以用數(shù)字做鍵值也可以用字符串做鍵值具篇。具體見(jiàn) python 的 re.match.groups()。然后跳出列表,執(zhí)行_execute() 方法阴挣,這個(gè)方法在 RequestHandler 里被定義骗爆,下次再說(shuō)律胀,簡(jiǎn)言之罪佳,它的作用是 根據(jù) http 方法轉(zhuǎn)到對(duì)應(yīng)方法勋眯,路徑正則表達(dá)式里解析到的參數(shù)也被原樣保留傳進(jìn)去。

一直有個(gè)疑問(wèn),路由規(guī)則是什么時(shí)候建立的呢而涉?為此著瓶,我們先看 add_handlers 方法,它接受兩個(gè)參數(shù)啼县,主機(jī)名正則蟹但,路徑路由列表。同時(shí)谭羔,我們還要注意到华糖,self.handlers 也是一個(gè)列表,是主機(jī)名路由列表瘟裸,它的每個(gè)元素是一個(gè) tuple客叉,包含主機(jī)名和對(duì)應(yīng)的路徑路由列表。如圖:

?

所以话告,add_handlers 的流程就很簡(jiǎn)單了:將路徑路由列表和主機(jī)名合成一個(gè) tuple 添加到 self.handlers 里兼搏,這樣該主機(jī)名就會(huì)在_get_host_handler 里被檢索,就可以根據(jù)主機(jī)名找到對(duì)應(yīng)的路徑路由規(guī)則了沙郭。這里需要注意的一個(gè)問(wèn)題是:由于.*的特殊性(它會(huì)匹配任意字符)佛呻,因此總是需要保證它被放置在列表的最后,所以就有了這段代碼

1

2

if self.handlers and self.handlers[-1][0].pattern == '.*$':

????self.handlers.insert(-1, (re.compile(host_pattern), handlers))

之前還說(shuō)到病线,默認(rèn)情況下吓著,所有的主機(jī)名都會(huì)被匹配,那是因?yàn)樵赺_init__方法里送挑,它調(diào)用了 add_handlers(".*",handlers)绑莺。由于.*匹配所有主機(jī)名,所以構(gòu)造 application 對(duì)象時(shí)傳入的路徑路由規(guī)則列表就是最終默認(rèn)路由列表了惕耕。

最后看一下__init__方法的流程纺裁。它一般接受一個(gè)參數(shù),handlers,亦即最終匹配主機(jī)的路徑路由列表欺缘。先設(shè)定 transform 列表栋豫,再設(shè)定靜態(tài)文件的路由,然后添加主機(jī)(.*)的路由列表谚殊。

好笼才,回顧一下。Application的__init__方法設(shè)定了.*主機(jī)的路徑路由規(guī)則络凿,listen 方法里開(kāi)啟了服務(wù)器并把自身作為回調(diào)骡送。__call__方法在服務(wù)器 accept 到一個(gè)新連接時(shí)被調(diào)用,主要是根據(jù)路由規(guī)則轉(zhuǎn)發(fā)請(qǐng)求到不同的處理器類(lèi)絮记,并在處理器里被分派到對(duì)應(yīng)的具體方法中摔踱,到此完成請(qǐng)求的處理

RequestHandler的分析

從上一節(jié)的流程可以看出,RequestHandler 類(lèi)把 _execute 方法暴露給了 application 對(duì)象怨愤,在這個(gè)方法里完成了請(qǐng)求的具體分發(fā)和處理派敷。因此,我主要看這一方法(當(dāng)然還包括__init__)撰洗,其它方法在開(kāi)發(fā)應(yīng)用時(shí)自然會(huì)用到篮愉,還是比較實(shí)用的,比如header,cookie,get/post參數(shù)的getter/setter方法差导,都是必須的试躏。

首先是__init__。負(fù)責(zé)對(duì)象的初始化设褐,在對(duì)象被構(gòu)造時(shí)一定會(huì)被調(diào)用的颠蕴。

那對(duì)象什么時(shí)候被調(diào)用呢?從上一節(jié)可以看到助析,在.*主機(jī)的路徑路由列表里犀被,當(dāng)路徑正則匹配了當(dāng)前請(qǐng)求的路由時(shí),application 就會(huì)新建一個(gè) RequestHandler 對(duì)象(實(shí)際上是子類(lèi)對(duì)象)外冀,然后調(diào)用 _execute 方法寡键。__init__ 方法接受3個(gè)參數(shù) : application, request, **kwargs,分別是application單例雪隧,當(dāng)前請(qǐng)求request 和 kwargs (暫時(shí)沒(méi)被用上西轩。不過(guò)可以覆蓋initialize方法,里面就有它)膀跌。這個(gè)kwargs 是靜態(tài)存儲(chǔ)在路由列表里的遭商,它最初是在給 application 設(shè)置路由列表時(shí)除了路徑正則,處理器類(lèi)之外的第三個(gè)對(duì)象捅伤,是一個(gè)字典,一般情況是空的巫玻。__init__ 方法也沒(méi)做什么丛忆,就是建立了這個(gè)對(duì)象和 application, request 的關(guān)聯(lián)就完了祠汇。構(gòu)造器就應(yīng)該是這樣,對(duì)吧熄诡?

接下來(lái)會(huì)調(diào)用 _execute 方法可很。

該方法接受三個(gè)參數(shù) transforms(相當(dāng)于 application 的中間件吧,對(duì)流程沒(méi)有影響)凰浮,*args(用數(shù)字做索引時(shí)的正則 group)我抠,**kwargs(用字符串做鍵值時(shí)的正則 group,與__init__的類(lèi)似但卻是動(dòng)態(tài)的)袜茧。該方法先設(shè)定好 transform(因?yàn)椴还苠e(cuò)誤與否都算輸出菜拓,因此中間件都必須到位)。然后笛厦,檢查 http 方法是否被支持纳鼎,然后整合路徑里的參數(shù),檢查 XSRF 攻擊裳凸。然后 prepare()贱鄙。這總是在業(yè)務(wù)邏輯前被執(zhí)行,在業(yè)務(wù)邏輯后還有個(gè) finish()姨谷。業(yè)務(wù)邏輯代碼被定義在子類(lèi)的 get/post/put/delete 方法里逗宁,具體視詳細(xì)情況而定。

還有一個(gè) finish 方法梦湘,它在業(yè)務(wù)邏輯代碼后被執(zhí)行疙剑。緩沖區(qū)里的內(nèi)容要被輸出,連接總得被關(guān)閉践叠,資源總得被釋放言缤,所以這些善后事宜就交給了 finish。與緩沖區(qū)相關(guān)的還有一個(gè)函數(shù) flush禁灼,顧名思義它會(huì)調(diào)用 transforms 對(duì)輸出做預(yù)處理管挟,然后拼接緩沖區(qū)一次性輸出?self.request.write(headers + chunk, callback=callback)。

以上弄捕,就是 handler 的分析僻孝。handler 的讀與寫(xiě),實(shí)際上都是依靠request對(duì)象來(lái)完成的守谓,而 request 到底如何呢穿铆?且看下回分解。

Tornado的核心web框架tornado.web小總結(jié)

Tornado的web框架(tornado.web)在web.py中實(shí)現(xiàn)斋荞,主要包括RequestHandler類(lèi)(本質(zhì)為對(duì)http請(qǐng)求處理的封裝)和Application類(lèi)(是一些列請(qǐng)求處理的集合荞雏,構(gòu)成的一個(gè)web-application,源代碼注釋不翻譯更容易理解:A collection of request handlers that make up a web application)。

RequestHandler分析

RequestHandler提供了一個(gè)針對(duì)http請(qǐng)求處理的基類(lèi)封裝凤优,方法比較多悦陋,主要有以下功能:

提供了GET/HEAD/POST/DELETE/PATCH/PUT/OPTIONS等方法的功能接口,具體開(kāi)發(fā)時(shí)RequestHandler的子類(lèi)重寫(xiě)這些方法以支持不同需求的請(qǐng)求處理筑辨。

提供對(duì)http請(qǐng)求的處理方法俺驶,包括對(duì)headers,頁(yè)面元素棍辕,cookie的處理暮现。

提供對(duì)請(qǐng)求響應(yīng)的一些列功能,包括redirect楚昭,write(將數(shù)據(jù)寫(xiě)入輸出緩沖區(qū))栖袋,渲染模板(render, reander_string)等。

其他的一些輔助功能哪替,如結(jié)束請(qǐng)求/響應(yīng)栋荸,刷新輸出緩沖區(qū),對(duì)用戶(hù)授權(quán)相關(guān)處理等凭舶。

Application分析

源代碼中的注釋寫(xiě)的非常好:

A collection of request handlers that make up a web application. Instances of this class are?callable?and can be passed directly to HTTPServer to serve the application.

該類(lèi)初始化的第一個(gè)參數(shù)接受一個(gè)(regexp, request_class)形式的列表晌块,指定了針對(duì)不同URL請(qǐng)求所采取的處理方法,包括對(duì)靜態(tài)文件請(qǐng)求的處理(web.StaticFileHandler)帅霜。Application類(lèi)中實(shí)現(xiàn)?__call__?函數(shù)匆背,這樣該類(lèi)就成為可調(diào)用的對(duì)象,由HTTPServer來(lái)進(jìn)行調(diào)用身冀。比如下邊是httpserver.py中HTTPConection類(lèi)的代碼钝尸,該處request_callback即為Application對(duì)象。

1

2

3

def _on_headers(self, data):

????# some codes...

????self.request_callback(self._request)

?__call__函數(shù)會(huì)遍歷Application的handlers列表搂根,匹配到相應(yīng)的URL后通過(guò)handler._execute進(jìn)行相應(yīng)處理珍促;如果沒(méi)有匹配的URL,則會(huì)調(diào)用ErrorHandler剩愧。

Application初始時(shí)有一個(gè)debug參數(shù)猪叙,當(dāng)debug=True時(shí),運(yùn)行程序時(shí)當(dāng)有代碼仁卷、模塊發(fā)生修改穴翩,程序會(huì)自動(dòng)重新加載,即實(shí)現(xiàn)了auto-reload功能锦积。該功能在autoreload.py文件中實(shí)現(xiàn)芒帕,是否需要reload的檢查在每次接收到http請(qǐng)求時(shí)進(jìn)行,基本原理是檢查每一個(gè)sys.modules以及_watched_files所包含的模塊在程序中所保存的最近修改時(shí)間和文件系統(tǒng)中的最近修改時(shí)間是否一致丰介,如果不一致背蟆,則整個(gè)程序重新加載鉴分。

1

2

3

4

5

6

def _reload_on_update(modify_times):

????for module in sys.modules.values():

????????# module test and some path handles

????????_check_file(modify_times, path)

????for path in _watched_files:

????????_check_file(modify_times, path)

Tornado的autoreload模塊提供了一個(gè)對(duì)外的main接口,可以通過(guò)下邊的方法實(shí)現(xiàn)運(yùn)行test.py程序運(yùn)行的auto-reload淆储。但是測(cè)試了一下冠场,功能有限家浇,相比于django的autorelaod模塊(具有較好的封裝和較完善的功能)還是有一定的差距本砰。最主要的原因是Tornado中的實(shí)現(xiàn)耦合了一些ioloop的功能,因而autoreload不是一個(gè)可獨(dú)立的模塊钢悲。

1

2

3

4

5

6

# tornado

python -m tornado.autoreload test.py [args...]

# django

from django.utils import autoreload

autoreload.main(your-main-func)

asynchronous方法

該方法通常被用為請(qǐng)求處理函數(shù)的decorator点额,以實(shí)現(xiàn)異步操作,被@asynchronous修飾后的請(qǐng)求處理為長(zhǎng)連接莺琳,在調(diào)用self.finish之前會(huì)一直處于連接等待狀態(tài)还棱。

總結(jié)

在前面小節(jié)Tornado HTTP服務(wù)器的基本流程中,給出了一張tornado httpserver的工作流程圖惭等,調(diào)用Application發(fā)生在HTTPConnection大方框的handle_request橢圓中珍手。那篇文章里使用的是一個(gè)簡(jiǎn)單的請(qǐng)求處理函數(shù)handle_request,無(wú)論是handle_request還是application辞做,其本質(zhì)是一個(gè)函數(shù)(可調(diào)用的對(duì)象)琳要,當(dāng)服務(wù)器接收連接并讀取http請(qǐng)求header之后進(jìn)行調(diào)用,進(jìn)行請(qǐng)求處理和應(yīng)答秤茅。

1

2

http_server = httpserver.HTTPServer(handle_request)

http_server = httpserver.HTTPServer(application)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末稚补,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子框喳,更是在濱河造成了極大的恐慌课幕,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件五垮,死亡現(xiàn)場(chǎng)離奇詭異乍惊,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)放仗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)润绎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人匙监,你說(shuō)我怎么就攤上這事凡橱。” “怎么了亭姥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵稼钩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我达罗,道長(zhǎng)坝撑,這世上最難降的妖魔是什么静秆? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮巡李,結(jié)果婚禮上抚笔,老公的妹妹穿的比我還像新娘。我一直安慰自己侨拦,他們只是感情好殊橙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著狱从,像睡著了一般膨蛮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上季研,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天敞葛,我揣著相機(jī)與錄音,去河邊找鬼与涡。 笑死惹谐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的驼卖。 我是一名探鬼主播氨肌,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼款慨!你這毒婦竟也來(lái)了儒飒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤檩奠,失蹤者是張志新(化名)和其女友劉穎桩了,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體埠戳,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡井誉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了整胃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颗圣。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖屁使,靈堂內(nèi)的尸體忽然破棺而出在岂,到底是詐尸還是另有隱情,我是刑警寧澤蛮寂,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布蔽午,位于F島的核電站,受9級(jí)特大地震影響酬蹋,放射性物質(zhì)發(fā)生泄漏及老。R本人自食惡果不足惜抽莱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骄恶。 院中可真熱鬧食铐,春花似錦、人聲如沸僧鲁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)悔捶。三九已至铃慷,卻和暖如春单芜,著一層夾襖步出監(jiān)牢的瞬間蜕该,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工洲鸠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留堂淡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓扒腕,卻偏偏與公主長(zhǎng)得像绢淀,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瘾腰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 先上代碼例子: 關(guān)于tornado,它既是web服務(wù)器(看成nginx服務(wù))皆的,又是web框架(看成一個(gè)wsgi程序...
    llicety閱讀 1,356評(píng)論 0 1
  • 官方文檔中文文檔Tornado概覽淺談Python Web 框架:Django, Twisted, Tornado...
    一只寫(xiě)程序的猿閱讀 41,935評(píng)論 7 50
  • 簡(jiǎn)介 Tornado龍卷風(fēng)是一個(gè)開(kāi)源的網(wǎng)絡(luò)服務(wù)器框架,它是基于社交聚合網(wǎng)站FriendFeed的實(shí)時(shí)信息服務(wù)開(kāi)發(fā)而...
    JunChow520閱讀 54,030評(píng)論 4 46
  • (代碼縮進(jìn)有點(diǎn)問(wèn)題 大家可以看源碼) tornado有許多關(guān)于如何處理路由列表的源碼分析的博客蹋盆,關(guān)鍵在與調(diào)用了Ap...
    lpj24閱讀 1,450評(píng)論 0 2
  • 這幾天看了Tornado的源碼费薄,寫(xiě)這篇文章以做總結(jié)。本文采用Tornado v1.2版本的源碼栖雾,討論Tornado...
    _heqin閱讀 1,152評(píng)論 0 1