日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Tornado源码分析 --- 静态文件处理模块

發(fā)布時間:2025/3/21 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Tornado源码分析 --- 静态文件处理模块 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

每個web框架都會有對靜態(tài)文件的處理支持,下面對于Tornado的靜態(tài)文件的處理模塊的源碼進(jìn)行分析,以加強自己對靜態(tài)文件處理的理解。

先從Tornado的主要模塊 web.py 入手,可以看到在Application類的 __init__() 方法中對靜態(tài)文件的處理部分:

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))

從第二行可以看到,需要處理靜態(tài)文件的話,需要在settings設(shè)置關(guān)于靜態(tài)環(huán)境的值:static_path

參數(shù)介紹:

  • static_url_prefix:靜態(tài)文件的URL前綴,可以對靜態(tài)文件的訪問路徑進(jìn)行設(shè)置,默認(rèn)為 "/static/"
  • static_handler_class:處理靜態(tài)文件的類,可以自定義處理靜態(tài)文件的動作,默認(rèn)的為?tornado.web.StaticFileHandler
  • static_handler_args:處理靜態(tài)文件的參數(shù),如果設(shè)置了,應(yīng)該有一個字典被傳入到static_handler_class類的?initialize?方法中

?

默認(rèn)的靜態(tài)文件處理模塊:class StaticFileHandler(RequestHandler)

介紹和用法:

  • 處理來自某個目錄的靜態(tài)文件內(nèi)容的模塊,如果在“Application”中傳遞了“static_url”關(guān)鍵字參數(shù)的話,“StaticFileHandler” 會被自動配置,當(dāng)然該處理模塊也可以定制上面介紹的 “static_url_prefix”、“static_handler_class”、“static_handler_args”。
  • 如果想為靜態(tài)文件目錄映射一個額外的路徑,可以參考如下方法實例:
1 application = web.Application([ 2 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}), 3 ])

   這樣,當(dāng)你訪問 ”/content/“ 目錄下資源的話,就是定向到 ”/var/www“下尋找。

  • 該靜態(tài)文件處理模塊需要一個 ”path“ 參數(shù),其指定需要被該模塊服務(wù)的本地目錄
  • 當(dāng)一個目錄被請求的時候,為了自動的處理類似”index.html“的文件,做法是在 Application 中的 settings中 設(shè)置 ”static_handler_args=dict(default_filename="index.html")“,或者為 ”StaticFileHandler“ 添加一個 初始化(initializer) 參數(shù) ”default_filename“
  • 為了最大化的利用瀏覽器的緩存,"StaticFileHandler" 類支持版本化的 URL(默認(rèn)在URL中使用參數(shù):?``?v=``),如果給出了該參數(shù),那么瀏覽器將會無期限的對該靜態(tài)文件進(jìn)行緩存(其實有期限,其定義了一個變量:CACHE_MAX_AGE = 86400 * 365 * 10,期限為10年)。`make_static_url`(也可以使用`RequestHandler.static_url`)可以用于構(gòu)建版本化的URL。
  • StaticFileHandler類模塊主要用于開發(fā)輕型文件服務(wù),對于那些繁重的文件服務(wù),使用專用靜態(tài)文件服務(wù)器(如nginx或Apache)效率會更高。該模塊也支持 HTTP“Accept-Ranges”機制來返回請求實體的部分內(nèi)容(因為一些瀏覽器需要這個功能來展示HTML5音頻或視頻)。

  子類擴(kuò)展注意項:

    • 這個類被設(shè)計為可以通過子類去進(jìn)行擴(kuò)展,但是由于該靜態(tài)URL方法是通過類方法生成并非通過實例方法,它的繼承模式不太尋常。當(dāng)要覆蓋重寫一個類方法的時候,請務(wù)必使用 "@classmethod" 裝飾器,實例方法可以使用 "self.path"、”self.absolute_path“、”self.modified“ 屬性。
    • 子類僅僅能夠覆蓋重寫該注意項討論的方法,不然覆蓋重寫其他的方法將會非常容易出錯,特別是覆蓋重寫 ”StaticFileHandler.get()“ 方法將會導(dǎo)致很嚴(yán)重的問題,因為它和 ”compute_etag“ 和其他方法耦合性很高。
    • 為了改變靜態(tài)URL的生成方式(例如:為了匹配其他服務(wù)器和CDN的行為),可以覆蓋重寫 ”make_static_url“、”parse_url_path“、”get_cache_time“、以及”get_version“。
    • 為了替換和文件系統(tǒng)的交互(例如:服務(wù)于來自數(shù)據(jù)庫中的靜態(tài)數(shù)據(jù)),可以覆蓋重寫 ”get_content“、"get_content_size"、”get_modified_time“、"get_absolute_path"、”validate_absolute_path“

