Django深度剖析


啟動過程

通過命令行執行

python manage.py runserver 127.0.0.1:8000

啟動Django服務

manage.py模塊獲取到命令行參數manage.py runserver 127.0.0.1:8000,然后將該參數交由execute_from_command_line方法處理。

# manage.py
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

該模塊調用django.core.management.__init__包下的ManagementUtility類方法,並把參數傳遞給該類初始化,
然后調用ManagementUtilityexecute開始一連串動作

  • 首先獲取命令行第一個參數,也就是runserver,第0個參數是manage.py程序本身,同時把剩下的參數存入到options變量,
    options=['127.0.0.1:8000'].
  • 調用ManagementUtility autocomplete檢查系統環境變量是否設置了DJANGO_AUTO_COMPLETE。接着再調用ManagementUtility
    fetch_command方法獲取命令模塊,該fetch_command 通過調用get_commands方法將django.core.management.commands目錄下所有
    默認的命令模塊獲取,同時將settings里已注冊的app目錄INSTALLED_APPS下順序反轉,依次將app目錄下對應的management/commands模塊獲取到。
    這些模塊都可以通過執行python manage.py 模塊名調用。由於django.contrib.staticfilesapp下已經重寫了runserver這個模塊,所以默認
    django.core.management.commands目錄下的runserver模塊會被后面查找到的模塊覆蓋掉.然后fetch_command再調用load_command_class
    方法加載django.contrib.staticfiles.runserver模塊,並將該模塊的Command類實例化返回。
  • 通過得到返回的runserver模塊下的Command對象,繼續調用對象的run_from_argv方法,將整個命令行參數傳入該方法, run_from_argv
    方法是基類django.core.management.base.BaseCommand的方法,如果要自定義manage.py的命令行參數模塊,都必須實現該方法,或者可以直接
    繼承該基類,該方法默認會依次調用對象本身的create_parser,創建命令行參數解析器,然后繼續調用execute執行一些系統檢查,包括數據庫連接同步,
    可以通過改變對象的requires_system_checks屬性為False則不進行該檢查操作,requires_migrations_checks控制是否檢查數據遷移計划行為.
  • 做完應用檢查后,再調用對象的handle方法,該方法由於對象本身沒有重寫,所有是繼承父類django.core.management.commands.runserver.Command
    handle方法,在該父類模塊runserver中,為了保持程序向后兼容,所以將BaseRunserverCommand = Command,handle方法首先檢查配置文件
    是否為DEBUG模式,如果是,則檢查是否配置了ALLOWED_HOSTS屬性,然后檢查命令行參數是否啟用了ipv6,如果啟用了,還得檢查socket是否支持ipv6,
    然后檢查監聽端口,默認端口8000handle方法會繼續調用對象的run方法,該方法會檢查命令行參數是否包含--noreload選項,如果包含則
    不啟用自動加載,由於命令行沒有傳入該選項,則使用默認行為,即:啟用自動加載功能。
默認的命令行選項參數值
--ipv6  # 默認為False,不使用IPV6
--nothreading # 默認值True,默認值為使用多線程,使用該選項后,則不使用線程,
--noreload  # 默認值True  默認自動加載,使用該選項后,則不自動加載代碼,用於代碼文件有改動時會重新加載代碼
--nostatic # 默認值True  默認處理指定STATIC_URL配置的靜態文件服務,使用該選項后,將不處理頁面加載后的靜態文件的URL請求
--insecure  # 默認值False  不允許在非DEBUG模式提供處理靜態文件的服務,使用該選項后,即使DEBUG模式,也處理靜態文件的請求
  • 所以run方法會獲取命令行的選項--nothreading值,默認沒有使用該選項,所以值為True,即使用多線程。然后調用django.utils.autoreload.main
    方法,並把對象本身的inner_run方法及命令行參數傳入main方法,通過平台選擇后,由於我的環境非java平台,所以繼續調用autoreload模塊下的
    python_reloader方法,該方法首先判斷系統環境中是否包含RUN_MAIN環境,且值為true,如果不包含,則使用通過該模塊的 restart_with_reloader
    方法,先設置環境變量RUN_MAIN='true''然后執行 subprocess模塊的call方法,重新運行命令行參數

通過這樣一執行,等於上面所執行的步驟都將重來一遍,唯一不同的是再次調用python_reloader方法時,將是新開啟一個線程執行之前傳入對象的inner_run方法及命令行參數傳入

這也就是為什么有人說django每次啟動都執行了2次的原因,
  • 以上步驟走完后,終於要到我們的inner_run方法了,該方法才是啟動web server的關鍵,調用該方法,會打印一些我們每次啟動Django服務的看到的基本信息,
Performing system checks...

System check identified no issues (0 silenced).
May 10, 2017 - 00:15:45
Django version 1.11, using settings 'cloud.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

inner_run內部再次調用該對象的check_migrations方法同步表結構遷移檢查,檢查完畢后則調用對象的get_handler方法,

對象本身重寫了get_handler方法,同時又調用了父類的get_handler的方法,通過調用父類的get_handler獲取一個WSGIHandler對象實例。

