美文网首页工作生活
Django启动入口分析

Django启动入口分析

作者: naralv | 来源:发表于2019-07-03 03:52 被阅读0次

    manage.py是启动入口,在里面调用execute_from_command_line(sys.argv)方法

    def execute_from_command_line(argv=None):
        """Run a ManagementUtility."""
        utility = ManagementUtility(argv)
        utility.execute()
    

    ManagementUtility对象的execute()方法

    def execute(self):
       
        try:
            subcommand = self.argv[1]
        except IndexError:
            subcommand = 'help'  # Display help if no arguments were given.
        if subcommand == 'help':
            ...
        elif subcommand == 'version' or self.argv[1:] == ['--version']:
            ...
        elif self.argv[1:] in (['--help'], ['-h']):
            ...
        else:
            # 关键代码,传入的subcommand='runserver'时,进入else分支
            # 调用fetch_command()返回command对象,再调用对象的run_from_argv方法
            self.fetch_command(subcommand).run_from_argv(self.argv)
    

    fetch_command()方法

    def fetch_command(self, subcommand):
    
        # Get commands outside of try block to prevent swallowing exceptions
        # commands是一个字典,key是subcommand,value是该subsommand对应的类所在的包的名称
        # 比如{'runserver': 'django.contrib.staticfiles, ...}
        commands = get_commands()
        try:
            # subcommand = "runserver"
            # app_name = "django.contrib.staticfiles"
            app_name = commands[subcommand]
        except KeyError:
            ...
            sys.exit(1)
        if isinstance(app_name, BaseCommand):
            klass = app_name
        else:
            # 关键代码
            # 加载"django.contrib.staticfiles.management.commands.runserver"模块中的Command类,
            # 并且实例化该类,klass实际上是一个Command类实例
            klass = load_command_class(app_name, subcommand)
        return klass
    

    klass是一个django.contrib.staticfiles.management.commands.runserver模块中的Command类的实例对象,他的父类实际上是django.core.management.commands.runserver中的Command类。

    run_from_argv()方法是klass对象的顶级父类中的方法。该方法中最终会调用django.core.management.commands.runserver的Command类的handle()方法。

    def handle(self, *args, **options):
        ...
        
        # 传入参数开启服务
        self.run(**options)
    
    def run(self, **options):
        """Run the server, using the autoreloader if needed."""
        use_reloader = options['use_reloader']
        # debug模式
        if use_reloader:
            autoreload.main(self.inner_run, None, options)
        else:
            # 非debug模式,开启服务
            self.inner_run(None, **options)
    
    def inner_run(self, *args, **options):
        ...
    
        try:
            # 1. 先实例化WSGIHandler,得到全局唯一的application对象,handler就是application
            handler = self.get_handler(*args, **options)
    
            # 2. 然后再开启服务server_cls是django自己重写的WSGIServer类,继承自wsgiref的WSGIServer,
            #    重写的不是关键方法,就当做是wsgiref的WSGIServer即可
            run(self.addr, int(self.port), handler,
                ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
        except socket.error as e:
            ...
            os._exit(1)
        except KeyboardInterrupt:
            if shutdown_message:
                self.stdout.write(shutdown_message)
            sys.exit(0)
    

    再看一下run()方法,是一个独立的函数在django.core.servers.basehttp模块中

    def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
        server_address = (addr, port)
        # threading默认为True,
        if threading:
            # 动态创建一个支持多线程的WSGIServer类,父类为ThreadingMixIn和原始的WSGIServer
            httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
        else:
            httpd_cls = server_cls
        httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
        if threading:
            httpd.daemon_threads = True
        httpd.set_app(wsgi_handler)
        httpd.serve_forever()
    

    其中WSGIServer是Django自己定义的一个类,继承自wsgiref模块的simple_server.WSGIServer类,继承自标准库http.server模块的HTTPServer类,继承自标准库socketserver模块的TCPServer

    WSGIRequestHandler是Django自己定义的一个类,继承自wsgiref模块的simple_server.WSGIRequestHandler类,它重写了父类的handle()方法这个类被作为参数传入到WSGIServer中,等到请求到来时,会触发WSGIRequestHandler类的实例化,在它实例化时会触发handle()方法的调用。

    注:每来一个请求都会实例化一个WSGIRequestHandler对象出来,然后这个对象来处理这个请求。

    来看一下django自定义的WSGIRequestHandler这个类

    django.core.servers.basehttp.WSGIRequestHandler.png
    class WSGIRequestHandler(simple_server.WSGIRequestHandler):
        protocol_version = 'HTTP/1.1'
    
        ...
    
        def get_environ(self):
            
            for k, v in self.headers.items():
                if '_' in k:
                    del self.headers[k]
    
            # 关键在于调用了父类的get_environ()方法,wsgiref模块中的WSGIRequestHandler类的get_eniron()方法构造了environ字典
            return super().get_environ()
        
        # handle()方法在WSGIRequestHandler的__init__()方法中被调用
        def handle(self):
            """Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
            # raw_requestline是客户端发来的请求行数据,读取请求行信息,其实这里读取的并不仅仅是请求行,而是
            # 读取了65537字节的数据到rfile这个缓冲输入流中,在parse_request()方法中都会对这个raw_requestline
            # 信息用"\r\n"来分割,取出第一行,也就是指取出请求行的信息。
            self.raw_requestline = self.rfile.readline(65537)
            if len(self.raw_requestline) > 65536:
                self.requestline = ''
                self.request_version = ''
                self.command = ''
                self.send_error(414)
                return
            # parse_request()方法解析socket对象收到的http请求报文的请求行信息,赋值给自己的属性
            if not self.parse_request():  # An error code has been sent, just exit
                return
            # 实例化ServerHandler,django的WSGIHandler中传入的start_response对象就是这个handler
            handler = ServerHandler(
                self.rfile, self.wfile, self.get_stderr(), self.get_environ()
            )
            handler.request_handler = self      # backpointer for logging
            # 在run()方法是调用application(environ, start_reponse)的
            handler.run(self.server.get_app())
    

    ServerHandler的父类是wsgiref模块的BaseHandler,它的作用就是用来调用application(environ, start_response)的,它在WSGIRequestHandlerhandle()方法中被实例化,同时把WSGIRequestHandler对象的实例属性rfilewfileenviron作为初始化参数传入,此时ServerHandler对象也持有这三个属性。然后调用run(application)来调用application

    django.core.servers.basehttp.ServerHandler.png
    class BaseHandler:
        ...
    
        def run(self, application):
            """调用application"""
           
            try:
    
                self.setup_environ()
    
                # 调用application(),返回一个response对象,在django中是HTTPResponse类的实例对象,赋值给了实例属性result
                self.result = application(self.environ, self.start_response)
    
                # 处理response对象中的数据发送到客户端
                self.finish_response()
            except:
                try:
                    self.handle_error()
                except:
                    # If we get an error handling an error, just give up already!
                    self.close()
                    raise   # ...and let the actual server figure it out.
    
    
        def setup_environ(self):
            """Set up the environment for one request"""
    
            env = self.environ = self.os_environ.copy()
            self.add_cgi_vars()
            # 给environ字典添加其他的key:value,这些字段是PEP333要求必须添加的
            env['wsgi.input']        = self.get_stdin()
            env['wsgi.errors']       = self.get_stderr()
            env['wsgi.version']      = self.wsgi_version
            env['wsgi.run_once']     = self.wsgi_run_once
            env['wsgi.url_scheme']   = self.get_scheme()
            env['wsgi.multithread']  = self.wsgi_multithread
            env['wsgi.multiprocess'] = self.wsgi_multiprocess
            
            ...
    
        def finish_response(self):
            """处理application返回的result,是一个可迭代的response对象"""
            try:
                if not self.result_is_file() or not self.sendfile():
    
                    # response是一个可迭代对象,实现了__iter__()方法,返回response中保存的数据内容,data是html内容,不包含请求头的内容
                    for data in self.result:
                        self.write(data)
                    self.finish_content()
            finally:
                self.close()
    
        def start_response(self, status, headers,exc_info=None):
            """'start_response()' callable as specified by PEP 3333"""
    
            ...
    
            self.status = status
            # 这个方法最重要的作用是把响应头信息封装在headers中,作为自己的实例属性保存起来
            self.headers = self.headers_class(headers)
            
            ...
    
            return self.write
    
        def write(self, data):
            """'write()' callable as specified by PEP 3333"""
            ...
    
            if not self.status:
                ...
            # headers_sent初始值为False,用于标记响应头是否发送
            elif not self.headers_sent:
                # Before the first output, send the stored headers
                self.bytes_sent = len(data)    # make sure we know content-length
                # 1.先把响应状态行+响应头通过wfile.write()方法发送给客户端,wfile就是一个跟socket关联的输出IO流对象
                self.send_headers()
            else:
                self.bytes_sent += len(data)
    
            # XXX check Content-Length and truncate if too many bytes written?
            # 2. 再把响应体data通过wfile.write()方法发送到客户端
            self._write(data)
            # 这个方法没用
            self._flush()
    

    总结:

    WSGIRequestHandler的作用有三点:

    1. 实例化时把socket对象分别封装成rfilewfile对象,他们都是带缓冲的IO流对象,用于接受和发送数据给客户端。
    2. 提供一个get_environ()方法,用来构造environ字典
    3. 实例化时调用自己重写的handler()方法,将BaseHandler的子类实例化,同时传入rfilewfileenviron参数。

    ServerHandler的作用有三点:

    1. 实例化时,接受WSGIRequestHandler对象的rfilewfileenviron作为初始化参数
    2. 调用自己的run(application)来调用application(environ, start_response)
    3. 调用自己的finish_response()来处理application(environ, start_response)返回响应对象response,迭代response对象中存放的数据发送给客户端。

    实际上ServerHandler对象write(data)方法中进行了3次socket.sendall()调用,分别是:

    • socket.sendall(status_line) 响应状态行
    • socket.sendall(headers) 响应头
    • socket.sendall(body) 响应体

    WSGIRequestHandler通过自己的handle()方法与ServerHandler对象产生了关联。而ServerHandler对象在自己的run(application)方法中调用application(environ, start_response),而application(environ, start_response)又是django框架处理请求的入口,整个django框架处理请求的逻辑就在application(environ, start_response)内部去实现了。

    application是一个WSGIHandler类的实例化对象,application(environ, start_response)调用实际上是调用了该对象的__call__(environ, start_response)方法。

    class WSGIHandler(base.BaseHandler):
    
        # WSGIRequest类是django自己提供的请求类,实例化之后就是request对象
        request_class = WSGIRequest
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            # 加载中间件实例到内存中,此时中间件实例的各种方法已经被包裹在 _get_response() 方法的前后了
            self.load_middleware()
    
        def __call__(self, environ, start_response):
            set_script_prefix(get_script_name(environ))
            signals.request_started.send(sender=self.__class__, environ=environ)
    
            # 实例化WSGIRequest,得到request对象,request对象中此时并没有session属性,而是在中间件加载后,
            # 请求进入session中间件时,在session中间件的process_request()方法中动态添加的session属性
            request = self.request_class(environ)
            response = self.get_response(request)
    
            response._handler_class = self.__class__
    
            status = '%d %s' % (response.status_code, response.reason_phrase)
            response_headers = list(response.items())
            for c in response.cookies.values():
                response_headers.append(('Set-Cookie', c.output(header='')))
            start_response(status, response_headers)
            if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
                response = environ['wsgi.file_wrapper'](response.file_to_stream)
            return response
    

    application(environ, start_response)返回的是一个HTTPResponse类的实例对象response。在上面的ServerHandler的,run(appliction)方法可以看到,response被赋值给了ServerHandler的实例属性result,最后把这个response中包含的数据发送给客户端。

    start_response()方法的作用,主要是将响应头信息headers封装在Header对象中,然后把这个Header对象作为start_response方法的持有者,即ServerHandler对象的一个属性,最后在发送数据到客户端时,调用ServerHandler对象的write(data)方法时,能直接取到自己的属性headers来发送响应头给客户端。

    可以看出http服务器的职责只是接收客户端发送过来的报文,然后把报文进行解析封装到environ字典中,至于请求报文中的body内容是什么http服务器不关心,只是把他们封装成environ传给web框架,再由框架来对environ中的内容进行区分。

    相关文章

      网友评论

        本文标题:Django启动入口分析

        本文链接:https://www.haomeiwen.com/subject/tzhfhctx.html