?

源碼分析:

  從主要的 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) 將靜態(tài)URL路徑轉(zhuǎn)換為所在文件系統(tǒng)的路徑:

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. 之后為了確保 傳入進(jìn)來的path 不會替代 self.path, 所以執(zhí)行了 del path 將該對象刪除。

?

  ? ?3. 調(diào)用?get_absolute_path(self.root, self.path) 將靜態(tài)URL路徑轉(zhuǎn)換為系統(tǒng)的絕對路徑:

   這里注意到,self.root這個參數(shù),其在 初始化函數(shù) initialize() 中已經(jīng)進(jìn)行了定義(self.root 為未進(jìn)行文件系統(tǒng)路徑轉(zhuǎn)換的路徑):

1 def initialize(self, path, default_filename=None): 2 self.root = path 3 self.default_filename = default_filename

   絕對路徑轉(zhuǎn)換函數(shù) 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. 調(diào)用?validate_absolute_path(self.root, absolute_path) 函數(shù)對前面返回的絕對路徑 self.absolute_path 進(jìn)行驗證,看該路徑文件是否有效存在:

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

   函數(shù)介紹:

    • 對于該函數(shù),參數(shù)來說,root(self.root)是 ”StaticFileHandler“ 的配置路徑,absolute_path(absolute_path)是前面調(diào)用 ”get_absolute_path“ 的結(jié)果。
    • 而且這是在請求處理的時候所調(diào)用的實例方法,所以它也許會返回 ‘HTTPerror’ 或者使用像 ‘RequestHandler.redirect’(重定向后會返回None,然后停止進(jìn)一步進(jìn)行處理) 這樣的方法,此時404錯誤(丟失文件)就會被生成。
    • 此方法可能會在返回之前修改路徑,但請注意任何這樣的修改都不會被`make_static_url`所理解。 
    • 在實例方法中,該方法的結(jié)果可用作 ``self.absolute_path``。(在該StaticFileHandler類模塊的處理中,使用到了該特性)

   注:該方法用到了大量的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

   其在處理過程中調(diào)用了 _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

   調(diào)用了 os.stat() 獲取該 self.absolute_path 路徑文件的系統(tǒng)信息;之后在 get_modified_time() 中獲取 ST_MTIME 屬性獲取最后修改時間。

   注:os.stat模塊可以參考:http://www.cnblogs.com/maseng/p/3386140.html

?

  6. 調(diào)用 set_headers() 設(shè)置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)

   函數(shù)分析:

    • 首先,通過 set_header() 對 Response中的 ”Accept-Ranges“ 進(jìn)行設(shè)置(Accept-Ranges:表明服務(wù)器是否支持指定范圍請求及哪種類型的分段請求)。

       該 set_header() 函數(shù)會調(diào)用?_convert_header_value() 方法,對 參數(shù) 'name', 'value' 進(jìn)行相應(yīng)格式的轉(zhuǎn)換:

      • 如果給出了一個datetime,我們會根據(jù)它自動格式化HTTP規(guī)范;
      • 如果值不是字符串,我們將其轉(zhuǎn)換為一個字符串;
      • 然后將所有標(biāo)題值編碼為UTF-8。
      • 并且對 python3 和 python2 的編碼有對應(yīng)的處理

       注:有興趣可以查看:https://github.com/tornadoweb/tornado/blob/master/tornado/web.py ?第361行

    • 然后,對頭信息header中的 etag 進(jìn)行設(shè)置,詳情可以參考 前面一篇博文:Tornado源碼分析--Etag實現(xiàn)
    • 接著,self.modified is not None 表明絕對路徑文件有改變,則在字段 ”Last-Modified“ 中記錄最新的修改時間。
    • 之后,調(diào)用 get_content_type()?設(shè)置header頭信息中的 字段”Content-Type“:

      注:有興趣可以查看:https://github.com/tornadoweb/tornado/blob/master/tornado/web.py 第2638行

    • 緩存時間 cache_time 設(shè)置,調(diào)用 get_cache_time() 進(jìn)行設(shè)置:
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中使用 參數(shù) ”?v=” 來持久化瀏覽器緩存進(jìn)行了判斷,該CACHE_MAX_AGE參數(shù)在類最開始進(jìn)行了定義(CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years);沒有定義該參數(shù)的話,就可以自己進(jìn)行定義,否則為0。

