1. 程式人生 > >django源碼分析

django源碼分析

man nag 客戶端 erl ret 退出程序 初始化過程 混淆 __call

原文網址

https://www.jianshu.com/p/17d78b52c732?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

環境說明

  • [x] Python 3.5
  • [x] Django 1.10.4

創建一個django項目

C:\Users\zhengtong>C:\Python35\Scripts\django-admin.exe startproject HelloWorld

項目文件結構

├── HelloWorld
│   ├── __init__
.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py

初始化項目

C:\Users\zhengtong>cd HelloWorld
C:\Users\zhengtong\HelloWorld>C:\Python35\python.exe manage.py migrate # 創建數據庫表(默認是采用的sqlite3).
C:\Users\zhengtong\HelloWorld>C:\Python35\python.exe manage.py createsuperuser # 創建管理員用戶, 該用戶用來django
提供的admin登錄管理界面.

啟動項目

C:\Users\zhengtong\HelloWorld>C:\Python35\python.exe manage.py runserver

啟動項目服務,默認監聽的端口是localhost:8000,也就是說打開瀏覽器,輸入 http://localhost:8000 就可以訪問到HelloWorld這個項目網站了。
還有就是在瀏覽器中輸入 http://localhost:8000/admin 可以訪問到django提供的admin登錄管理界面。

Django Management

Django框架的整個數據流向、服務啟動、端口監聽等基礎核心功能都是按照WSGI標準進行開發的。WSGI是Web Server Gateway Interface的縮寫,它的主要作用是接收客戶端(瀏覽器/軟件/APP/調試程序)發送的請求和轉發請求給上層服務(例如Django)。它采用了select網絡模型進行數據的接收和分發,可以利用操作系統的非堵塞和線程池等特性,因此它是非常高效的(Django采用wsgi是純python代碼實現,開源項目有一個uWSGI是C語言代碼實現,因此現在更多的django項目實際上是運行在uWSGI上的,uWSGI不再討論範圍之內)。

接下來是記錄我利用pycharm提供的斷點調試功能以及自己打日誌整個過程的梳理和總結。

源碼的分析過程取決與分析人員想要關註的原理點,不同的需求就會有不同的側重點,我當前的需求點是,想要知道django是如何通過網絡層接收數據(wsgi)並將請求轉發給django的urls層,因此我會朝著這個主題方向列出每個流轉環節中的核心代碼並做相應的註解。以後如果有機會的話我將會從另外一個側重點(程序設計)去分析源碼。

每個過程我都會先將代碼列出來然後在後面做註解,因此告誡自己以後重讀自己的筆記時不需要努力的去看代碼,而是直接看註解,然後找到對應的代碼進行理解。

代碼塊我會用...來忽略掉一些跟主題無關的代碼。

manage.py
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "TestDrivenDjango.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError:
        ...

    execute_from_command_line(sys.argv)

from django.core.management import execute_from_command_line當這行代碼開始執行時,首先會去運行django.core.management.__init__.py這一整個文件,接著找到execute_from_command_line函數並將其導入到當前程序的命名空間中。

由於整個django.core.management.__init__.py文件都是class類對象和function函數對象,很多時候很自然的就認為這個文件並沒有執行任何命令,只是加載了這些個對象,然後在這些個對象中尋找是否包含有execute_from_command_line。最終忽視了其他很重要的代碼塊==from和import==。

django/core/management/__init__.py
from __future__ import unicode_literals

import os
import pkgutil
import sys
from collections import OrderedDict, defaultdict
from importlib import import_module

import django                               # 這裏
from django.apps import apps                # 這裏
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import (
    BaseCommand, CommandError, CommandParser, handle_default_options,
)
from django.core.management.color import color_style
from django.utils import autoreload, lru_cache, six
from django.utils._os import npath, upath
from django.utils.encoding import force_text

@lru_cache.lru_cache(maxsize=None)
def get_commands():
    commands = {name: ‘django.core‘ for name in find_commands(upath(__path__[0]))}
    if not settings.configured:
        return commands
    for app_config in reversed(list(apps.get_app_configs())):
        path = os.path.join(app_config.path, ‘management‘)
        commands.update({name: app_config.name for name in find_commands(path)})
    return commands

