tornado: web.py 之 RequestHandler

RequestHandler这个类有超过1000行,看似十一个庞然大物,其实大多是注释和空方法,总体读起来非常容易。

这个类也是blog项目中所有handler的父类,想必大多数tornado项目也是如此,当然顾名思义这个类以及它的派生类用来处理tornado web server收到的httprequest,目前为止还没有看到httpserver的代码,无责任随便猜测一下,一个httprequest处理的大概流程:

  1. Httpserver收到请求
  2. 扔给Application实例去处理(调用__call__)
  3. Application的实例根据初始化时(或者后来通过add_handler添加的)路由表,生成相应的handler实例,把request扔进去进行处理。

目前默认tornado支持如下几种方法:

SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT",
                         "OPTIONS")

 貌似比几年前看得REST要多好几个啊。。。。head,patch,options是什么情况?

这个类因为是基础类,所以所以定义了一堆空方法,有几个方法值得拿出来单独列在这里的(虽然在基类中并没有任何实现)

def prepare(self):
        """Called at the beginning of a request before `get`/`post`/etc.

        Override this method to perform common initialization regardless
        of the request method.
        """
        pass

    def on_finish(self):
        """Called after the end of a request.

        Override this method to perform cleanup, logging, etc.
        This method is a counterpart to `prepare`.  ``on_finish`` may
        not produce any output, as it is called after the response
        has been sent to the client.
        """
        pass

    def on_connection_close(self):
        """Called in async handlers if the client closed the connection.

        Override this to clean up resources associated with
        long-lived connections.  Note that this method is called only if
        the connection was closed during asynchronous processing; if you
        need to do cleanup after every request override `on_finish`
        instead.

        Proxies may keep a connection open for a time (perhaps
        indefinitely) after the client has gone away, so this method
        may not be called promptly after the end user closes their
        connection.
        """
        pass

 基本上提供了某种程度上的callback,俺没有任何tornado经验,不知到有多少项目中真正用到了。

 比较诡异的是,这个类里面定义了一些看起来重复的变量,比如下面两个

# UIModules are available as both `modules` and `_modules` in the
        # template namespace.  Historically only `modules` was available
        # but could be clobbered by user additions to the namespace.
        # The template {% module %} directive looks in `_modules` to avoid
        # possible conflicts.
        self.ui["_modules"] = ObjectDict((n, self._ui_module(n, m)) for n, m in
                                 application.ui_modules.iteritems())
        self.ui["modules"] = self.ui["_modules"]

还有这两个方法中用到的两个成员变量。。。

def set_header(self, name, value):
        """Sets the given response header name and value.

        If a datetime is given, we automatically format it according to the
        HTTP specification. If the value is not a string, we convert it to
        a string. All header values are then encoded as UTF-8.
        """
        self._headers[name] = self._convert_header_value(value)

    def add_header(self, name, value):
        """Adds the given response header and value.

        Unlike `set_header`, `add_header` may be called multiple times
        to return multiple values for the same header.
        """
        self._list_headers.append((name, self._convert_header_value(value)))
  

尼嘛意义何在啊啊。。。还没看到后面,暂时还理解不了。

