tornado框架源码分析---Application类之debug参数

先贴上Application这个类的源码。

class Application(httputil.HTTPServerConnectionDelegate):
    """A collection of request handlers that make up a web application.

    Instances of this class are callable and can be passed directly to
    HTTPServer to serve the application::

        application = web.Application([
            (r"/", MainPageHandler),
        ])
        http_server = httpserver.HTTPServer(application)
        http_server.listen(8080)
        ioloop.IOLoop.instance().start()

    The constructor for this class takes in a list of `URLSpec` objects
    or (regexp, request_class) tuples. When we receive requests, we
    iterate over the list in order and instantiate an instance of the
    first request class whose regexp matches the request path.
    The request class can be specified as either a class object or a
    (fully-qualified) name.

    Each tuple can contain additional elements, which correspond to the
    arguments to the `URLSpec` constructor.  (Prior to Tornado 3.2, this
    only tuples of two or three elements were allowed).

    A dictionary may be passed as the third element of the tuple,
    which will be used as keyword arguments to the handler‘s
    constructor and `~RequestHandler.initialize` method.  This pattern
    is used for the `StaticFileHandler` in this example (note that a
    `StaticFileHandler` can be installed automatically with the
    static_path setting described below)::

        application = web.Application([
            (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
        ])

    We support virtual hosts with the `add_handlers` method, which takes in
    a host regular expression as the first argument::

        application.add_handlers(r"www\.myhost\.com", [
            (r"/article/([0-9]+)", ArticleHandler),
        ])

    You can serve static files by sending the ``static_path`` setting
    as a keyword argument. We will serve those files from the
    ``/static/`` URI (this is configurable with the
    ``static_url_prefix`` setting), and we will serve ``/favicon.ico``
    and ``/robots.txt`` from the same directory.  A custom subclass of
    `StaticFileHandler` can be specified with the
    ``static_handler_class`` setting.

    """
    def __init__(self, handlers=None, default_host="", transforms=None,
                 **settings):
        if transforms is None:
            self.transforms = []
            if settings.get("compress_response") or settings.get("gzip"):
                self.transforms.append(GZipContentEncoding)
        else:
            self.transforms = transforms
        self.handlers = []
        self.named_handlers = {}
        self.default_host = default_host
        self.settings = settings
        self.ui_modules = {linkify: _linkify,
                           xsrf_form_html: _xsrf_form_html,
                           Template: TemplateModule,
                           }
        self.ui_methods = {}
        self._load_ui_modules(settings.get("ui_modules", {}))
        self._load_ui_methods(settings.get("ui_methods", {}))
        if self.settings.get("static_path"):
            path = self.settings["static_path"]
            handlers = list(handlers or [])
            static_url_prefix = settings.get("static_url_prefix",
                                             "/static/")
            static_handler_class = settings.get("static_handler_class",
                                                StaticFileHandler)
            static_handler_args = settings.get("static_handler_args", {})
            static_handler_args[path] = path
            for pattern in [re.escape(static_url_prefix) + r"(.*)",
                            r"/(favicon\.ico)", r"/(robots\.txt)"]:
                handlers.insert(0, (pattern, static_handler_class,
                                    static_handler_args))
        if handlers:
            self.add_handlers(".*$", handlers)

        if self.settings.get(debug):
            self.settings.setdefault(autoreload, True)
            self.settings.setdefault(compiled_template_cache, False)
            self.settings.setdefault(static_hash_cache, False)
            self.settings.setdefault(serve_traceback, True)

        # Automatically reload modified modules
        if self.settings.get(autoreload):
            from tornado import autoreload
            autoreload.start()

    def listen(self, port, address="", **kwargs):
        """Starts an HTTP server for this application on the given port.

        This is a convenience alias for creating an `.HTTPServer`
        object and calling its listen method.  Keyword arguments not
        supported by `HTTPServer.listen <.TCPServer.listen>` are passed to the
        `.HTTPServer` constructor.  For advanced uses
        (e.g. multi-process mode), do not use this method; create an
        `.HTTPServer` and call its
        `.TCPServer.bind`/`.TCPServer.start` methods directly.

        Note that after calling this method you still need to call
        ``IOLoop.instance().start()`` to start the server.
        """
        # import is here rather than top level because HTTPServer
        # is not importable on appengine
        from tornado.httpserver import HTTPServer
        server = HTTPServer(self, **kwargs)
        server.listen(port, address)

    def add_handlers(self, host_pattern, host_handlers):
        """Appends the given handlers to our handler list.

        Host patterns are processed sequentially in the order they were
        added. All matching patterns will be considered.
        """
        if not host_pattern.endswith("$"):
            host_pattern += "$"
        handlers = []
        # The handlers with the wildcard host_pattern are a special
        # case - they‘re added in the constructor but should have lower
        # precedence than the more-precise handlers added later.
        # If a wildcard handler group exists, it should always be last
        # in the list, so insert new groups just before it.
        if self.handlers and self.handlers[-1][0].pattern == .*$:
            self.handlers.insert(-1, (re.compile(host_pattern), handlers))
        else:
            self.handlers.append((re.compile(host_pattern), handlers))

        for spec in host_handlers:
            if isinstance(spec, (tuple, list)):
                assert len(spec) in (2, 3, 4)
                spec = URLSpec(*spec)
            handlers.append(spec)
            if spec.name:
                if spec.name in self.named_handlers:
                    app_log.warning(
                        "Multiple handlers named %s; replacing previous value",
                        spec.name)
                self.named_handlers[spec.name] = spec

    def add_transform(self, transform_class):
        self.transforms.append(transform_class)

    def _get_host_handlers(self, request):
        host = request.host.lower().split(:)[0]
        matches = []
        for pattern, handlers in self.handlers:
            if pattern.match(host):
                matches.extend(handlers)
        # Look for default host if not behind load balancer (for debugging)
        if not matches and "X-Real-Ip" not in request.headers:
            for pattern, handlers in self.handlers:
                if pattern.match(self.default_host):
                    matches.extend(handlers)
        return matches or None

    def _load_ui_methods(self, methods):
        if isinstance(methods, types.ModuleType):
            self._load_ui_methods(dict((n, getattr(methods, n))
                                       for n in dir(methods)))
        elif isinstance(methods, list):
            for m in methods:
                self._load_ui_methods(m)
        else:
            for name, fn in methods.items():
                if not name.startswith("_") and hasattr(fn, "__call__")                         and name[0].lower() == name[0]:
                    self.ui_methods[name] = fn

    def _load_ui_modules(self, modules):
        if isinstance(modules, types.ModuleType):
            self._load_ui_modules(dict((n, getattr(modules, n))
                                       for n in dir(modules)))
        elif isinstance(modules, list):
            for m in modules:
                self._load_ui_modules(m)
        else:
            assert isinstance(modules, dict)
            for name, cls in modules.items():
                try:
                    if issubclass(cls, UIModule):
                        self.ui_modules[name] = cls
                except TypeError:
                    pass

    def start_request(self, connection):
        # Modern HTTPServer interface
        return _RequestDispatcher(self, connection)

    def __call__(self, request):
        # Legacy HTTPServer interface
        dispatcher = _RequestDispatcher(self, None)
        dispatcher.set_request(request)
        return dispatcher.execute()

    def reverse_url(self, name, *args):
        """Returns a URL path for handler named ``name``

        The handler must be added to the application as a named `URLSpec`.

        Args will be substituted for capturing groups in the `URLSpec` regex.
        They will be converted to strings if necessary, encoded as utf8,
        and url-escaped.
        """
        if name in self.named_handlers:
            return self.named_handlers[name].reverse(*args)
        raise KeyError("%s not found in named urls" % name)

    def log_request(self, handler):
        """Writes a completed HTTP request to the logs.

        By default writes to the python root logger.  To change
        this behavior either subclass Application and override this method,
        or pass a function in the application settings dictionary as
        ``log_function``.
        """
        if "log_function" in self.settings:
            self.settings["log_function"](handler)
            return
        if handler.get_status() < 400:
            log_method = access_log.info
        elif handler.get_status() < 500:
            log_method = access_log.warning
        else:
            log_method = access_log.error
        request_time = 1000.0 * handler.request.request_time()
        log_method("%d %s %.2fms", handler.get_status(),
                   handler._request_summary(), request_time)