class ManagementUtility(object):

    def __init__(self, argv=None):
        ...

    def fetch_command(self, subcommand):
        commands = get_commands()
        try:
            app_name = commands[subcommand]
        except KeyError:
            ...
        if isinstance(app_name, BaseCommand):
            klass = app_name
        else:
            klass = load_command_class(app_name, subcommand)
        return klass        
        
    def execute(self):
        ...
        if subcommand == ‘help‘:
            if ‘--commands‘ in args:
                sys.stdout.write(self.main_help_text(commands_only=True) + ‘\n‘)
            elif len(options.args) < 1:
                sys.stdout.write(self.main_help_text() + ‘\n‘)
            else:
                self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
        elif subcommand == ‘version‘ or self.argv[1:] == [‘--version‘]:
            sys.stdout.write(django.get_version() + ‘\n‘)
        elif self.argv[1:] in ([‘--help‘], [‘-h‘]):
            sys.stdout.write(self.main_help_text() + ‘\n‘)
        else:
            self.fetch_command(subcommand).run_from_argv(self.argv)

def execute_from_command_line(argv=None):
    """
    A simple method that runs a ManagementUtility.
    """
    utility = ManagementUtility(argv)
    utility.execute()

import django 這行代碼運行了django.__init__.py文件。

from django.apps import apps這行代碼運行了django.apps.__init__.py文件,然而整個django的開端就是從這裏開始的,它落實了非常多的事情(例如:初始化日誌模塊、加載INSTALL_APP、檢查各APP是否正常、檢查緩存模塊是否正常等),當一切無誤時才會往下走,否則將會報錯退出程序。

execute_from_command_line這個方法是一個工廠函數,它負責指揮ManagementUtility類利用execute方法來解析參數和啟動wsgi服務。

ManagementUtility.execute方法中的一大堆if條件就是判斷參數是否合法,重點還是在self.fetch_command(subcommand).run_from_argv(self.argv),這條命令應該拆成兩部分去看。

  • self.fetch_command
    是利用django內置的命令管理工具去匹配到具體的模塊,例如self.fetch_command(subcommand)其實就相當於是self.fetch_command(‘runserver‘),它最終找到了==django.contrib.staticfiles.management.commands.runserver.Command==這個命令工具。
    django中的命令工具代碼組織采用的是策略模式+接口模式,也就是說django.core.management.commands這個目錄下面存在各種命令工具,每個工具下面都有一個Command接口,當匹配到‘runserver‘時調用‘runserver‘命令工具的Command接口,當匹配到‘migrate‘時調用‘migrate‘命令工具的Command接口。
  • run_from_argv(self.argv)
    run_from_argv的作用是初始化中間件、啟動服務,也就是拉起wgsi(但實際上並不是由它來直接完成,而是由後續很多其他代碼來完成),直觀上看它應該是runserver.Command對象的一個方法,但實際上要稍微更復雜一些,因為沒有列出關聯代碼,所以在下一個代碼塊中進行說明。

小結
這部分代碼實際上是一個匹配命令工具的一個過程,通過提供的參數‘runserver‘到命令工具集中去找到runserver模塊。

django/contrib/staticfiles/management/commands/runserver.py
from django.contrib.staticfiles.handlers import StaticFilesHandler
from django.core.management.commands.runserver import     Command as RunserverCommand


class Command(RunserverCommand):

    def add_arguments(self, parser):
        super(Command, self).add_arguments(parser)
        parser.add_argument(
            ‘--nostatic‘, action="store_false", dest=‘use_static_handler‘, default=True,
            help=‘Tells Django to NOT automatically serve static files at STATIC_URL.‘,
        )
        parser.add_argument(
            ‘--insecure‘, action="store_true", dest=‘insecure_serving‘, default=False,
            help=‘Allows serving static files even if DEBUG is False.‘,
        )

    def get_handler(self, *args, **options):
        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

雖然它一共只有兩個方法,但是它繼承了django.core.management.commands.runserver.Command,因此實際上它有非常多的‘方法’可以被調用。由於請求入口在這裏,所以後續有調用==get_handler==的地方需要優先看這裏,因為這非常容易混淆,我就混淆了好多次。

小結
當前類對象中不存在run_from_argv方法,因此我們要往下看它的繼承對象django.core.management.commands.runserver.Command

django/core/management/commands/runserver.py
from django.core.servers.basehttp import get_internal_wsgi_application, run

