Tornado 是由 Facebook 開源的一個服務器“套裝”,適合於做 python 的 web 或者使用其本身提供的可擴展的功能,完成了不完整的 wsgi 協議,可用於做快速的 web 開發,封裝了 epoll 性能較好。文章主要以分析 tornado 的網絡部分即異步事件處理與上層的 IOstream 類提供的異步IO,其他的模塊如 web 的 tornado.web 以后慢慢留作分析。
下面開始我們的 Tornado 之旅,看源代碼之前必定需要有一份源碼了,大家可以去官網下載一份。這里分析的是4.4.2。
Tornado 的源碼組織如下
tornado網絡部分最核心的兩個模塊就是ioloop.py與iostream.py,我們主要分析的就是這兩個部分。
- ioloop.py 主要的是將底層的epoll或者說是其他的IO多路復用封裝作異步事件來處理。
- iostream.py主要是對於下層的異步事件的進一步封裝,為其封裝了更上一層的buffer(IO)事件。
我們先來看看 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:
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) # 創建一個ioloop 實例 io_loop = ioloop.IOLoop.instance() # connection_ready 的第一個參數為 sock,即 socket 的返回值 callback = functools.partial(connection_ready, sock) # 注冊函數,第一個參數是將 sock 轉換為標准的描述符,第二個為回調函數,第三個是事件類型 io_loop.add_handler(sock.fileno(), callback, io_loop.READ) io_loop.start()
可以看到在注釋前都是使用了傳統的創建服務器的方式,不用多介紹,注意就是把套接口設置為非阻塞方式。
創建ioloop實例,這里是使用了ioloop.IOLoop中的 instance()靜態方法,以 @classmethod 方式包裝。
在后面的add_handler中,程序為我們的監聽套接口注冊了一個回調函數和一個事件類型。工作方式是這樣,在注冊了相應的事件類型和回調函數以后,程序開始啟動,如果在相應的套接口上有事件發生(注冊的事件類型)那么調用相應的回調函數。
當監聽套接口有可讀事件發生,意味着來了一個新連接,在回調函數中就可以對這個套接口accept,並調用相應的處理函數,其實應該是處理函數也設置為異步的,將相應的連接套接口也加入到事件循環並注冊相應的回調函數,只是這里沒有展示出來。
在使用非阻塞方式的accept時候常常返回EAGAIN,EWOULDBLOCK 錯誤,這里采取的方式是放棄這個連接。
設計模型:
在深入到模塊進行分析之前,首先來看看Tornado的設計模型。
從上面的圖可以看出,Tornado 不僅僅是一個WEB框架,它還完整地實現了HTTP服務器和客戶端,在此基礎上提供WEB服務。它可以分為四層:
- 最底層的EVENT層處理IO事件;
- TCP層實現了TCP服務器,負責數據傳輸;
- HTTP/HTTPS層基於HTTP協議實現了HTTP服務器和客戶端;
- 最上層為WEB框架,包含了處理器、模板、數據庫連接、認證、本地化等等WEB框架需要具備的功能。
理解Tornado的核心框架之后,就能便於我們后續的理解。
Tornado核心文件解讀
為了方便,約定$root指帶tornado的根目錄。總的來說,要用 Tornado 完成一個網站的構建,其實主要需要以下幾個文件:
- $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
另外可能還需要一些功能庫的支持而需要引入的文件就不列舉了,比如util和httputil之類的。來看看每個文件的作用。
- app.py 是自己寫的,內容就如 tornado 的 readme 文檔里給的示例一樣,定義路由規則和 handler,然后創建 application,發起 server 監聽,服務器就算跑起來了。
- 緊接着就是 web.py。其中定義了 Application 和 RequestHandler 類,在 app.py 里直接就用到了。Application 是個單例,總攬全局路由,創建服務器負責監聽,並把服務器傳回來的請求進行轉發(__call__)。RequestHandler 是個功能很豐富的類,基本上 web 開發需要的它都具備了,比如redirect,flush,close,header,cookie,render(模板),xsrf,etag等等
- 從 web 跟蹤到 httpserver.py 和 tcpserver.py。這兩個文件主要是實現 http 協議,解析 header 和 body, 生成request,回調給 appliaction,一個經典意義上的 http 服務器(written in python)。眾所周知,這是個很考究性能的一塊(IO),所以它和其它很多塊都連接到了一起,比如 IOLoop,IOStream,HTTPConnection 等等。這里 HTTPConnection 是實現了 http 協議的部分,它關注 Connection 嘛,這是 http 才有的。至於監聽端口,IO事件,讀寫緩沖區,建立連接之類都是在它的下層--tcp里需要考慮的,所以,tcpserver 才是和它們打交道的地方,到時候分析起來估計很麻煩
- 先說這個IOStream。顧名思義,就是負責IO的。說到IO,就得提緩沖區和IO事件。緩沖區的處理都在它自個兒類里,IO事件的異步處理就要靠 IOLoop 了。
- 然后是 IOLoop。如果你用過 select/poll/epoll/libevent 的話,對它的處理模型應該相當熟悉。簡言之,就是一個大大的循環,循環里等待事件,然后處理事件。這是開發高性能服務器的常見模型,tornado 的異步能力就是在這個類里得到保證的
- 最后是 epoll.py。其實這個文件也沒干啥,就是聲明了一下服務器使用 epoll。選擇 select/poll/epoll/kqueue 其中的一種作為事件分發模型,是在 tornado 里自動根據操作系統的類型而做的選擇,所以這幾種接口是一樣的(當然效率不一樣),出於簡化,直接就epoll吧^_^
- PS。如果你是一個細節控,可能會注意到 tornado 里的回調 callback 函數都不是直接使用的,而是使用 stack_context.wrap 進行了封裝。但據我觀察,封裝前后沒多大差別(指邏輯流程),函數的參數也不變。但根據它代碼里的注釋,這個封裝還是相當有用的:
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).
所以,我猜,是使用了獨有的context來保證在不同環境也能很好的執行。猜測而已,我也沒細想,以后有時間再看好,最有用一個簡單的流程來做結。d
本小節介紹Tornado HTTP服務器的基本流程,分別分析httpserver, ioloop, iostream模塊的代碼來剖析Tornado底層I/O的內部實現。
httpserver.py中給出了一個簡單的http服務器的demo,代碼如下所示:
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服務器主要使用到IOLoop, IOStream, HTTPServer, HTTPConnection幾大模塊,分別在代碼ioloop.py, iostream.py, httpserver.py中實現。工作的流程如下圖所示
服務器的工作流程:首先按照socket->bind->listen順序創建listen socket監聽客戶端,並將每個listen socket的fd注冊到IOLoop的單例實例中;當listen socket可讀時回調_handle_events處理客戶端請求;在與客戶端通信的過程中使用IOStream封裝了讀、寫緩沖區,實現與客戶端的異步讀寫。
HTTPServer分析
HTTPServer在httpserver.py中實現,繼承自TCPServer(netutil.py中實現),是一個無阻塞、單線程HTTP服務器。支持HTTP/1.1協議keep-alive連接,但不支持chunked encoding。服務器支持'X-Real-IP'和'X-Scheme'頭以及SSL傳輸,支持多進程為prefork模式實現。在源代碼的注釋中對以上描述比較詳細的說明,這里就不再細說。
HTTPServer和TCPServer的類結構:
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):
文章開始部分創建HTTPServer的過程:首先需要定義處理request的回調函數,在tornado中通常使用tornado.web.Application封裝。然后構造HTTPServer實例,注冊回調函數。接下來監聽端口,啟動服務器。最后啟動IOLoop。
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創建,address,flags處理部分代碼 for res in set(socket.getaddrinfo(address, port, family, socket.SOCK_STREAM, 0, flags)): af, socktype, proto, canonname, sockaddr = res # 創建socket sock = socket.socket(af, socktype, proto) # 設置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 # 當有連接被accepted時callback會被調用 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通過listen函數啟動監聽,這種方法是單進程模式。另外可以通過先后調用bind和start(num_processes=1)函數啟動監聽同時創建多進程服務器實例,后文有關於此的詳細描述。
bind_sockets在啟動監聽端口過程中調用,getaddrinfo返回服務器的所有網卡信息, 每塊網卡上都要創建監聽客戶端的請求並返回創建的sockets。創建socket過程中綁定地址和端口,同時設置了fcntl.FD_CLOEXEC(創建子進程時關閉打開的socket)和socket.SO_REUSEADDR(保證某一socket關閉后立即釋放端口,實現端口復用)標志位。sock.listen(backlog=128)默認設定等待被處理的連接最大個數為128。
返回的每一個socket都加入到IOLoop中同時添加回調函數_handle_connection,IOLoop添加對相應socket的IOLoop.READ事件監聽。_handle_connection在接受客戶端的連接處理結束之后會被調用,調用時傳入連接和ioloop對象初始化IOStream對象,用於對客戶端的異步讀寫;然后調用handle_stream,傳入創建的IOStream對象初始化一個HTTPConnection對象,HTTPConnection封裝了IOStream的一些操作,用於處理HTTPRequest並返回。至此HTTP Server的創建、啟動、注冊回調函數的過程分析結束。
HTTPConnection分析
該類用於處理http請求。在HTTPConnection初始化時對self.request_callback賦值為一個可調用的對象(該對象用於對http請求的具體處理和應答)。該類首先讀取http請求中header的結束符b("\r\n\r\n"),然后回調self._on_headers函數。request_callback的相關實現在以后的系列中有詳細介紹。
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)
多進程HTTPServer
Tornado的HTTPServer是單進程單線程模式,同時提供了創建多進程服務器的接口,具體實現是在主進程啟動HTTPServer時通過process.fork_processes(num_processes)產生新的服務器子進程,所有進程之間共享端口。fork_process的方法在process.py中實現,十分簡潔。對fork_process詳細的分析,可以參考 番外篇:Tornado的多進程管理分析。
FriendFeed使用nginx提供負載均衡、反向代理服務並作為靜態文件服務器,在后端服務器上可以部署多個Tornado實例。一般可以通過Supervisor控制Tornado app,然后再通過nginx對Tornado的輸出進行反向代理。 具體可以參考下這篇文章:Supervisord進程管理工具的安裝使用。
Tornado RequestHandler和Application類
前面一小節提到了需要了解 web.py 這個文件,這個文件最關鍵的地方是定義了 Application 和 RequestHandler 類。我們再看看 Tornado 的 Hello World,我們再精簡一下,下面是最簡單的實例化並啟動 Application 的方式:
import ioloop import web application = web.Application([ (r'/', MainHandler), ]) application.listen(8888) ioloop.IOLoop.instance().start()
從代碼里可以看到的是:應用里定義了 URI 路由和對應的處理類,並以此構建了application對象,然后讓這個對象監聽在8888端口,最后由 ioloop 單例進入循環,不斷分發事件。
這里的URI路由就是r"/",對應處理類就是 MainHandler,它們被放在同一個 tuple 里形成了關聯。可以看到,application 是接受一個列表的,因此可以定義多個全局路由對應不同處理,往列表里 append 就是了。
如果只是在 tornado 的框架基礎上進行開發,那就只需要不斷定義不同的處理類,並把對應路由與其關聯即可。
tornado.web 里的 RequestHandler 和 Application 類
Tornado 使用 web 模塊的 Application 做URI轉發,然后通過 RequestHandler處理請求。 Application 提供了一個 listen 方法作為 HTTPServer 中的 listen 的封裝。
初始化 Application 時,一般將處理器直接傳入,它會調用 add_handlers 添加這些處理器,初始化還包括 transforms (分塊、壓縮等)、UI模塊、靜態文件處理器的初始化。 add_handlers 方法負責添加URI和處理器的映射。
Application 實現 URI 轉發時使用了一個技巧,它實現了 __call__ 方法,並將 Application 的實例傳遞給 HTTPServer ,當監聽到請求時,它通過調用 Application 實例觸發 __call__ 。 __call__ 方法中完成具體的URI轉發工作,並調用已注冊的處理器的 _execute 方法,處理請求。
def __call__(self, request): transforms = [t(request) for t in self.transforms] handler = None args = [] kwargs = {} handlers = self._get_host_handlers(request) # 取得請求的host的一組處理器 if not handlers: handler = RedirectHandler( self, request, url="http://" + self.default_host + "/") else: for spec in handlers: match = spec.regex.match(request.path) # 匹配請求的URI if match: handler = spec.handler_class(self, request, **spec.kwargs) # 實例化 if spec.regex.groups: # 取得參數 ... 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: # 無匹配 handler = ErrorHandler(self, request, status_code=404) ... handler._execute(transforms, *args, **kwargs) # 處理請求 return handler
RequestHandler 完成具體的請求,開發者需要繼承它,並根據需要,覆蓋 head 、 get 、 post 、 delete 、 patch 、 put 、 options 等方法,定義處理對應請求的業務邏輯。
RequestHandler 提供了很多鈎子,包括 initialize 、 prepare 、 on_finish 、 on_connection_close 、 set_default_headers 等等。
下面是 _execute 的處理流程:
RequestHandler 中涉及到很多 HTTP 相關的技術,包括 Header、Status、Cookie、Etag、Content-Type、鏈接參數、重定向、長連接等等,還有和用戶身份相關的XSRF和CSRF等等。這方面的知識可以參考《HTTP權威指南》。
Tornado默認實現了幾個常用的處理器:
- ErrorHandler :生成指定狀態碼的錯誤響應。
- RedirectHandler :重定向請求。
- StaticFileHandler :處理靜態文件請求。
- FallbackHandler :使可以在Tornado中混合使用其他HTTP服務器。
上面提到了 transform ,Tornado 使用這種機制來對輸出做分塊和壓縮的轉換,默認給出了 GZipContentEncoding 和 ChunkedTransferEncoding 。也可以實現自定義的轉換,只要實現 transform_first_chunk 和 transform_chunk 接口即可,它們由 RequestHandler 中的 flush 調用。
總的來說,Application對象提供如下幾個接口:
- __init__ 接受路由-處理器列表,制定路由轉發規則
- listen 建立服務器並監聽端口,是對httpserver的封裝調用
- add_handlers 添加路由轉發規則(包括主機名匹配)
- add_transform 添加輸出過濾器。例如gzip,chunk
- __call__ 服務器連接的網關處理接口,一般是被服務器調用
最簡單的應該算是 add_transform 了。將一個類添加到列表里就結束了。它會在輸出時被調用,比較簡單,略過不提。
然后是 listen。它接受端口,地址,其它參數。也很簡單,用自身和參數構造 http 服務器,並讓服務器監聽在端口-地址上。其中涉及到底層 socket 的使用和 ioloop 事件綁定,放在以后再說。總之,可以認為產生了如下效果:在特定端口-地址上創建並監聽 socket,並注冊了該 socket 的可讀事件到自身的__call__方法(亦即,每逢一個新連接到來時,__call__就會被調用)
接下來看 __call__ 方法。這是 python 的一個語法特性,這個函數使得 Application 可以直接被當成函數來使用。
這里有一個問題,為什么不直接定義一個函數例如 call 並在 listen 方法里把 self.call 傳給服務器做回調函數,而是使用 self 呢?它們不都是函數嗎?有什么區別呢?
區別還是有的。首先,如果使用self.call方法,那么它就是一個純粹的函數,那么 application 的內部成員就不能用了(比如路由規則表)。而使用 self(也不是self.__call__)傳遞給服務器做回調,當這個對象被當作函數調用時,__call__會被自動調用,此時對象上下文就被保留下來了。python 里好像經常這么搞。。。
好,來看看__call__的參數:request,HttpRequest對象,在 httputil 里被定義,在這里被用到的是它的 host 和 path 成員,用在路由匹配。忽略錯誤情況,這個方法的執行流程如下:_get_host_handler(request) 得到該 host 對應的路徑路由列表,默認情況下,任何 host 都會被匹配(原因詳見__init__),返回的列表直接就是傳遞給構造 application 時的那個 tuple 列表,當然,對象變了,蛋內容是一樣的。然后,對於路徑路由列表中的每一個對象,用 request.path 來匹配,如果匹配上了,就生成 RequestHandler 對象,並根據正則表達式解析路徑里的參數,可以用數字做鍵值也可以用字符串做鍵值。具體見 python 的 re.match.groups()。然后跳出列表,執行_execute() 方法,這個方法在 RequestHandler 里被定義,下次再說,簡言之,它的作用是 根據 http 方法轉到對應方法,路徑正則表達式里解析到的參數也被原樣保留傳進去。
一直有個疑問,路由規則是什么時候建立的呢?為此,我們先看 add_handlers 方法,它接受兩個參數,主機名正則,路徑路由列表。同時,我們還要注意到,self.handlers 也是一個列表,是主機名路由列表,它的每個元素是一個 tuple,包含主機名和對應的路徑路由列表。如圖:
所以,add_handlers 的流程就很簡單了:將路徑路由列表和主機名合成一個 tuple 添加到 self.handlers 里,這樣該主機名就會在_get_host_handler 里被檢索,就可以根據主機名找到對應的路徑路由規則了。這里需要注意的一個問題是:由於.*的特殊性(它會匹配任意字符),因此總是需要保證它被放置在列表的最后,所以就有了這段代碼
if self.handlers and self.handlers[-1][0].pattern == '.*$': self.handlers.insert(-1, (re.compile(host_pattern), handlers))
之前還說到,默認情況下,所有的主機名都會被匹配,那是因為在__init__方法里,它調用了 add_handlers(".*",handlers)。由於.*匹配所有主機名,所以構造 application 對象時傳入的路徑路由規則列表就是最終默認路由列表了。
最后看一下__init__方法的流程。它一般接受一個參數,handlers,亦即最終匹配主機的路徑路由列表。先設定 transform 列表,再設定靜態文件的路由,然后添加主機(.*)的路由列表。
好,回顧一下。Application 的__init__方法設定了.*主機的路徑路由規則,listen 方法里開啟了服務器並把自身作為回調。__call__方法在服務器 accept 到一個新連接時被調用,主要是根據路由規則轉發請求到不同的處理器類,並在處理器里被分派到對應的具體方法中,到此完成請求的處理
RequestHandler的分析
從上一節的流程可以看出,RequestHandler 類把 _execute 方法暴露給了 application 對象,在這個方法里完成了請求的具體分發和處理。因此,我主要看這一方法(當然還包括__init__),其它方法在開發應用時自然會用到,還是比較實用的,比如header,cookie,get/post參數的getter/setter方法,都是必須的。
首先是__init__。負責對象的初始化,在對象被構造時一定會被調用的。
那對象什么時候被調用呢?從上一節可以看到,在.*主機的路徑路由列表里,當路徑正則匹配了當前請求的路由時,application 就會新建一個 RequestHandler 對象(實際上是子類對象),然后調用 _execute 方法。__init__ 方法接受3個參數 : application, request, **kwargs,分別是application單例,當前請求request 和 kwargs (暫時沒被用上。不過可以覆蓋initialize方法,里面就有它)。這個kwargs 是靜態存儲在路由列表里的,它最初是在給 application 設置路由列表時除了路徑正則,處理器類之外的第三個對象,是一個字典,一般情況是空的。__init__ 方法也沒做什么,就是建立了這個對象和 application, request 的關聯就完了。構造器就應該是這樣,對吧?
接下來會調用 _execute 方法。
該方法接受三個參數 transforms(相當於 application 的中間件吧,對流程沒有影響),*args(用數字做索引時的正則 group),**kwargs(用字符串做鍵值時的正則 group,與__init__的類似但卻是動態的)。該方法先設定好 transform(因為不管錯誤與否都算輸出,因此中間件都必須到位)。然后,檢查 http 方法是否被支持,然后整合路徑里的參數,檢查 XSRF 攻擊。然后 prepare()。這總是在業務邏輯前被執行,在業務邏輯后還有個 finish()。業務邏輯代碼被定義在子類的 get/post/put/delete 方法里,具體視詳細情況而定。
還有一個 finish 方法,它在業務邏輯代碼后被執行。緩沖區里的內容要被輸出,連接總得被關閉,資源總得被釋放,所以這些善后事宜就交給了 finish。與緩沖區相關的還有一個函數 flush,顧名思義它會調用 transforms 對輸出做預處理,然后拼接緩沖區一次性輸出 self.request.write(headers + chunk, callback=callback)。
以上,就是 handler 的分析。handler 的讀與寫,實際上都是依靠 request 對象來完成的,而 request 到底如何呢?且看下回分解。
Tornado的核心web框架tornado.web小總結
Tornado的web框架(tornado.web)在web.py中實現,主要包括RequestHandler類(本質為對http請求處理的封裝)和Application類(是一些列請求處理的集合,構成的一個web-application,源代碼注釋不翻譯更容易理解:A collection of request handlers that make up a web application)。
RequestHandler分析
RequestHandler提供了一個針對http請求處理的基類封裝,方法比較多,主要有以下功能:
- 提供了GET/HEAD/POST/DELETE/PATCH/PUT/OPTIONS等方法的功能接口,具體開發時RequestHandler的子類重寫這些方法以支持不同需求的請求處理。
- 提供對http請求的處理方法,包括對headers,頁面元素,cookie的處理。
- 提供對請求響應的一些列功能,包括redirect,write(將數據寫入輸出緩沖區),渲染模板(render, reander_string)等。
- 其他的一些輔助功能,如結束請求/響應,刷新輸出緩沖區,對用戶授權相關處理等。
Application分析
源代碼中的注釋寫的非常好:
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.
該類初始化的第一個參數接受一個(regexp, request_class)形式的列表,指定了針對不同URL請求所采取的處理方法,包括對靜態文件請求的處理(web.StaticFileHandler)。Application類中實現 __call__ 函數,這樣該類就成為可調用的對象,由HTTPServer來進行調用。比如下邊是httpserver.py中HTTPConection類的代碼,該處request_callback即為Application對象。
def _on_headers(self, data): # some codes... self.request_callback(self._request)
__call__函數會遍歷Application的handlers列表,匹配到相應的URL后通過handler._execute進行相應處理;如果沒有匹配的URL,則會調用ErrorHandler。
在Application初始時有一個debug參數,當debug=True時,運行程序時當有代碼、模塊發生修改,程序會自動重新加載,即實現了auto-reload功能。該功能在autoreload.py文件中實現,是否需要reload的檢查在每次接收到http請求時進行,基本原理是檢查每一個sys.modules以及_watched_files所包含的模塊在程序中所保存的最近修改時間和文件系統中的最近修改時間是否一致,如果不一致,則整個程序重新加載。
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模塊提供了一個對外的main接口,可以通過下邊的方法實現運行test.py程序運行的auto-reload。但是測試了一下,功能有限,相比於django的autorelaod模塊(具有較好的封裝和較完善的功能)還是有一定的差距。最主要的原因是Tornado中的實現耦合了一些ioloop的功能,因而autoreload不是一個可獨立的模塊。
# tornado python -m tornado.autoreload test.py [args...] # django from django.utils import autoreload autoreload.main(your-main-func)
asynchronous方法
該方法通常被用為請求處理函數的decorator,以實現異步操作,被@asynchronous修飾后的請求處理為長連接,在調用self.finish之前會一直處於連接等待狀態。
總結
在前面小節 Tornado HTTP服務器的基本流程 中,給出了一張tornado httpserver的工作流程圖,調用Application發生在HTTPConnection大方框的handle_request橢圓中。那篇文章里使用的是一個簡單的請求處理函數handle_request,無論是handle_request還是application,其本質是一個函數(可調用的對象),當服務器接收連接並讀取http請求header之后進行調用,進行請求處理和應答。
http_server = httpserver.HTTPServer(handle_request) http_server = httpserver.HTTPServer(application)