• Tornado框架路由系统介绍及(IOloop.current().start())启动源码分析


    Tornado框架路由系统介绍及(IOloop.current().start())启动源码分析

    1、前言

    众所周知,Django、Flask、Tornado是非常受欢迎的三大Web开发框架,Django大而全、flask小而精、Tornado性能高。那具体他们到底有哪些的优势?在各种项目上又改该如何选择?

    Django:
    	Django走的大而全的方向,开发效率高。它的MTV框架,自带的ORM,admin后台管理,自带的sqlite数据库和开发测试用的服务器,给开发者提高了超高的开发效率。
        重量级web框架,功能齐全,提供一站式解决的思路,能让开发者不用在选择上花费大量时间
        自带ORM和模板引擎,支持jinja等非官方模板引擎。
        自带ORM使Django和关系型数据库耦合度高,如果要使用非关系型数据库,需要使用第三方库
        自带数据库管理app
        成熟,稳定,开发效率高,相对于Flask,Django的整体封闭性比较好,适合做企业级网站的开发。python web框架的先驱,第三方库丰富
    Flask:
    	Flask是轻量级的框架,自由、灵活、可扩展性强,核心基于Werkzeug WSGI工具和jinja2模板引擎。
        适用于做小网站以及web服务的API,开发大型网站无压力,但架构需要自己设计。
        与关系型数据库的结合不弱于Django,而与非关系型数据库的结合远远优于Django。
    Tornado:
    	Tornado走的是少而精的方向,性能优越,它最出名的异步非阻塞的设计方式。
        Tornado的两大核心模块:
        iostraem:对非阻塞的socket进行简单的封装。
        ioloop:对I/O多路复用的封装,它实现一个单例。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2、tornado介绍

    Tornado 是一个Python web框架和异步网络库 起初由 FriendFeed 开发. 通过使用非阻塞网络I/O, Tornado 可以支持上万级的连接,处理 长连接, WebSockets, 和其他 需要与每个用户保持长久连接的应用.
    Tornado 大体上可以被分为4个主要的部分:
        web框架 (包括创建web应用的 RequestHandler 类,还有很多其他支持的类).
        HTTP的客户端和服务端实现 (HTTPServer and AsyncHTTPClient).
        异步网络库 (IOLoop and IOStream), 为HTTP组件提供构建模块,也可以用来实现其他协议.
        协程库 (tornado.gen) 允许异步代码写的更直接而不用链式回调的方式.
    Tornado web 框架和HTTP server 一起为 WSGI 提供了一个全栈式的选择. 在WSGI容器 (WSGIAdapter) 中使用Tornado web框架或者使用Tornado HTTP server 作为一个其他WSGI框架(WSGIContainer)的容器,这样的组合方式都是有局限性的. 为了充分利用Tornado的特性,你需要一起使用Tornado的web框架和HTTP server.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3、helloworld

    import tornado.ioloop
    from tornado import web
    
    """
    settings = {
        "debug":True}
    """
    # 注意:
    # 	一旦在settings内将debug设置为True就必须以debug模式启动项目。
    # 	且尽量不要终止项目,一旦终止项目再重新启动就要将之前的进程终止,否则会报端口占用的错误。
    
    class MainHandler(web.RequestHandler):
        def get(self):
            self.write("Hello World!")
    
    def make_app():
        return web.Application([
            (r"/",MainHandler)
        ])
    
    if __name__ == "__main__":
        application = make_app()
        application.listen(8888)
        print("Tornado is starting %s" %'http://localhost:8888')
        tornado.ioloop.IOLoop.current().start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在上面的代码中能够看到,首先定义了一个MainHandler请求处理类下面的get方法就是代表在浏览器中发送的get请求,函数make_app则更像是一个路由注册函数,与Flask,Django的路由注册没有太大差别。调用make_app()会返回Application类的实例化对象,对象调用listen方法设置监听端口,重点则是在tornado.ioloop.IOLoop.current().start()这一句start()启动框架

    4、Application路由注册源码分析

    Application类初始化方法参数介绍

    class Application(ReversibleRouter):
        """
        一个组成web application的request handler(请求处理器)的集合
    
        """
        def __init__(self, handlers=None, default_host=None, transforms=None,
                     **settings):
            # 输出的分块与压缩
            # 设置相应头部的Content-Encoding和Transfer-Encoding
            if transforms is None:
                self.transforms = []
                if settings.get("compress_response") or settings.get("gzip"):
                    self.transforms.append(GZipContentEncoding)
            else:
                self.transforms = transforms
            # 默认主机(host)
            self.default_host = default_host
            # 加载所有传入的settings到self.settings
            self.settings = settings
            # ui_modules和ui_methods都是模板相关的属性
            self.ui_modules = {'linkify': _linkify,
                               'xsrf_form_html': _xsrf_form_html,
                               'Template': TemplateModule,
                               }
            self.ui_methods = {}
            #获取获取用户自定义的ui_modules和ui_methods,并将他们添加到之前创建的成员变量self.ui_modules和self.ui_methods中
            self._load_ui_modules(settings.get("ui_modules", {}))
            self._load_ui_methods(settings.get("ui_methods", {}))
            # 检查settings中有没有设置static_path(静态文件路径)
            if self.settings.get("static_path"):
                # 读取static_path用于配置静态文件路径
                path = self.settings["static_path"]
                # 将传入的handler转换为list
                handlers = list(handlers or [])
                # 静态文件前缀
                static_url_prefix = settings.get("static_url_prefix",
                                                 "/static/")
                # 静态文件处理类(默认StaticFileHandler)
                static_handler_class = settings.get("static_handler_class",
                                                    StaticFileHandler)
                # 静态文件处理参数
                static_handler_args = settings.get("static_handler_args", {})
                # 将静态文件地址加入处理参数中
                static_handler_args['path'] = path
                #在参数中传入的handlers前再添加三个映射:
                #【/static/.*】            -->  StaticFileHandler
                #【/(favicon\.ico)】    -->  StaticFileHandler
                #【/(robots\.txt)】        -->  StaticFileHandler
                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))
            
            # 检测是否开启debug模式
            if self.settings.get('debug'):
                # 开启后进行下面设置
                # 自动重载
                self.settings.setdefault('autoreload', True)
                # 取消编译模板缓存
                self.settings.setdefault('compiled_template_cache', False)
                # 取消静态文件的hash值缓存
                self.settings.setdefault('static_hash_cache', False)
                # 提供处理跟踪信息
                self.settings.setdefault('serve_traceback', True)
    
            # 添加路由(_ApplicationRouter)
            self.wildcard_router = _ApplicationRouter(self, handlers)
            self.default_router = _ApplicationRouter(self, [
                Rule(AnyMatches(), self.wildcard_router)
            ])
    
            # Automatically reload modified modules
            # 自动重载
            if self.settings.get('autoreload'):
                from tornado import autoreload
                autoreload.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    个人是习惯先看路由注册的Application,在这里只挑重点部分的代码进行分析

    class Application(ReversibleRouter):
        def __init__(self, handlers=None, default_host=None, transforms=None,
                     **settings):
           ········
        	self.wildcard_router = _ApplicationRouter(self, handlers)
            """
             _ApplicationRouter(self, handlers)会将Application类本身与handlers([(r"/",MainHandler)])传递给_ApplicationRouter类
             class _ApplicationRouter(ReversibleRuleRouter):
             	 def __init__(self, application, rules=None):
                     assert isinstance(application, Application)
                     self.application = application
                     super(_ApplicationRouter, self).__init__(rules)
             _ApplicationRouter类在初始化的时候,会调用父类ReversibleRuleRouter的__init__()方法
             class ReversibleRuleRouter(ReversibleRouter, RuleRouter):
                 def __init__(self, rules=None):
                    self.named_rules = {}  # type: typing.Dict[str]
                    super(ReversibleRuleRouter, self).__init__(rules)
             ReversibleRuleRouter在执行__init__()方法时,又会调用父类RuleRouter的__init__()方法
             class RuleRouter(Router):
             	 def __init__(self, rules=None):
             	 	self.rules = []  # type: typing.List[Rule]
            		if rules:
                		self.add_rules(rules) # 调用RuleRouter类下的add_rules方法
                 def add_rules(self, rules): # rules = [(r"/",MainHandler)]
                     for rule in rules:      # rule = (r"/",MainHandler)
                		if isinstance(rule, (tuple, list)): # 判断rule是不是属于tuple类或者list类
                    		assert len(rule) in (2, 3, 4)   # 对rule的长度进行断言,长度只能在(2, 3, 4)这个范围
                    		if isinstance(rule[0], basestring_type): # 判断路由的第一个元素是否属于str类
                    			'''
                    			PathMatches(rule[0])类,对"/"尾部加入正则结尾符"$"使其变成"/$",PathMatches主要是用正则表达式匹配指定请求
                    			*rule[1:]取到的就是MainHandler对象。并且Rule的初始化方法中会对对传入的Handler处理类进行校验,如果类属于							 str则会通过import_object()方法将其转成实例化的类(import_object('x')就相当于'import x'.),具体可在tornado.util中佛的import_object()
                    			方法查看.
                    			此时Rule(, , kwargs={}, name=None)
                    			'''
                        		rule = Rule(PathMatches(rule[0]), *rule[1:])
                    		else:
                        		rule = Rule(*rule)
                    '''
                    注意此时的process_rule方法,在下面的代码中self.process_rule(rule)可没有调用RuleRouter本身的process_rule,始终要记住这个self是_ApplicationRouter
                    类,所以他调用的是_ApplicationRouter类的process_rule方法。看如下代码
                    def process_rule(self, rule):
                    	# 此代码又会调用_ApplicationRouter的父类ReversibleRuleRouter中的process_rule方法
                    	# 在ReversibleRuleRouter中的process_rule方法同样是调用父类RuleRouter中的process_rule,但是父类RuleRouter里process_rule并没有对rule做任何操
                    	# 作,只是留着给子类继承并重写,所以重点就是在ReversibleRuleRouter中的process_rule,他主要做了对rule.name的操作,rule.name存在就判断其在不在
                    	# named_rules中,如果在就打印错误日志’有多个headlers处理程序,将替换之前的‘,就是与以前的headler程序做个比较。如果不在就直接返回。返回后会继续执行
                    	# 下面代码,判断rule.target是不是属于list或者tuple,正常来说rule.target就是上面的headler对象,如果是则会重新再走一遍上面的代码重新校验,不是直接返回
            			rule = super(_ApplicationRouter, self).process_rule(rule)
            			if isinstance(rule.target, (list, tuple)):
                			rule.target = _ApplicationRouter(self.application, rule.target)
            			return rule
            		下面代码跑完这里会将所有注册的路由都遍历一遍,重复上面的代码,直到遍历完所有的路由
                    '''
                	self.rules.append(self.process_rule(rule))
            然后所有的路由都会放入self.wildcard_router.rules下,以列表嵌套元组的形式存储如下所示:
            self.wildcard_router.rules = [Rule(, , kwargs={}, 			name=None),......等等其他的路由]
            """
            # 这个是tornado在做默认路由,不必太多关心,实际上也就是_ApplicationRouter本身,如下所示:
            # self.default_router.rules = [Rule(, , kwargs={}, name=None)]
            self.default_router = _ApplicationRouter(self, [
                Rule(AnyMatches(), self.wildcard_router)
            ])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    5、官方指定的几种路由实现规则

    tornado内部提供了多种灵活的路由实现方法,开发者可以根据框架内路由规则进行多样的选择一个或者多个适合项目的路由,当然tornado也为开发者提供了自己实现路由类的方法(需要继承Router基类或者Router的子类。且内部必须要实现find_handler方法,用来提供一个合适的httputil.HTTPMessageDelegate实例来处理请求)

    1、自己实现路由类

    class GetResource(RequestHandler):
        def get(self, path):
            if path not in resources:
                raise HTTPError(404)
            self.finish(resources[path])
    
    class PostResource(RequestHandler):
        def post(self, path):
            resources[path] = self.request.body
    
    class HTTPMethodRouter(Router):
        def __init__(self, app):
            self.app = app
    
        def find_handler(self, request, **kwargs):
            handler = GetResource if request.method == "GET" else PostResource
            return self.app.get_handler_delegate(request, handler, path_args=[request.path])
    
    router = HTTPMethodRouter(Application())
    server = HTTPServer(router)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2、httputil.HTTPServerConnectionDelegate的实例

    router = RuleRouter([
        Rule(PathMatches("/handler"), ConnectionDelegate()),
        # …… 更多路由
    ])
    
    class ConnectionDelegate(http.HTTPServerConnectionDelegate):
        def start_request(self,server_conn,request_conn):
            return MessageDelegate(request_conn)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3、一个可调用并接受一个 HTTPServerRequest 类参数的对象

    router = RuleRouter([
            Rule(PathMatches("/callable"), request_callable)
        ])
    def request_callable(request):
        request.write(b"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nOK")
        request.finish()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4、RuleRouter内嵌套RuleRoute或Application也是可以的

    router = RuleRouter([
        Rule(HostMatches("example.com"), RuleRouter([
                Rule(PathMatches("/app1/.*"), Application([(r"/app1/handler", Handler)]))),
        ]))
    ])
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5、路由分发

    app1 = Application([
        (r"/app1/handler",HandlerClass1)
        # other router
    ])
    apps = Application([
        (r"/app1/handler",HandlerClasss)
        # other router
    ])
    
    router = RuleRouter([
        Rule(PathMatches("/app1.*"),app1),
        Rule(PathMatches("/app2.*"),app2),
    ])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    6、初始化HTTPServer

    由上面代码可知,再application实例初始化之后,application调用listen方法

    • application.listen源码
    def listen(self, port, address="", **kwargs):
        """
        在给定端口上为此应用程序启动一个 HTTP服务
        """
        from tornado.httpserver import HTTPServer
        # 创建一个HTTPServer的实例
        server = HTTPServer(self, **kwargs)
        # 调用实例的listen方法,并将端口与地址传给listen。所以由此可看出listen方法实际上是HTTPServer的父类TCPServer下的
        server.listen(port, address)
        # # 返回这个HTTPServer实例
        return server
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 官方HTTPServe注解的源码
    1、listen: 简单的单个进程
        server = TCPServer()
        server.listen(8888)
        IOLoop.current().start()
        
    2、bind/start: 简单的多个进程
        server = TCPServer()
        server.bind(8888)
        server.start(0) # 多个子进程数量
        IOLoop.current().start()
        
    3、add_sockets: 高级的多个进程
        sockets = bind_sockets(8888)
        torbado.process.fork_process(0) # 子进程数量
        server = TCPServer()
        server.add_sockets(sockets)
        IOLoop.current().start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    9、IOloop.current().start()启动源码分析

    # 待更新
    
    • 1
  • 相关阅读:
    只有线上出了bug,老板们才知道测试的价值?
    Spring AOP用法(待完善)
    提供话费充值接口 话费快充慢充/API独立接口,商城/小程序/公众号合作
    2022年12月全国DAMA-CDGA/CDGP数据治理认证招生简章
    【排序15:多数元素 II】
    天龙八部科举答题问题和答案(全8/8)
    初识设计模式 - 解释器模式
    激光雷达(LiDAR)技术
    Revit翻模技巧丨怎么一次性翻转所有墙体?
    Spark Overview
  • 原文地址:https://blog.csdn.net/prigilm/article/details/126054064