class Command(BaseCommand):
    
    def execute(self, *args, **options):
        if options[‘no_color‘]:
            os.environ[str("DJANGO_COLORS")] = str("nocolor")
        super(Command, self).execute(*args, **options)    

    def get_handler(self, *args, **options):
        return get_internal_wsgi_application()
        
    def handle(self, *args, **options):
        ...
        self.run(**options)
        
    def run(self, **options):
        use_reloader = options[‘use_reloader‘]

        if use_reloader:
            autoreload.main(self.inner_run, None, options)
        else:
            self.inner_run(None, **options)        
            
    def inner_run(self, *args, **options):
        ...
        try:
            handler = self.get_handler(*args, **options)
            run(self.addr, int(self.port), handler,
                ipv6=self.use_ipv6, threading=threading)
        except socket.error as e:
            ...

小結
當前這個類對象中也沒有run_from_argv這個方法,因此我們要往下看它的繼承對象django.core.management.base.BaseCommand

django/core/management/base.py
class BaseCommand(object):

    def run_from_argv(self, argv):
        ...
        try:
            self.execute(*args, **cmd_options)
        except Exception as e:
            ...
        finally:
            connections.close_all()
            
    def execute(self, *args, **options):
        ...
        try:
            ...
            output = self.handle(*args, **options)
            ...
        finally:
            if saved_locale is not None:
                translation.activate(saved_locale)
        return output            

runserver繼承對象分布 依次按順序如下

  • django.contrib.staticfiles.management.commands.runserver.Command
  • django.core.management.commands.runserver.Command
  • django.core.management.base.BaseCommand
  • object

當前類對象的run_from_argv方法中調用了self.execute方法,==由於請求的入口是django.contrib.staticfiles.management.commands.runserver.Command對象==,因此python並不會去執行BaseCommand.execute而是執行django.core.management.commands.runserver.Command.execute,最後通過super(Command, self).execute(*args, **options)來執行BaseComand.execute

代碼流向
由於接下來的代碼重度使用了繼承、多態、接口等設計模式的方式來運作,沒辦法做過多解釋,所以這裏列出了它的一個基本流轉過程。

  • BaseComand.execute方法中調用了self.handle(即:django.core.management.commands.runserver.Command.handle)。
  • Command.handle方法中調用了self.run(即:django.core.management.commands.runserver.Command.run)。
  • Command.run方法調用了self.inner_run(即:django.core.management.commands.runserver.Command.inner_run)。
  • Command.inner_run方法調用了self.get_handler(即:==django.contrib.staticfiles.management.commands.runserver.Command.get_handler==)

    這裏要特別強調一下self.get_handler,它非常重要,三個重點:

    1. 因為它負責獲取WSGIHandler。
    2. 由於請求入口是django.contrib.staticfiles.management.commands.runserver.Command,整好它本來就有get_handler這個方法,因此並沒有采用django.core.management.commands.runserver.Command.get_handler
    3. self.get_handler並不會返回一個常規的WSGIHandler而是返回一個StaticFilesHandler
    4. StaticFilesHandler類對象繼承WSGIHandler,它的目的是為了判斷每個請求,如果是常規的url請求則直接分配到某個view中去執行,如果是靜態文件規則那麽將不會找view而是響應這個文件。
  • Command.inner_run方法調用了run(即:django.core.servers.basehttp.run),由於沒有列出代碼塊,因此在下一個環節中進行說明。

小結
這部分代碼實際上就是一個初始化過程,全部都為‘runserver‘服務,雖然很多代碼我沒有列出來,但是它確實做了一些,例如參數解析、端口指定檢測、ipv4檢測、ipv6檢測、端口是否占用、線程檢查等工作。

django/core/servers/basehttp.py
from wsgiref import simple_server
from django.utils.six.moves import socketserver


class WSGIServer(simple_server.WSGIServer, object):

    request_queue_size = 10

    def __init__(self, *args, **kwargs):
        if kwargs.pop(‘ipv6‘, False):
            self.address_family = socket.AF_INET6
        self.allow_reuse_address = kwargs.pop(‘allow_reuse_address‘, True)
        super(WSGIServer, self).__init__(*args, **kwargs)

    def server_bind(self):
        super(WSGIServer, self).server_bind()
        self.setup_environ()

    def handle_error(self, request, client_address):
        if is_broken_pipe_error():
            logger.info("- Broken pipe from %s\n", client_address)
        else:
            super(WSGIServer, self).handle_error(request, client_address)


class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
    def address_string(self):
        return self.client_address[0]


def run(addr, port, wsgi_handler, ipv6=False, threading=False):
    server_address = (addr, port)
    if threading:
        httpd_cls = type(str(‘WSGIServer‘), (socketserver.ThreadingMixIn, WSGIServer), {})   # Work Here
    else:
        httpd_cls = WSGIServer
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    if threading:
        httpd.daemon_threads = True

    httpd.set_app(wsgi_handler)
    httpd.serve_forever()

