tornado處理get請求時持續返回304狀態碼

Tornado源碼分析 --- Etag實現

Etag(URL的Entity Tag):

對於具體Etag是什麼,請求流程,實現原理,這裏不進行介紹,可以參考下面鏈接:
http://www.oschina.net/questi...
https://zh.wikipedia.org/wiki...

Tornado實現分析:

先從Tornado處理一個請求的調用順序開始看(摘自文檔:http://www.tornadoweb.cn/documentation):
程序爲每一個請求創建一個 RequestHandler 對象
程序調用 initialize() 函數,這個函數的參數是 Application 配置中的關鍵字 參數定義。(initialize 方法是 Tornado 1.1 中新添加的,舊版本中你需要 重寫 __init__ 以達到同樣的目的) initialize 方法一般只是把傳入的參數存 到成員變量中,而不會產生一些輸出或者調用像 send_error 之類的方法。
程序調用 prepare()。無論使用了哪種 HTTP 方法,prepare 都會被調用到,因此 這個方法通常會被定義在一個基類中,然後在子類中重用。prepare可以產生輸出 信息。如果它調用了finish(或send_error` 等函數),那麼整個處理流程 就此結束。
程序調用某個 HTTP 方法:例如 get()、post()、put() 等。如果 URL 的正則表達式模式中有分組匹配,那麼相關匹配會作爲參數傳入方法。
在一個請求結束的時候肯定會進行Etag的處理,所以找到調用的 finish() 函數:

finish() 函數 ---- 地址:tornado/web.py(刪除了部分不在此主題的代碼)

  def finish(self, chunk=None):
      # Automatically support ETags and add the Content-Length header if
      # we have not flushed any content yet.
      if not self._headers_written:
          if (self._status_code == 200 and
              self.request.method in ("GET", "HEAD") and
                  "Etag" not in self._headers):
              self.set_etag_header()
              if self.check_etag_header():
                 self._write_buffer = []
                 self.set_status(304)
          if self._status_code in (204, 304):
             assert not self._write_buffer, "Cannot send body with %s" % self._status_code
             self._clear_headers_for_304()
          elif "Content-Length" not in self._headers:
             content_length = sum(len(part) for part in self._write_buffer)
             self.set_header("Content-Length", content_length)

分析:
  在調用 finish() 函數的時候,對HTTP請求進行判斷,如果 狀態碼爲200,請求的方法爲 GET 或 HEAD,並且 Etag 不在HTTP頭信息裏面,則說明該請求是第一次發生。接下來,調用 set_etag_header() 函數,將 etag 寫入到 header頭信息中

set_etag_header() 函數 ---- 地址:tornado/web.py

def set_etag_header(self):
    etag = self.compute_etag()
    if etag is not None:
        self.set_header("Etag", etag)

分析:
  接着調用 compute_etag() 函數生成 etag,如果返回成功,則調用 set_header() 函數將 etag 寫入header頭信息的 “Etag” 字段。查看 compute_etag() 函數:

compute_etag() 函數 ---- 地址:tornado/web.py

def compute_etag(self):
    hasher = hashlib.sha1()
    for part in self._write_buffer:
        hasher.update(part)
    return '"%s"' % hasher.hexdigest()

分析:
  這裏通過 調用 hashlib庫 生成相應的 etag,然後通過對於 self._write_buffer的循環,當服務端文件有改變的時候,調用hashlib中的 update() 函數更新生成的新的對象 hasher,從而返回最新的 etag
  注:self._write_buffer在初始化的時候已經進行了定義 self._write_buffer = [ ], 如果某一個頁面有改變,則會進行記錄,從而來判斷是否客戶端請求的頁面在服務端是否有改變,這裏對於 etag 的生成函數 set_etag_header() 函數已經介紹完了,接着進行 check_etag_header() 校驗函數的分析:

check_etag_header() 校驗函數 ---- 地址:tornado/web.py

 def check_etag_header(self):
     etags = re.findall(
         br'\*|(?:W/)?"[^"]*"',
         utf8(self.request.headers.get("If-None-Match", ""))
     )
     if not computed_etag or not etags:
         return False
 
     match = False
     if etags[0] == b'*':
         match = True
     else:
         # Use a weak comparison when comparing entity-tags.
         def val(x):
             return x[2:] if x.startswith(b'W/') else x
 
         for etag in etags:
             if val(etag) == val(computed_etag):
                 match = True
                 break
     return match

分析:
  首先服務端獲取 客戶端發送過來的 header頭信息 中的 “If-None-Match” 字段,拿到該 etag,並通過正則表達式匹配,查看是否跟服務端保存的etag相同。如果 沒有獲取到header頭信息中的 etag字段或跟服務端etag不匹配,則返回 False,否認返回 True。
  之後,如果該 check_etag_header() 函數 返回True 的話, 則說明,該請求中包含有該 etag 並且該etag和服務端保存的相同,接下來t通過 self._write_buffer = [ ] 對這個字段進行清空處理(表明該請求的頁面暫時沒有任何修改), 並且返回 狀態碼304 給客戶端。

解決304問題

刪除請求頭中的'If-None-Match'

del self.request.headers['If-None-Match']

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章