django啓動入口源碼分析

manage.py是啓動入口,在裏面調用execute_from_command_line(sys.argv)方法html

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

ManagementUtility對象的execute()方法django

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()方法cookie

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類。session

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模塊中app

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()方法的調用。socket

注:每來一個請求都會實例化一個WSGIRequestHandler對象出來,而後這個對象來處理這個請求。ide

來看一下django自定義的WSGIRequestHandler這個類函數

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

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來發送響應頭給客戶端。

相關文章
相關標籤/搜索