django.core.servers.basehttp.run工廠函數負責只會各個對象負責啟動wsgi服務。
wsgi_handler參數,這裏傳遞的是StaticFilesHandler

type(str(‘WSGIServer‘), (socketserver.ThreadingMixIn, WSGIServer), {}) 是一種很特殊的寫法,通過代碼塊中WSGIServer類對象可以看出它只繼承了wsgiref.simple_server.WSGIServer、object這兩個類對象,但是通過type這種寫法相當於是強行賦予它一個socketserver.ThreadingMixIn繼承對象,它的用意是每次調用這個對象的時候都會單獨啟用一個線程來處理。另外雖然 WSGIServer 只繼承了 wsgiref.simple_server.WSGIServer、object兩個對象,但是wsgiref.simple_server.WSGIServer卻<遞歸式>的繼承了一堆對象,下面完整的列出WSGIServer繼承家族。

  • django.core.servers.basehttp.WSGIServer
  • wsgiref.simple_server.WSGIServersocketserver.ThreadingMixIn
  • http.server.HTTPServer
  • socketserver.TCPServer
  • socketserver.BaseServer
  • object

httpd_cls這個變量被定義完成之後,由於大量的繼承關系,它其實已經不單純的屬於django,它是一個傳統意義上的WSGI服務對象了。

httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)這行代碼非常重要,因為它是WSGI服務器與django之間相互通信的唯一樞紐通道,也就是說,當WSGI服務對象收到socket請求後,會將這個請求傳遞給django的WSGIRequestHandler(下節會列出WSGIRequestHandler是如何工作的)。

httpd.set_app(wsgi_handler)是將django.contrib.staticfiles.handlers.StaticFilesHandler 傳遞給WSGIServer當作一個application,當WSGIServer收到網絡請求後,可以將數據分發給django.core.servers.basehttp.WSGIRequestHandler,最終由django.core.servers.basehttp.WSGIRequestHandler將數據傳遞給application(即:django.contrib.staticfiles.handlers.StaticFilesHandler)。

httpd.serve.forever()啟動非堵塞網絡監聽服務。

總結

上面所有的過程都是django內部代碼的為了啟動服務而做的準備,簡單的把流程給列出來。

  1. 解析運行 python manage.py 所提供的參數,例如: runserver.
  2. 根據參數 找到相對應的 命令管理工具。
  3. 加載所有的app。
  4. 檢查端口、ipv4檢測、ipv6檢測、端口是否占用、線程檢查、orm對象檢查(表是否創建)。
  5. 實例化WSGIRequestHandler,並且將它註冊到python Lib庫中的WSGIServer中。
  6. 最後啟動python Lib庫中的WSGIServer。

WSGIServer To Django WSGIRequestHandler

接下來的部分是python Lib庫中的WSGIServer運作過程中,如何將接收到的請求分發會django的WSGIRequestHandler。

C:/Python35/Lib/socketserver.py
class BaseServer:

    def __init__(self, server_address, RequestHandlerClass):
        self.server_address = server_address
        self.RequestHandlerClass = RequestHandlerClass
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False
        
    def serve_forever(self, poll_interval=0.5):
        self.__is_shut_down.clear()
        try:

            with _ServerSelector() as selector:
                selector.register(self, selectors.EVENT_READ)

                while not self.__shutdown_request:
                    ready = selector.select(poll_interval)
                    if ready:
                        self._handle_request_noblock()              # 這裏

                    self.service_actions()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()
    
    def _handle_request_noblock(self):
        try:
            request, client_address = self.get_request()
        except OSError:
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address)       # 這裏
            except:
                self.handle_error(request, client_address)
                self.shutdown_request(request)
        else:
            self.shutdown_request(request)    
            
    def verify_request(self, request, client_address):
        """Verify the request.  May be overridden.

        Return True if we should proceed with this request.

        """
        return True            
            
    def process_request(self, request, client_address):
        self.finish_request(request, client_address)                # 這裏
        self.shutdown_request(request)     
        
    def finish_request(self, request, client_address):
        self.RequestHandlerClass(request, client_address, self)     # 這裏

上一小節最後一個動作是httpd.serve_forever,調用的是socketserver.BaseServer.serve_forever方法。該方法采用了selector網絡模型進行等待數據,每0.5秒遍歷一次文件描述符,當有數據進來時,ready變量會是一個socket請求對象,這時會將後續工作轉交給self._handler_request_noblock方法(即:socketserver.BaseServer._handler_request_noblock)去處理。