?

  7.?should_return_304() 函數(shù)還是對 header頭信息中 etag 的判斷,如果沒有改變則返回狀態(tài)碼304。

?

  8. 下面就是對 Request中的 字段“Range” 進(jìn)行處理了(Range:只請求實體的一部分,指定范圍):

   第一步,我們先從 resquest請求的頭信息header中獲取到 字段“Range” 內(nèi)容,如果含有該字段,則調(diào)用 httputil.py 文件中的?_parse_request_range(range_header) 函數(shù)進(jìn)行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)

    注:具體實現(xiàn)方法有興趣可以查看:https://github.com/tornadoweb/tornado/blob/master/tornado/httputil.py 第640行

   第二步,調(diào)用 get_content_size() 獲取給定路徑上面文件資源的總大小:

1 def get_content_size(self): 2 stat_result = self._stat() 3 return stat_result[stat.ST_SIZE]

    函數(shù)分析:

      該函數(shù)同樣調(diào)用了上文提到的 _stat() 函數(shù),來獲取到給定路徑上文件資源的系統(tǒng)信息,然后通過 ST_SIZE 屬性獲取到文件的大小。

   第三步,就是對請求的資源范圍和文件大小進(jìn)行判斷了:

    • 如果 請求范圍中開始位置比文件size大,則返回狀態(tài)碼416(請求范圍不滿足);并且寫好頭信息的字段?"Content-Type" ?和?"Content-Range"(字段值為請求文件的大小)
    • 如果 start 字段小于 0 的話,最終的 start 為 start + size 得出該請求字段的范圍
    • 如果 end 字段比文件的最大值還要大的話,那么為了防止客戶端盲目使用大范圍進(jìn)行設(shè)置請求范圍,則以實際文件大小來返回該請求
    • 如果 請求范圍符合要求,在實際文件大小范圍內(nèi),那么返回狀態(tài)碼206;調(diào)用 _get_content_range() 返回值為:"bytes %s-%s/%s" % (start, end-1, total)?并且把文件的 起始位置、結(jié)束位置以及文件大小信息寫入字段"Content-Range"中

   第四步,就開始對返回頭中的響應(yīng)體長度字段”content_length“進(jìn)行設(shè)置:

    利用上述請求范圍的 start,end進(jìn)行計算,從而返回符合要求的內(nèi)容,之后調(diào)用上文分析的 set_header() 函數(shù)寫入頭信息header

   第五步,對 include_body 進(jìn)行判斷,在最開始 def get(self, path, include_body=True)?函數(shù)中,有一個字段是 include_body = True,然后注意到源碼上面還有一個函數(shù)?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請求中,是發(fā)送的 ”HEAD“請求,那么執(zhí)行上述的head函數(shù),include_body=False,則只返回頭部信息給客戶端;否則發(fā)送的是”GET“請求,那么include_body=True,則會將請求的靜態(tài)文件數(shù)據(jù)根據(jù)上述的范圍,調(diào)用 self.flush()?函數(shù)把緩存中的數(shù)據(jù)寫入到網(wǎng)絡(luò)中,傳輸給客戶端。

    注:HEAD:只請求頁面的頭部信息

      GET: ? 請求指定的頁面信息,并返回實體主體

    

    

  

?

轉(zhuǎn)載于:https://www.cnblogs.com/ShaunChen/p/6636122.html

總結(jié)

以上是生活随笔為你收集整理的Tornado源码分析 --- 静态文件处理模块的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。