这里贴上一个官方的文档 http://www.tornadoweb.org/en/stable/web.html?highlight=application#tornado.web.Application 链接

tornado框架有一个web模块,里面有一个application类,在利用这个框架开发网站的时候,有时我们需要不断地修改代码以及在浏览器上调试,会发现这样做时需要频繁的关闭和重启自己的服务器,否则无法看到修改代码后的情况。我们在想有没有一个办法让代码实现动态编译(这里不知道用编译正不正确,毕竟python是解释性的代码),让自己修改了代码以后直接刷新浏览器的页面就可以调试新的效果,不需要重启服务器呢?

tornado框架允许你在实例化一个application类的时候可以给它提供一个debug=True参数,它调用了一个便利的测试模式:tornado.autoreload模块,此时,一旦主要的Python文件被修改,Tornado将会尝试重启服务器,并且在模板改变时会进行刷新。在开发网站的时候这是非常好的一个功能,但不要在生产上使用它,因为它将防止Tornado缓存模板。

 

下面让我们截取上面application类的构造函数其中的一部分

    def __init__(self, handlers=None, default_host="", transforms=None,
                 **settings):

    """此处我们略去其他不需要分析的部分"""

        if self.settings.get(debug):
            self.settings.setdefault(autoreload, True)
            self.settings.setdefault(compiled_template_cache, False)
            self.settings.setdefault(static_hash_cache, False)
            self.settings.setdefault(serve_traceback, True)

        # Automatically reload modified modules
        if self.settings.get(autoreload):
            from tornado import autoreload
            autoreload.start()

