tornado处理get请求时持续返回304状态码

24次阅读

共计 2880 个字符,预计需要花费 8 分钟才能阅读完成。

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']

参考资料

正文完
 0