前言:本博文重在tornado源碼剖析,相信讀者讀完此文能夠更加深入的了解tornado的運行機制,從而更加高效的使用tornado框架。
本文參考武sir博客地址:http://www.cnblogs.com/wupeiqi/tag/Tornado/
初識tornado
首先我們從經典的hello world 案例入手:
import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") application = tornado.web.Application([ (r"/index", MainHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
運行該腳本,依次執行:
- 創建一個Application對象,並把一個正則表達式'/'和類名MainHandler傳入構造函數:tornado.web.Application(...)
- 執行Application對象的listen(...)方法,即:application.listen(8888)
- 執行IOLoop類的類的 start() 方法,即:tornado.ioloop.IOLoop.instance().start()
1.首先我們來分析如下代碼:
application = tornado.web.Application([ (r"/index", MainHandler), ])
源碼截圖:
由上圖Application類的源碼我們可以看出,需要傳入的第一個參數是handlers,即上述代碼可以進行如下表示:
application = tornado.web.Application(handlers=[ (r"/index", MainHandler), ])
這里的參數handlers非常重要,值得我們更加深入的研究。它應該是一個元組組成的列表,其中每個元組的第一個元素是一個用於匹配的正則表達式,第二個元素是一個RequestHanlder類。在hello.py中,我們只指定了一個正則表達式-RequestHanlder對,但你可以按你的需要指定任意多個。
2.Application類的深入:
源碼截圖:
這是源碼中關於靜態文件路徑的描述,從上到下的意思依次為:
- 如果用戶在settings有配置名為“static_path”的文件路徑(這就要求我們如果需要配置靜態文件路徑,則key值必須是“static_path”)
從源碼可看出,這是根據字典的方式進行取值,因此可斷定settings是字典格式
,這是配置靜態文件路徑前綴,便於tronado在前端的靜態文件路徑中找到靜態文件,即告訴tronado,我是靜態文件,按照靜態文件路徑查詢即可,這里,靜態文件前綴也可以理解為靜態文件標識。
3.application.listen(8888):
application.listen(8888)
即Application類的listen方法:
源碼截圖:
從上述源碼可看出listen方法接收port端口,address即ip地址,默認為空。然后實例化HTTPServer對象,執行listen方法:
HTTPServer類listen方法源碼截圖:
上述listen方法綁定了ip和端口,並開啟socket。
bind方法源碼截圖:
由上述源碼可看出,bind方法內部創建socket對象,調用socket對象綁定ip和端口,並進行監聽。
4.tornado.ioloop.IOLoop.instance().start():
tornado.ioloop.IOLoop.instance().start()
這是tronado的ioloop.py文件中的IOLoop類:
instance方法源碼截圖:
這里使用了類方法,即可以通過類名直接訪問。我們需要關注的是最后面的代碼:
if not hasattr(cls, "_instance"): cls._instance = cls() return cls._instance
注:這里使用了單例模式,因為我們不需要為每一次連接IO都創建一個對象,換句話說,每次連接IO只需要是同一個對象即可
start方法:
class IOLoop(object): def add_handler(self, fd, handler, events): #HttpServer的Start方法中會調用該方法 self._handlers[fd] = stack_context.wrap(handler) self._impl.register(fd, events | self.ERROR) def start(self): while True: poll_timeout = 0.2 try: #epoll中輪詢 event_pairs = self._impl.poll(poll_timeout) except Exception, e: #省略其他 #如果有讀可用信息,則把該socket對象句柄和Event Code序列添加到self._events中 self._events.update(event_pairs) #遍歷self._events,處理每個請求 while self._events: fd, events = self._events.popitem() try: #以socket為句柄為key,取出self._handlers中的stack_context.wrap(handler),並執行 #stack_context.wrap(handler)包裝了HTTPServer類的_handle_events函數的一個函數 #是在上一步中執行add_handler方法時候,添加到self._handlers中的數據。 self._handlers[fd](fd, events) except: #省略其他
由上述源碼中while Ture:可以看出當執行了start方法后程序會進入死循環,不斷檢測是否有用戶發送請求過來,如果有請求到達,則執行封裝了HttpServer類的_handle_events方法和相關上下文的stack_context.wrap(handler)(其實就是執行HttpServer類的_handle_events方法),詳細見下篇博文,簡要代碼如下:
class HTTPServer(object): def _handle_events(self, fd, events): while True: try: connection, address = self._socket.accept() except socket.error, e: if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): return raise if self.ssl_options is not None: assert ssl, "Python 2.6+ and OpenSSL required for SSL" try: connection = ssl.wrap_socket(connection, server_side=True, do_handshake_on_connect=False, **self.ssl_options) except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error, err: if err.args[0] == errno.ECONNABORTED: return connection.close() else: raise try: if self.ssl_options is not None: stream = iostream.SSLIOStream(connection, io_loop=self.io_loop) else: stream = iostream.IOStream(connection, io_loop=self.io_loop) HTTPConnection(stream, address, self.request_callback, self.no_keep_alive, self.xheaders) except: logging.error("Error in connection callback", exc_info=True)
5.tornado.web.RequestHandler
這是所有業務處理handler需要繼承的父類,接下來,介紹一些RequestHandler類中常用的一些方法:
- initialize:
從源碼中可以看出initialize函數會在RequestHandler類初始化的時候執行,但是源碼中initialize函數並沒有做任何事情,這其實是tornado為我們預留的修改源碼的地方,這就允許程序在執行所有的handler前首先執行我們在initialize中定義的方法。
- write
源碼截圖:
write方法是后端向前端頁面直接展示的方法。從上述源碼中可以看出,write方法接收字典和字符串類型的參數,如果用戶傳來的數據是字典類型,源碼中會自動用json對字典進行序列化,最終序列化成字符串。
self._write_buffer是源碼中定義的一個臨時存放需要輸出的字符串的地方,是列表格式。
- 需要write的內容添加到 self._write_buffer后,系統會執行flush方法:
flush方法源碼截圖:
由上述源碼可以看出:flush方法會self._write_buffer列表中的所有元素拼接成字符串,並賦值給chunk,然后清空self._write_buffer列表,然后設置請求頭,最終調用request.write方法在前端頁面顯示。
- render方法:
源碼截圖:
由上述源碼可看出render方法是根據參數渲染模板,下面我們來介紹具體源碼中是如何渲染的:
js和css部分的源碼截圖:
由上述源碼可看出,靜態文件(以JavaScript為例,css是類似的)的渲染流程是:
首先通過module.embedded_javascript() 獲取需要插入JavaScript字符串,添加到js_embed 列表中;
進而通過module.javascript_files()獲取已有的列表格式的JavaScript files,最終將它加入js_files.
下面對js_embed和js_files做進一步介紹:
js_embed源碼截圖:
上圖源碼即生成script標簽,這是一些我們自己定義的一些JavaScript代碼;最終是通過字符串拼接方式插入到整個html中。
js_files源碼截圖:
上圖源碼即生成script標簽,這是一些需要引入的JavaScript代碼塊;最終是通過字符串拼接方式插入到整個html中。需要注意的是:其中靜態路徑是調用self.static_url(path)實現的。
static_url方法源碼截圖:
由上述代碼可看出:源碼首先會判斷用戶有沒有設置靜態路徑的前綴,然后將靜態路徑與相對路徑進行拼接成絕對路徑,接下來按照絕對路徑打開文件,並對文件內容(f.read())做md5加密,最終將根目錄+靜態路徑前綴+相對路徑拼接在前端html中展示。
render_string方法:
這是render方法中最重要的一個子方法,它負責去處理Html模板並返回最終結果:
詳細流程:
- 創建Loader對象,並執行load方法
-- 通過open函數打開html文件並讀取內容,並將內容作為參數又創建一個 Template 對象
-- 當執行Template的 __init__ 方法時,根據模板語言的標簽 {{}}、{%%}等分割並html文件,最后生成一個字符串表示的函數 - 獲取所有要嵌入到html模板中的變量,包括:用戶返回和框架默認
- 執行Template對象的generate方法
-- 編譯字符串表示的函數,並將用戶定義的值和框架默認的值作為全局變量
-- 執行被編譯的函數獲取被嵌套了數據的內容,然后將內容返回(用於響應給請求客戶端)
源碼注釋:
class RequestHandler(object): def render_string(self, template_name, **kwargs): #獲取配置文件中指定的模板文件夾路徑,即:template_path = 'views' template_path = self.get_template_path() #如果沒有配置模板文件的路徑,則默認去啟動程序所在的目錄去找 if not template_path: frame = sys._getframe(0) web_file = frame.f_code.co_filename while frame.f_code.co_filename == web_file: frame = frame.f_back template_path = os.path.dirname(frame.f_code.co_filename) if not getattr(RequestHandler, "_templates", None): RequestHandler._templates = {} #創建Loader對象,第一次創建后,會將該值保存在RequestHandler的靜態字段_template_loaders中 if template_path not in RequestHandler._templates: loader = self.application.settings.get("template_loader") or\ template.Loader(template_path) RequestHandler._templates[template_path] = loader #執行Loader對象的load方法,該方法內部執行執行Loader的_create_template方法 #在_create_template方法內部使用open方法會打開html文件並讀取html的內容,然后將其作為參數來創建一個Template對象 #Template的構造方法被執行時,內部解析html文件的內容,並根據內部的 {{}} {%%}標簽對內容進行分割,最后生成一個字符串類表示的函數並保存在self.code字段中 t = RequestHandler._templates[template_path].load(template_name) #獲取所有要嵌入到html中的值和框架默認提供的值 args = dict( handler=self, request=self.request, current_user=self.current_user, locale=self.locale, _=self.locale.translate, static_url=self.static_url, xsrf_form_html=self.xsrf_form_html, reverse_url=self.application.reverse_url ) args.update(self.ui) args.update(kwargs) #執行Template的generate方法,編譯字符串表示的函數並將namespace中的所有key,value設置成全局變量,然后執行該函數。從而將值嵌套進html並返回。 return t.generate(**args)

class Loader(object): """A template loader that loads from a single root directory. You must use a template loader to use template constructs like {% extends %} and {% include %}. Loader caches all templates after they are loaded the first time. """ def __init__(self, root_directory): self.root = os.path.abspath(root_directory) self.templates = {} Loader.__init__

class Loader(object): def load(self, name, parent_path=None): name = self.resolve_path(name, parent_path=parent_path) if name not in self.templates: path = os.path.join(self.root, name) f = open(path, "r") #讀取html文件的內容 #創建Template對象 #name是文件名 self.templates[name] = Template(f.read(), name=name, loader=self) f.close() return self.templates[name] Loader.load

class Template(object): def __init__(self, template_string, name="<string>", loader=None,compress_whitespace=None): # template_string是Html文件的內容 self.name = name if compress_whitespace is None: compress_whitespace = name.endswith(".html") or name.endswith(".js") #將內容封裝到_TemplateReader對象中,用於之后根據模板語言的標簽分割html文件 reader = _TemplateReader(name, template_string) #分割html文件成為一個一個的對象 #執行_parse方法,將html文件分割成_ChunkList對象 self.file = _File(_parse(reader)) #將html內容格式化成字符串表示的函數 self.code = self._generate_python(loader, compress_whitespace) try: #將字符串表示的函數編譯成函數 self.compiled = compile(self.code, self.name, "exec") except: formatted_code = _format_code(self.code).rstrip() logging.error("%s code:\n%s", self.name, formatted_code) raise Template.__init__

class Template(object): def generate(self, **kwargs): """Generate this template with the given arguments.""" namespace = { "escape": escape.xhtml_escape, "xhtml_escape": escape.xhtml_escape, "url_escape": escape.url_escape, "json_encode": escape.json_encode, "squeeze": escape.squeeze, "linkify": escape.linkify, "datetime": datetime, } #創建變量環境並執行函數,詳細Demo見上一篇博文 namespace.update(kwargs) exec self.compiled in namespace execute = namespace["_execute"] try: #執行編譯好的字符串格式的函數,獲取嵌套了值的html文件 return execute() except: formatted_code = _format_code(self.code).rstrip() logging.error("%s code:\n%s", self.name, formatted_code) raise Template.generate
示例html:
源碼模板語言處理部分的截圖:
結束語
本博文從tornado url正則匹配、路由與映射、底層socket實現、端口監聽、多請求並發、Handler類方法的源碼剖析,便於tronado web開發人員能夠更深入的理解tronado的運行機制,從而更高效的從事tronado web開發。
如果您覺得本文對您有參考價值,歡迎幫博主點擊文章下方的推薦,謝謝!