首先,构造函数中有一个**settings参数,在python中是一种关键字参数,关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。例如:

def person(name, age, **kw):
    print name:, name, age:, age, other:, kw

>>> person(Bob, 35, city=Beijing)
name: Bob age: 35 other: {city: Beijing}

那么当我们给application传递参数debug=True时

tornado.web.Application(
        handlers=[(r/, IndexHandler)],
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
        debug=True
    )

settings 将为 {‘debug‘: ‘True‘},

我们在构造函数中可以看到这部分

        if self.settings.get(debug):
            self.settings.setdefault(autoreload, True)
            self.settings.setdefault(compiled_template_cache, False)
            self.settings.setdefault(static_hash_cache, False)
            self.settings.setdefault(serve_traceback, True)

它的意思就是当setting参数中有debug被设置且为True时,执行下面四条语句,将 autoreload、serve_traceback设置为True,而将compiled_template_cache、static_hash_cache设置为False,那么这四个参数是什么意思呢?

下面先找出官方文档的解释

  • autoreload: If True, the server process will restart when any source files change, as described in Debug mode and automatic reloading. This option is new in Tornado 3.2; previously this functionality was controlled by the debug setting.
  • compiled_template_cache: Default is True; if False templates will be recompiled on every request. This option is new in Tornado 3.2; previously this functionality was controlled by the debug setting.
  • static_hash_cache: Default is True; if False static urls will be recomputed on every request. This option is new in Tornado 3.2; previously this functionality was controlled by the debug setting.
  • serve_traceback: If true, the default error page will include the traceback of the error. This option is new in Tornado 3.2; previously this functionality was controlled by thedebug setting.

1、autoreload:这个参数的意思是当源代码(.py文件)改变的时候,服务器进程将自动重启。从tornado的3.2版本起,可以直接设置settings参数为autoreload=True来启动这个模块,构造函数中有下面这几行代码可以看出

        # Automatically reload modified modules
        if self.settings.get(autoreload):
            from tornado import autoreload
            autoreload.start()

2、compiled_template_cache:当这个参数是False时,每一次浏览器向服务器发出请求时,服务器下的模板都将重新编译。

3、static_hash_cache:当这个参数是False时,代码中使用了static_url()函数的地方都将被重新计算,因为每次调用static_url函数时它都创建了一个基于文件内容的hash值,并将其添加到URL末尾(查询字符串的参数v)。这个hash值确保浏览器总是加载一个文件的最新版而不是之前的缓存版本。意思就是说如果你的static文件内容改变的话,那么产生的hash值也将改变,浏览器将会发现这样的变化,从而重新载入需要读取的static下的文件,而不是用自己已经缓存了的文件。

4、serve_traceback:traceback是python的处理异常栈的模块,我们在写python代码的时候,如果出错的了话,可以看到爆出一大堆错误,例如错误中可以追溯到错误地调用python标准库的一些函数,我们在开发网站的时候如果遇到错误,一般浏览器只会返回404not found, 505等错误信息,而代码的错误将在服务器终端显示出来,当这个serve_traceback被设置为True之后,我们就可以发现代码的错误也在浏览器上显示出来了。

 

现在,通过分析我们可以看到,在利用tornado框架开发的阶段,当我们在实例化一个application这个类的时候,不妨加上debug=True这个参数,使我们的代码和模板能实行动态编译,而不必频繁重启我们的服务器,这将非常有效地提高我们的开发及调试的效率。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。