前言:本博文重在tornado源碼剖析,相信讀者讀完此文可以更加深刻的瞭解tornado的運行機制,從而更加高效的使用tornado框架。javascript
本文參考武sir博客地址:http://www.cnblogs.com/wupeiqi/tag/Tornado/css
首先咱們從經典的hello world 案例入手:html
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()
運行該腳本,依次執行:前端
1.首先咱們來分析以下代碼:java
application = tornado.web.Application([ (r"/index", MainHandler), ])
源碼截圖:python
由上圖Application類的源碼咱們能夠看出,須要傳入的第一個參數是handlers,即上述代碼能夠進行以下表示:ios
application = tornado.web.Application(handlers=[ (r"/index", MainHandler), ])
這裏的參數handlers很是重要,值得咱們更加深刻的研究。它應該是一個元組組成的列表,其中每一個元組的第一個元素是一個用於匹配的正則表達式,第二個元素是一個RequestHanlder類。在hello.py中,咱們只指定了一個正則表達式-RequestHanlder對,但你能夠按你的須要指定任意多個。web
2.Application類的深刻:
源碼截圖:
這是源碼中關於靜態文件路徑的描述,從上到下的意思依次爲:正則表達式
3.application.listen(8888):json
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函數會在RequestHandler類初始化的時候執行,可是源碼中initialize函數並無作任何事情,這實際上是tornado爲咱們預留的修改源碼的地方,這就容許程序在執行全部的handler前首先執行咱們在initialize中定義的方法。
源碼截圖:
write方法是後端向前端頁面直接展現的方法。從上述源碼中能夠看出,write方法接收字典和字符串類型的參數,若是用戶傳來的數據是字典類型,源碼中會自動用json對字典進行序列化,最終序列化成字符串。
self._write_buffer是源碼中定義的一個臨時存放須要輸出的字符串的地方,是列表格式。
flush方法源碼截圖:
由上述源碼能夠看出:flush方法會self._write_buffer列表中的全部元素拼接成字符串,並賦值給chunk,而後清空self._write_buffer列表,而後設置請求頭,最終調用request.write方法在前端頁面顯示。
源碼截圖:
由上述源碼可看出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模板並返回最終結果:
詳細流程:
源碼註釋:
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開發。
若是您以爲本文對您有參考價值,歡迎幫博主點擊文章下方的推薦,謝謝!