一、Tornado之架构概述图
二、Application类详细分析:
#!/usr/bin/env python # -*- coding: utf8 -*- # __Author: "Skiler Hao" # date: 2017/5/17 11:45 import tornado.web class Application(object): """A collection of request handlers that make up a web application. Application类是request的Handler的集合,组合是一个web应用的组成部分 Instances of this class are callable and can be passed directly to HTTPServer to serve the application: Application实例化之后就是可以调用的,也可以直接传递给HTTServer来为Web应用服务 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. 构造方法接受 URLSpec对象列表 或者 (正则,相应的处理请求的类)的元祖,web服务器收到request请求 接下来会按顺序,如果某个repexp正则匹配成功,就会实例化相应的类 Each tuple can contain an optional third element, which should be a dictionary if it is present. That dictionary is passed as keyword arguments to the contructor of the handler. This pattern is used for the StaticFileHandler below: 每个元祖都可以包含第三个可选的参数,这个第三个参数必须是一个字典,这个字典会作为关键字参数传递给hander的构造器, 下面声明了一个静态文件Handler application = web.Application([ (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}), ]) 还支持虚拟主机,通过add_handlers方法,需要把一个主机的正则表达式作为第一个参数 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. 同样可以提供静态文件的访问,需要把静态文件的设置作为关键字参数,我们可以提供/static/文件目录的文件 (在配置中static_url_prefix参数可以配置) """ def __init__(self, handlers=None, default_host="", transforms=None, wsgi=False, **settings): # 通常的配置都放在settings中 # 设置响应的编码和返回方式,对应的http相应头:Content-Encoding和Transfer-Encoding # Content-Encoding:gzip 表示对数据进行压缩,然后再返回给用户,从而减少流量的传输。 # Transfer-Encoding:chunck 表示数据的传送方式通过一块一块的传输。 if transforms is None: self.transforms = [] if settings.get("gzip"): self.transforms.append(GZipContentEncoding) self.transforms.append(ChunkedTransferEncoding) else: self.transforms = transforms # 将收到的参数赋值给实例变量,方便调用 self.handlers = [] self.named_handlers = {} self.default_host = default_host self.settings = settings self.ui_modules = {} self.ui_methods = {} self._wsgi = wsgi # 获取获取用户自定义的ui_modules和ui_methods self._load_ui_modules(settings.get("ui_modules", {})) self._load_ui_methods(settings.get("ui_methods", {})) # 设置静态文件路径,设置方式则是通过正则表达式匹配url,让StaticFileHandler来处理匹配的url if self.settings.get("static_path"): # 检查settings是否有static_path path = self.settings["static_path"] # 传递的参数是否有handlers,有的话转化成列表,没有的话是空列表 handlers = list(handlers or []) # 检查是否有static_url_prefix配置,没有的话使用默认/static/ static_url_prefix = settings.get("static_url_prefix", "/static/") # 在handlers追加上静态文件的handlers handlers = [ (re.escape(static_url_prefix) + r"(.*)", StaticFileHandler, dict(path=path)), (r"/(favicon\.ico)", StaticFileHandler, dict(path=path)), (r"/(robots\.txt)", StaticFileHandler, dict(path=path)), ] + handlers # 此时,handlers是一个列表,其中的每个元素都是一个对应关系,即:url正则表达式和处理匹配该正则的url的Handler # 执行Application类的add_handlers ,将 handlers加入进去 if handlers: self.add_handlers(".*$", handlers) # Automatically reload modified modules # 如果setting设置了debug模式,自动加载重新启动 if self.settings.get("debug") and not wsgi: import autoreload autoreload.start() def listen(self, port, address="", **kwargs): """Starts an HTTP server for this application on the given port. 在指定的端口启动一个HTTPserver应用 This is a convenience alias for creating an HTTPServer object and calling its listen method. Keyword arguments not supported by HTTPServer.listen are passed to the HTTPServer constructor. For advanced uses (e.g. preforking), do not use this method; create an HTTPServer and call its bind/start methods directly. 这是一个创建HTTPServer对象的快捷方式,会自动调用HTTPServer的listen方法 HTTPServer不支持关键字参数,listen回传给HTTPServer构造器。但是对于高级的应用(例如:Preforking),请不要使用本方法 可以创建一个HTTTPServer,然后调用它的bind和start方法 Note that after calling this method you still need to call IOLoop.instance().start() to start the server. 但是注意调用了此方法,还得调用IOLoop.instance().start()去启动服务 """ # import is here rather than top level because HTTPServer # is not importable on appengine # 在此处导入而不是最顶部,是由于HTTPServer在appengine层面不可导入 from tornado.httpserver import HTTPServer # 实例化HTTPServer对象 server = HTTPServer(self, **kwargs) # 调用HTTPServer的listen方法 server.listen(port, address) def add_handlers(self, host_pattern, host_handlers): """Appends the given handlers to our handler list. 追加给定的handlers到handler list中 Note that host patterns are processed sequentially in the order they were added, and only the first matching pattern is used. This means that all handlers for a given host must be added in a single add_handlers call. 注意主机模式的匹配是按加入的顺序依次执行的,只会使用第一次匹配到的模式 所以给定的主机所有handlers,只能通过调用add_handlers来追加 """ # 如果添加的pattern末尾不是以$结尾,主动加上 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 == '.*$': # 使用.* 这个通配符,还记得上面的init方法不。这些应该有更低的优先级相对于后面使用add_handers添加的 # 所以.* 应该放在新加入的后面,默认.*位置应该总是-1,所以只要insert(-1,)即可在.*前面 self.handlers.insert(-1, (re.compile(host_pattern), handlers)) else: # 如果没有的话,直接追加就可以,这个else一般木有可能,一般都得先init self.handlers.append((re.compile(host_pattern), handlers)) # 遍历我们设置的和构造函数中添加的【url->Handler】映射,将url和对应的Handler封装到URLSpec类中(构造函数中会对url进行编译) # 并将所有的URLSpec对象添加到handlers列表中,而handlers列表和主机名模型组成一个元祖,添加到self.Handlers列表中。 for spec in host_handlers: # 判断类型是不是元祖 if type(spec) is type(()): assert len(spec) in (2, 3) # 断言host_handlers 的长度应该是2或3,例如:(r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}) pattern = spec[0] handler = spec[1] if len(spec) == 3: # 将第三个放到kwargs kwargs = spec[2] else: kwargs = {} # 将数据封装成URLSpec对象 spec = URLSpec(pattern, handler, kwargs) # 让后将URLSpec对象放在handlers列表里 handlers.append(spec) # 如果URLSpec对象有名字,则追加到Application实例对象的named_handlers中 if spec.name: # 如果已经有了,调用logging模块输出多个handlers使用名字~~~,后面的将代替前面的 if spec.name in self.named_handlers: logging.warning( "Multiple handlers named %s; replacing previous value", spec.name) self.named_handlers[spec.name] = spec def add_transform(self, transform_class): """ Adds the given OutputTransform to our transform list. 添加OutputTransform到我们的transform列表中 """ self.transforms.append(transform_class) def _get_host_handlers(self, request): # 给定一个request可以返回是哪个handlers处理的 host = request.host.lower().split(':')[0] for pattern, handlers in self.handlers: if pattern.match(host): return handlers # Look for default host if not behind load balancer (for debugging) if "X-Real-Ip" not in request.headers: for pattern, handlers in self.handlers: if pattern.match(self.default_host): return handlers return None def _load_ui_methods(self, methods): """关于加载ui方法的""" if type(methods) is 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.iteritems(): 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): """关于加载ui模块的""" if type(modules) is 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.iteritems(): try: if issubclass(cls, UIModule): self.ui_modules[name] = cls except TypeError: pass def __call__(self, request): """ Called by HTTPServer to execute the request. HTTPServert调用来执行request请求的 """ transforms = [t(request) for t in self.transforms] handler = None args = [] kwargs = {} handlers = self._get_host_handlers(request) if not handlers: handler = RedirectHandler( self, request, url="http://" + self.default_host + "/") else: for spec in handlers: match = spec.regex.match(request.path) if match: # None-safe wrapper around urllib.unquote to handle # unmatched optional groups correctly def unquote(s): if s is None: return s return urllib.unquote(s) handler = spec.handler_class(self, request, **spec.kwargs) # Pass matched groups to the handler. Since # match.groups() includes both named and unnamed groups, # we want to use either groups or groupdict but not both. kwargs = dict((k, unquote(v)) for (k, v) in match.groupdict().iteritems()) if kwargs: args = [] else: args = [unquote(s) for s in match.groups()] break if not handler: handler = ErrorHandler(self, request, status_code=404) # In debug mode, re-compile templates and reload static files on every # request so you don't need to restart to see changes if self.settings.get("debug"): if getattr(RequestHandler, "_templates", None): for loader in RequestHandler._templates.values(): loader.reset() RequestHandler._static_hashes = {} handler._execute(transforms, *args, **kwargs) return handler def reverse_url(self, name, *args): """Returns a URL path for handler named `name` 给 名字 可以返回其URL路径 The handler must be added to the application as a named URLSpec """ if name in self.named_handlers: """ self.named_handlers['name'] = URLSpec URLSpec.reverse()方法,否则抛异常找不到 """ 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 = logging.info elif handler.get_status() < 500: log_method = logging.warning else: log_method = logging.error request_time = 1000.0 * handler.request.request_time() log_method("%d %s %.2fms", handler.get_status(), handler._request_summary(), request_time)