首先調用django.core.servers.basehttp.get_internal_wsgi_application,該方法通過獲取settings配置的WSGI_APPLICATION屬性,默認
的屬性都是以項目名+'.wsgi.application'拼接而成的字符串值,默認的django項目下都有一個wsgi模塊,即項目名.wsgi這樣的模塊,
get_internal_wsgi_application檢測到該settings,再調用django.utils.module_loading.import_string方法,該方法首先以.分割字符串,
WSGI_APPLICATION的值會分隔為項目名.wsgiapplication兩部分,前半部分為模塊路徑,后半部分為模塊內的屬性,先導入該模塊,再獲取到
該模塊的屬性,然后返回,默認得到的是一個WSGIHandler實例化對象,django.core.handlers.wsgi.WSGIHandler

class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super(WSGIHandler, self).__init__(*args, **kwargs)
        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)
        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 = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
        start_response(force_str(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

實例化時,同時會調用WSGIHandler父類django.core.handlers.base.BaseHandlerload_middleware方法。

該方法首先判斷Django使用的哪種版本,1.10(不包括1.10)以前的版本對應settings配置的中間件都是寫到MIDDLEWARE_CLASSES列表里,
1.10開始改為MIDDLEWARE,

handler = convert_exception_to_response(self._get_response)
for middleware_path in reversed(settings.MIDDLEWARE):
    middleware = import_string(middleware_path)
    try:
        mw_instance = middleware(handler)
    except MiddlewareNotUsed as exc:
        if settings.DEBUG:
            if six.text_type(exc):
                logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
            else:
                logger.debug('MiddlewareNotUsed: %r', middleware_path)
        continue

    if mw_instance is None:
        raise ImproperlyConfigured(
            'Middleware factory %s returned None.' % middleware_path
        )

    if hasattr(mw_instance, 'process_view'):
        self._view_middleware.insert(0, mw_instance.process_view)
    if hasattr(mw_instance, 'process_template_response'):
        self._template_response_middleware.append(mw_instance.process_template_response)
    if hasattr(mw_instance, 'process_exception'):
        self._exception_middleware.append(mw_instance.process_exception)
    handler = convert_exception_to_response(mw_instance)
self._middleware_chain = handler
def convert_exception_to_response(get_response):
    """
    包裹傳入到response對象,所有的已知異常都會被轉換成對應的4XX異常,如(Http404,
    PermissionDenied, MultiPartParserError, SuspiciousOperation),其它異常都會轉換為500錯誤,
    這個裝飾器自動應用到所有到中間件,確保中間件內部異常不會泄露而影響到后面到代碼運行,
    然后下一個中間件可以繼續處理上一個中間件返回的response,而不是拋出的異常
    """
    @wraps(get_response, assigned=available_attrs(get_response))
    def inner(request):
        try:
            response = get_response(request)  
        except Exception as exc:
            response = response_for_exception(request, exc)  # 對於中間件拋出的所有異常進行攔截,然后判斷該響應是什么錯誤,
                                                             # 已知的則返回對應的錯誤碼,未知的,都是500錯誤
        return response
    return inner

首先該django.core.handlers.base.BaseHandler._get_responseconvert_exception_to_response包裹起來,然后傳入到中間件列表的
最后中間件得到一個中間件實例對象,然后
判斷該實例對象是否包含了process_view方法,如果包含,則將該對象的process_view插入到一個_view_middleware的列表的第一個位置,
判斷該實例對象是否包含了process_template_response方法,如果包含,則將該對象的process_template_response方法追加到
_template_response_middleware列表末尾,
判讀該實例對象是否包含了process_exception方法,如果包含,則將該對象的process_exception方法追加到_exception_middleware列表末尾,
最后將該中間件實例通過convert_exception_to_response方法包裹,進行下一次迭代時傳給下一個中間件作為實例化時的參數對象,直到所有的中間件
都被實例化,然后將最后一個中間件的實例保存到
django.core.handlers.base.BaseHandler對象的_middleware_chain屬性里。

    def get_handler(self, *args, **options):
        """
        Returns the static files serving handler wrapping the default handler,
        if static files should be served. Otherwise just returns the default
        handler.
        """
        handler = super(Command, self).get_handler(*args, **options)
        use_static_handler = options['use_static_handler']
        insecure_serving = options['insecure_serving']
        if use_static_handler and (settings.DEBUG or insecure_serving):
            return StaticFilesHandler(handler)
        return handler

整個WSGIHandler對象實例化完畢,同時返回給django.core.management.commands.runserver.get_handler,再由該方法返回給它的子類
django.contrib.staticfiles.management.commands.runserver.get_handler方法。
get_handler處理靜態文件服務時,檢查當前的程序模式是否為DEBUG或命令行是否包含--insecure選項,如果是則將WSGIHandler實例化對象
傳入給StaticFilesHandler類進行實例化,並返回,該類也繼承了WSGIHandler.如果非DEBUG模式,且沒有包含--insecure選項,則直接將之前的
實例化對象返回給django.core.management.commands.runserver.Command.inner_run方法,

def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
    server_address = (addr, port)
    if threading:
        httpd_cls = type(str('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()
# 將命令行的參數解析后的值傳入run方法,並將handler對象傳入
run(self.addr, int(self.port), handler, ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)

看到這個run方法是不是有種很熟悉的感覺,沒錯,它就是像我們平時使用socketserver方法一樣的,被傳入的handler會在有連接請求的時候,
自動調用該handler類的handle方法處理請求,通過serve_forever就進入無限循環狀態,直到強制終止,socket服務端才停止服務。


到此,整個Django程序才算完成啟動,剩下的都是socket server的操作方式了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM