每一個web框架都會有對靜態文件的處理支持,下面對於Tornado的靜態文件的處理模塊的源碼進行分析,以增強本身對靜態文件處理的理解。html
先從Tornado的主要模塊 web.py 入手,能夠看到在Application類的 __init__() 方法中對靜態文件的處理部分:python
1 class Application(ReversibleRouter): 2 if self.settings.get("static_path"): 3 path = self.settings["static_path"] 4 handlers = list(handlers or []) 5 static_url_prefix = settings.get("static_url_prefix", 6 "/static/") 7 static_handler_class = settings.get("static_handler_class", 8 StaticFileHandler) 9 static_handler_args = settings.get("static_handler_args", {}) 10 static_handler_args['path'] = path 11 for pattern in [re.escape(static_url_prefix) + r"(.*)", 12 r"/(favicon\.ico)", r"/(robots\.txt)"]: 13 handlers.insert(0, (pattern, static_handler_class, 14 static_handler_args))
從第二行能夠看到,須要處理靜態文件的話,須要在settings設置關於靜態環境的值:static_pathios
參數介紹:nginx
tornado.web.StaticFileHandler
initialize
方法中
介紹和用法:git
1 application = web.Application([ 2 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}), 3 ])
這樣,當你訪問 」/content/「 目錄下資源的話,就是定向到 」/var/www「下尋找。github
子類擴展注意項:web
從主要的 StaticFileHandler.get() 方法開始入手:數據庫
1 def get(self, path, include_body=True): 2 self.path = self.parse_url_path(path) 3 del path 4 absolute_path = self.get_absolute_path(self.root, self.path) 5 self.absolute_path = self.validate_absolute_path( 6 self.root, absolute_path) 7 if self.absolute_path is None: 8 return 9 10 self.modified = self.get_modified_time() 11 self.set_headers() 12 13 if self.should_return_304(): 14 self.set_status(304) 15 return 16 17 request_range = None 18 range_header = self.request.headers.get("Range") 19 if range_header: 20 request_range = httputil._parse_request_range(range_header) 21 22 size = self.get_content_size() 23 if request_range: 24 start, end = request_range 25 if (start is not None and start >= size) or end == 0: 26 self.set_status(416) # Range Not Satisfiable 27 self.set_header("Content-Type", "text/plain") 28 self.set_header("Content-Range", "bytes */%s" % (size, )) 29 return 30 if start is not None and start < 0: 31 start += size 32 if end is not None and end > size: 33 end = size 34 if size != (end or size) - (start or 0): 35 self.set_status(206) # Partial Content 36 self.set_header("Content-Range", 37 httputil._get_content_range(start, end, size)) 38 else: 39 start = end = None 40 41 if start is not None and end is not None: 42 content_length = end - start 43 elif end is not None: 44 content_length = end 45 elif start is not None: 46 content_length = size - start 47 else: 48 content_length = size 49 self.set_header("Content-Length", content_length) 50 51 if include_body: 52 content = self.get_content(self.absolute_path, start, end) 53 if isinstance(content, bytes): 54 content = [content] 55 for chunk in content: 56 try: 57 self.write(chunk) 58 yield self.flush() 59 except iostream.StreamClosedError: 60 return 61 else: 62 assert self.request.method == "HEAD"
1. 經過 parse_url_path(path) 將靜態URL路徑轉換爲所在文件系統的路徑:瀏覽器
1 def parse_url_path(self, url_path): 2 if os.path.sep != "/": 3 url_path = url_path.replace("/", os.path.sep) 4 return url_path
2. 以後爲了確保 傳入進來的path 不會替代 self.path, 因此執行了 del path 將該對象刪除。緩存
3. 調用 get_absolute_path(self.root, self.path) 將靜態URL路徑轉換爲系統的絕對路徑:
這裏注意到,self.root這個參數,其在 初始化函數 initialize() 中已經進行了定義(self.root 爲未進行文件系統路徑轉換的路徑):
1 def initialize(self, path, default_filename=None): 2 self.root = path 3 self.default_filename = default_filename
絕對路徑轉換函數 get_absolute_path():
1 def get_absolute_path(cls, root, path): 2 abspath = os.path.abspath(os.path.join(root, path)) 3 return abspath
經過 os.path.join() 將 path與root合爲一個路徑,而後經過 os.path.abspath() 獲取該路徑的絕對路徑,並返回。
4. 調用 validate_absolute_path(self.root, absolute_path) 函數對前面返回的絕對路徑 self.absolute_path 進行驗證,看該路徑文件是否有效存在:
1 def validate_absolute_path(self, root, absolute_path): 2 root = os.path.abspath(root) 3 if not root.endswith(os.path.sep): 4 root += os.path.sep 5 if not (absolute_path + os.path.sep).startswith(root): 6 raise HTTPError(403, "%s is not in root static directory", 7 self.path) 8 if (os.path.isdir(absolute_path) and 9 self.default_filename is not None): 10 if not self.request.path.endswith("/"): 11 self.redirect(self.request.path + "/", permanent=True) 12 return 13 absolute_path = os.path.join(absolute_path, self.default_filename) 14 if not os.path.exists(absolute_path): 15 raise HTTPError(404) 16 if not os.path.isfile(absolute_path): 17 raise HTTPError(403, "%s is not a file", self.path) 18 return absolute_path
函數介紹:
注:該方法用到了大量的os模塊,對os模塊不太熟悉,能夠參考:http://www.cnblogs.com/dkblog/archive/2011/03/25/1995537.html
5. 獲取該絕對路徑文件最後修改時間 get_modified_time():
1 def get_modified_time(self): 2 stat_result = self._stat() 3 modified = datetime.datetime.utcfromtimestamp( 4 stat_result[stat.ST_MTIME]) 5 return modified
其在處理過程當中調用了 _stat() :
1 def _stat(self): 2 if not hasattr(self, '_stat_result'): 3 self._stat_result = os.stat(self.absolute_path) 4 return self._stat_result
調用了 os.stat() 獲取該 self.absolute_path 路徑文件的系統信息;以後在 get_modified_time() 中獲取 ST_MTIME 屬性獲取最後修改時間。
注:os.stat模塊能夠參考:http://www.cnblogs.com/maseng/p/3386140.html
6. 調用 set_headers() 設置HTTP的Response頭部header信息:
1 def set_headers(self): 2 self.set_header("Accept-Ranges", "bytes") 3 self.set_etag_header() 4 5 if self.modified is not None: 6 self.set_header("Last-Modified", self.modified) 7 8 content_type = self.get_content_type() 9 if content_type: 10 self.set_header("Content-Type", content_type) 11 12 cache_time = self.get_cache_time(self.path, self.modified, 13 content_type) 14 if cache_time > 0: 15 self.set_header("Expires", datetime.datetime.utcnow() + 16 datetime.timedelta(seconds=cache_time)) 17 self.set_header("Cache-Control", "max-age=" + str(cache_time)) 18 19 self.set_extra_headers(self.path)
函數分析:
該 set_header() 函數會調用 _convert_header_value() 方法,對 參數 'name', 'value' 進行相應格式的轉換:
注:有興趣能夠查看:https://github.com/tornadoweb/tornado/blob/master/tornado/web.py 第361行
注:有興趣能夠查看:https://github.com/tornadoweb/tornado/blob/master/tornado/web.py 第2638行
1 def get_cache_time(self, path, modified, mime_type): 2 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
這裏對最開始介紹的在URL中使用 參數 」?v=」 來持久化瀏覽器緩存進行了判斷,該CACHE_MAX_AGE參數在類最開始進行了定義(CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years);沒有定義該參數的話,就能夠本身進行定義,不然爲0。
7. should_return_304() 函數仍是對 header頭信息中 etag 的判斷,若是沒有改變則返回狀態碼304。
8. 下面就是對 Request中的 字段「Range」 進行處理了(Range:只請求實體的一部分,指定範圍):
第一步,咱們先從 resquest請求的頭信息header中獲取到 字段「Range」 內容,若是含有該字段,則調用 httputil.py 文件中的 _parse_request_range(range_header) 函數進行Range的解析:
解析實例:
1 >>> _parse_request_range("bytes=1-2") 2 (1, 3) 3 >>> _parse_request_range("bytes=6-") 4 (6, None) 5 >>> _parse_request_range("bytes=-6") 6 (-6, None) 7 >>> _parse_request_range("bytes=-0") 8 (None, 0) 9 >>> _parse_request_range("bytes=") 10 (None, None)
注:具體實現方法有興趣能夠查看:https://github.com/tornadoweb/tornado/blob/master/tornado/httputil.py 第640行
第二步,調用 get_content_size() 獲取給定路徑上面文件資源的總大小:
1 def get_content_size(self): 2 stat_result = self._stat() 3 return stat_result[stat.ST_SIZE]
函數分析:
該函數一樣調用了上文提到的 _stat() 函數,來獲取到給定路徑上文件資源的系統信息,而後經過 ST_SIZE 屬性獲取到文件的大小。
第三步,就是對請求的資源範圍和文件大小進行判斷了:
第四步,就開始對返回頭中的響應體長度字段」content_length「進行設置:
利用上述請求範圍的 start,end進行計算,從而返回符合要求的內容,以後調用上文分析的 set_header() 函數寫入頭信息header
第五步,對 include_body 進行判斷,在最開始 def get(self, path, include_body=True) 函數中,有一個字段是 include_body = True,而後注意到源碼上面還有一個函數 def head(self, path):
1 def head(self, path): 2 return self.get(path, include_body=False)
而後在 def get(self, path, include_body=True) 中,注意到最後一行代碼(爲方便閱讀和理解,將上述 if 語句簡化截取下來):
1 if include_body: 2 ...... 3 else: 4 assert self.request.method == "HEAD"
若是客戶端request請求中,是發送的 」HEAD「請求,那麼執行上述的head函數,include_body=False,則只返回頭部信息給客戶端;不然發送的是」GET「請求,那麼include_body=True,則會將請求的靜態文件數據根據上述的範圍,調用 self.flush() 函數把緩存中的數據寫入到網絡中,傳輸給客戶端。
注:HEAD:只請求頁面的頭部信息
GET: 請求指定的頁面信息,並返回實體主體