socketserver.BaseServer._handler_request_noblock方法基本沒做什麽事情(self.verify_request壓根就沒有檢查任何東西),直接就把後續工作轉交給 socketserver.BaseServer.process_request 方法。

socketserver.BaseServer.process_request也沒做什麽事情,直接就將後續工作轉交給socketserver.BaseServer.finish_request方法,只不過在最後加了一條關閉請求的命令。

socketserver.BaseServer.finish_request也沒做什麽事情,直接就將後續工作轉交給socketserver.BaseServer.RequestHandlerClass

socketserver.BaseServer.RequestHandlerClass是由上一節httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)傳遞過來的參數django.core.servers.basehttp.WSGIRequestHandler。 也就是說當執行self.RequestHandler(request, client_address, self)時等同於執行django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)

小結
serve_forever開啟了一個while來無限監聽網絡層的scoket請求,當一條請求過來時,就層層轉交到django.core.servers.basehttp.WSGIRequestHandler手中。

django.core.servers.basehttp.py 單獨列出WSGIRequestHandler代碼片段
class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
    def address_string(self):
        return self.client_address[0]

    def get_environ(self):
        for k, v in self.headers.items():
            if ‘_‘ in k:
                del self.headers[k]

        env = super(WSGIRequestHandler, self).get_environ()
        path = self.path
        if ‘?‘ in path:
            path = path.partition(‘?‘)[0]
        path = uri_to_iri(path).encode(UTF_8)
        env[‘PATH_INFO‘] = path.decode(ISO_8859_1) if six.PY3 else path
        return env

    def handle(self):
        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

        if not self.parse_request():  # An error code has been sent, just exit
            return

        handler = ServerHandler(
            self.rfile, self.wfile, self.get_stderr(), self.get_environ()
        )

        handler.request_handler = self      # backpointer for logging
        handler.run(self.server.get_app())

接著上一節繼續分析:
socketserver.BaseServer.RequestHandler(request, client_address, self)等同於django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)

首先django.core.servers.basehttp.WSGIRequestHandler的繼承分布:

  • django.core.servers.basehttp.WSGIRequestHandler
  • wsgiref.simple_server.WSGIRequestHandler
  • http.server.BaseHTTPRequestHandler
  • socketserver.StreamRequestHandler
  • socketserver.BaseRequestHandler
  • object

從代碼上看django.core.servers.basehttp.WSGIRequestHandler並沒有init或者call方法,因此需要遍歷所有父類對象。
最終在socketserver.BaseRequestHandler中看到了init實例初始化方法,它調用了self.handle方法(即回調了:django.core.servers.basehttp.WSGIRequestHandler.handle)。

handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())實例化了ServerHandler對象。

handler.run(self.server.get_app()),意思是將django.contrib.staticfiles.handlers.StaticFilesHandler轉交給ServerHandler去運行。

ServerHandler對象並沒有run方法,它的繼承分布:

  • django.core.servers.basehttp.ServerHandler
  • wsgiref.simple_server.ServerHandler
  • wsgiref.handlers.SimpleHandler
  • wsgiref.handlers.BaseHandler
  • object

最終在 wsgiref.handlers.BaseHandler 中找到了run方法。

wsgiref.handlers.py
class BaseHandler:
    def run(self, application):
        ...
        self.result = application(self.environ, self.start_response)    # 這裏
        self.finish_response()

application(self.environ, self.start_response)也就相當於是django.contrib.staticfiles.handlers.StaticFilesHandler.__call__(self.environ, lf.start_response)

django.contrib.staticfiles.handlers.py
class StaticFilesHandler(WSGIHandler):
    def __call__(self, environ, start_response):
        if not self._should_handle(get_path_info(environ)):
            return self.application(environ, start_response)
        return super(StaticFilesHandler, self).__call__(environ, start_response)

通過層層流轉,最終進入django的靜態文件處理的Handler。

總結
environ這個變量在django的WSGIServer和WSGIRequestHandler中扮演這非常重要的角色,因為所有的客戶端ip請求的URLcookiesessionheader等等信息都保存在其中。

WSGIServer: 用於處理socket請求和對接WSGIRequestHandler。
WSGIRequestHandler:針對environ進行預處理和對接WSGIServerHandler。
ServerHandler: 用於執行應用程序(application)和返回響應給WSGIServer。

django源碼分析