然后下面是一些已经实现的方法,其中比较重要的一个个拿出来说说

  • set_cookie
    在handle中初始化一个名为_new_cookie的SimpleCookie实例,然后根据传入的参数设置domain, expires, path, max-age, 以及其他用户自定义的cookie pair
  • clear_cookie
    这个方法挺有意思,clear的意思并不是“clear”,而是通过设置cookie的expire time让客户端浏览器中把cookie删除,而不是server端(本来清除cookie就是应该这么干吧?)
  • set_secure_cookie
    这个方法会用Application.Settings中设置的secret来产生经过加密的cookie
  • get_secure_cookie
    这是上面方法的配对方法
  • redirect
    设置response header中的location,并设置http status为3xx,这样的response一旦返回,浏览器默认会触发重定向到location中指定的url (看成中指的请自觉面壁)
  • write
    这个方法的注释说的很清楚了。。。
    """Writes the given chunk to the output buffer.
    
            To write the output to the network, use the flush() method below.
    
            If the given chunk is a dictionary, we write it as JSON and set
            the Content-Type of the response to be application/json.
            (if you want to send JSON as a different Content-Type, call
            set_header *after* calling write()).
    
            Note that lists are not converted to JSON because of a potential
            cross-site security vulnerability.  All JSON output should be
            wrapped in a dictionary.  More details at
            http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
            """
  • render
    这是个很重要函数,用来根据指定的模板生成最终的渲染页面。因为太长了,所以我跳过去了,以后回头看。
  • render_string
    这也是一个很重要的函数,不太长,但是看了一下。。。没看懂。。。主要是没明白template神码的是怎么回事。。其中一个term叫做template的namespace,我怎么看怎么不应该叫namespace,似乎叫context更加合适。。。。不信你看。。。
    namespace = 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.reverse_url)
        namespace.update(self.ui)
     
  • flush
    基本上这个方法会把buffer里面的东东弄出来调用注册的transform变成data chunk,生成header,最后调用httprequest的write方法写回到客户端
  • finish
    flush方法才真正的把数据写入request中(总觉的是不是用一个单独的response会比较不让人糊涂?),但是不会调用request的finish方法,而这些是在finish方法中调用的。所以一个request最后一个调用的一定是finish方法来结束请求。在finish方法内部也会调用flush,不过如果在finish之前调用过flush,行为可能是不确定的,大概会取决于传入flush方法的callback方法如何实现。但从代码来看,请不要在在代码中调用flush,这种粗重活,让finish来做好了。有一点需要注意的是,handler中某些方法的默认实现是会帮我们调用finish的,所以不用调用第二遍,否则会抛出exception,这些方法包括:
    1. redirect
    2. send_error
    3. write_error
  • send_error
    这个方法主要依赖write_error来产生错误页面返回用户
  • write_error
    看了一下代码,觉得自定义在handler中自定义一个get_error_html方法的做法比较靠谱
  • locale
    这个property在内部可能会调用两个不同的方法
    1. self.get_user_locale
    2. self.get_browser_locale
    其中第一个方法默认没有提供实现,不过可以由程序员重载。提供两个方法的主要目的大概是:第一个方法不去理会浏览器中的language参数,而是从某个其他的地方,例如从数据库中读取用户设置的语言偏好(常用代理的同学应该体会比较深);第二个方法顾名思义,从用户request的header中读取Accept-language来决定用户的locale。 只有第一个方法调用失败或者返回None的情况下才会调用第二个方法。
  • current_user
    获取当前用户信息。默认提供的时候屁事没干,所以如果你需要用这个方法来做用户身份认证,获取用户信息的话,需要重载self.get_current_user方法
  • get_login_url
    获得定义在Application.Settings中的get_login_url地址
  • get_template_path
    获得定义在Application.Settings中的get_template_path。这个路径应该是一个绝对路径,指向存放template文件的目录,或者返回None,用于从当前文件的相对路径开始查找
  • xsrf_token
    property获得xsrf_token,用于防止forgery攻击。这个xsrf_token会同时保存在用户浏览器的cookie中,以及当前handler的_xsrf_token属性中,用于对接下来用户post消息的验证。不过这个实现我有点儿糊涂--如果用户cookie被盗怎么办?在设置用户cookie的时候至少用set_secure_cookie吧?
  • check_xsrf_cookie
    检查参数中是否携带_xsrf, 或者header中包含X-Xsrftoken, X-Csrftoken。如果存在,再和handle中保存的值比较。
  • xsrf_form_html
    helper方法,生成一个hidden的form item代码片段,会随着form被post给server,也就是在check_xsrf_cookie中会检查的东东
  • _execute
    根据http类型分别调用handle中的get, post, delete, edit等方法

相关推荐