Django開發:wsgi源碼分析

入口

看源碼,找到程序的入口是第一步,很簡單,我們怎麼啓動django來着

python manage.py runserver 8000

好了,就它manage.py,我們來看看它裏面都幹了些啥(讀源碼不必面面俱到,找到關鍵代碼即可)

#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Winston_crm.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError:
        # The above import may fail for some other reason. Ensure that the
        # issue is really that Django is missing to avoid masking other
        # exceptions on Python 2.
        try:
            import django
        except ImportError:
            raise ImportError(
                "Couldn't import Django. Are you sure it's installed and "
                "available on your PYTHONPATH environment variable? Did you "
                "forget to activate a virtual environment?"
            )
        raise
    execute_from_command_line(sys.argv)

找到關鍵代碼execute_from_command_line(sys.argv),進入到裏面:

def execute_from_command_line(argv=None):
    """Run a ManagementUtility."""
    # 調用當前文件中的類ManagementUtility產生對象,這個類就在該函數的上方,一找就能找到
    utility = ManagementUtility(argv)
    # 調用類ManagementUtility中的方法execute
    utility.execute()

關鍵代碼utility.execute() ,去類ManagementUtility中可以找到,該方法特別長,就不列舉了,一連串if條件就是判斷參數是否合法

進入到execute()方法中

def execute(self):
    """
    Given the command-line arguments, figure out which subcommand is being
    run, create a parser appropriate to that command, and run it.
    """
    # 獲取命令行輸入第一個參數,如果沒有則爲help
    try:
        subcommand = self.argv[1]
    except IndexError:
        subcommand = 'help'  # Display help if no arguments were given.

    # Preprocess options to extract --settings and --pythonpath.
    # These options could affect the commands that are available, so they
    # must be processed early.
    # 添加命令說明
    parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False)
    parser.add_argument('--settings')
    parser.add_argument('--pythonpath')
    parser.add_argument('args', nargs='*')  # catch-all
    try:
        # 解析後面的參數,options:Namespace(args=[],pythonpath=None,settings=None)
        options, args = parser.parse_known_args(self.argv[2:])
        # 如果options中的pythonpath或者settings有,則使用傳入的路徑與文件
        handle_default_options(options)
    except CommandError:
        pass  # Ignore any option errors at this point.
    # 當是django-admin輸入時沒有配置文件此時會報錯,
    # 如果是已經生產的項目則可以導入配置文件中已經配置的應用
    try:
        settings.INSTALLED_APPS
    except ImproperlyConfigured as exc:
        self.settings_exception = exc
    except ImportError as exc:
        self.settings_exception = exc

    if settings.configured:
        # Start the auto-reloading dev server even if the code is broken.
        # The hardcoded condition is a code smell but we can't rely on a
        # flag on the command class because we haven't located it yet.
        # 如果不是runserver並且沒有關閉自動重載功能,則執行以下函數
        if subcommand == 'runserver' and '--noreload' not in self.argv:
            try:
                # 調用自動檢測文件是否修改如果修改則自動重新啓動Django服務
                autoreload.check_errors(django.setup)()
            except Exception:
                # The exception will be raised later in the child process
                # started by the autoreloader. Pretend it didn't happen by
                # loading an empty list of applications.
                apps.all_models = defaultdict(OrderedDict)
                apps.app_configs = OrderedDict()
                apps.apps_ready = apps.models_ready = apps.ready = True

                # Remove options not compatible with the built-in runserver
                # (e.g. options for the contrib.staticfiles' runserver).
                # Changes here require manually testing as described in
                # #27522.
                _parser = self.fetch_command('runserver').create_parser('django', 'runserver')
                _options, _args = _parser.parse_known_args(self.argv[2:])
                for _arg in _args:
                    self.argv.remove(_arg)

        # In all other cases, django.setup() is required to succeed.
        else:
            # 初始化Django環境
            django.setup()
    # 檢測是否是自動完成
    self.autocomplete()
    # 如果解析命令爲help
    if subcommand == 'help':
        if '--commands' in args:
            # 打印出help命令
            sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
        # 如果輸入參數爲空
        elif not options.args:
            sys.stdout.write(self.main_help_text() + '\n')
        else:
            # 針對某個命令打印相應命令的幫助信息
            self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
    # Special-cases: We want 'django-admin --version' and
    # 'django-admin --help' to work, for backwards compatibility.
    # 如果輸入的命令是打印版本信息
    elif subcommand == 'version' or self.argv[1:] == ['--version']:
        # 則輸出當前Django的版本
        sys.stdout.write(django.get_version() + '\n')
    # 如果輸入參數中包括了--help -h 則打印幫助信息
    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)

首先使用命令行工具創建django項目和應用,此時使用的命令爲startproject,startapp,該兩個命令比較相似,實現的功能都是講django/conf目錄下的project_template和app_template兩個模板進行一定數據的渲染後生成一個完整的項目到指定目錄下。假如當前執行的參數爲django-admin startapp testapp,此時就會執行到self.fetch_command(subcommand).run_from_argv(self.argv),鏈式調用,我們一點一點來看; 先看fetch_command(subcommand),即fetch_command('runserver'),就在類ManagementUtility中往上翻可以找到該方法,進入其中:

def fetch_command(self, subcommand): # 執行命令行輸入的具體命令
    """
    Try to fetch the given subcommand, printing a message with the
    appropriate command called from the command line (usually
    "django-admin" or "manage.py") if it can't be found.
    """
    # Get commands outside of try block to prevent swallowing exceptions
    # get_comands()會返回一個字典
    commands = get_commands() # 獲取所有支持的命令
    try:
    # app_name = commands[subcommand]取值操作即app_name='django.core' 
        app_name = commands[subcommand] # 獲取命令名稱所在的路徑或者實例
    except KeyError:
        if os.environ.get('DJANGO_SETTINGS_MODULE'):
            # If `subcommand` is missing due to misconfigured settings, the
            # following line will retrigger an ImproperlyConfigured exception
            # (get_commands() swallows the original one) so the user is
            # informed about it.
            settings.INSTALLED_APPS
        else:
            sys.stderr.write("No Django settings specified.\n")
        possible_matches = get_close_matches(subcommand, commands)
        sys.stderr.write('Unknown command: %r' % subcommand)
        if possible_matches:
            sys.stderr.write('. Did you mean %s?' % possible_matches[0])
        sys.stderr.write("\nType '%s help' for usage.\n" % self.prog_name)
        sys.exit(1)
    if isinstance(app_name, BaseCommand):# 判斷app_name是否是基本命令的實例,還是命令的路徑
        # If the command is already loaded, use it directly.
        klass = app_name
    else:
    # klass = load_command_class(app_name, subcommand)
    # 即klass = load_command_class('django.core' ,’runserver‘),
    # 自己去看很簡單,klass=django.core.management.commands.runserver.Command類
        klass = load_command_class(app_name, subcommand) # 如果是路徑則導入該命令
    return klass # 將命令的實例化對象返回

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接口。

好啦,此時我們得知self.fetch_command(subcommand)得到的是類Command,好多人就在這懵逼了,接下來鏈式調用應該去找run_from_argv(self.argv)了,但是在Command類中怎麼也找不到,傻了吧,去Command的父類BaseCommand裏找啊

    def run_from_argv(self, argv):
        """
        Set up any environment changes requested (e.g., Python path
        and Django settings), then run this command. If the
        command raises a ``CommandError``, intercept it and print it sensibly
        to stderr. If the ``--traceback`` option is present or the raised
        ``Exception`` is not ``CommandError``, raise it.
        """
        self._called_from_command_line = True # 從命令行調入標識
        parser = self.create_parser(argv[0], argv[1])# 創建幫助的說明

        options = parser.parse_args(argv[2:])# 解析輸入的參數
        cmd_options = vars(options)
        # Move positional args out of options to mimic legacy optparse
        args = cmd_options.pop('args', ())
        handle_default_options(options)# 調用命令行輸入的配置文件
        try:
            self.execute(*args, **cmd_options)# 調用execute方法
        except Exception as e:
            if options.traceback or not isinstance(e, CommandError):
                raise

            # SystemCheckError takes care of its own formatting.
            if isinstance(e, SystemCheckError):
                self.stderr.write(str(e), lambda x: x)
            else:
                self.stderr.write('%s: %s' % (e.__class__.__name__, e))
            sys.exit(1)
        finally:
            try:
                connections.close_all()# 關閉所有的數據庫連接
            except ImproperlyConfigured:
                # Ignore if connections aren't setup at this point (e.g. no
                # configured settings).
                pass

run_from_argv的作用是初始化中間件、啓動服務,也就是拉起wsgi(但實際上並不是由它來直接完成,而是由後續很多其他代碼來完成),直觀上看它應該是runserver.Command對象的一個方法,但實際上要稍微更復雜一些,因爲沒有列出關聯代碼,所以在下一個代碼塊中進行說明。

關鍵代碼self.execute(*args, **cmd_options),注意了,這個execute應該去Command類裏找啊,因爲該selfCommand類的對象啊,讓我們回到Command類中,找execute

這裏有個坑,我找了好久都沒找到Command類的入口,原來藏在runserver.py中:

django/core/management/commands/runserver.py # Command類所在文件
class Command(BaseCommand):
    help = "Starts a lightweight Web server for development."

    # Validation is called explicitly each time the server is reloaded.
    requires_system_checks = False
    stealth_options = ('shutdown_message',)

    default_addr = '127.0.0.1'
    default_addr_ipv6 = '::1'
    default_port = '8000'# 默認啓動服務監聽的端口
    protocol = 'http'
    server_cls = WSGIServer

    def add_arguments(self, parser):  # 創建幫助信息
        parser.add_argument(
            'addrport', nargs='?',
            help='Optional port number, or ipaddr:port'
        )
        parser.add_argument(
            '--ipv6', '-6', action='store_true', dest='use_ipv6',
            help='Tells Django to use an IPv6 address.',
        )
        parser.add_argument(
            '--nothreading', action='store_false', dest='use_threading',
            help='Tells Django to NOT use threading.',
        )
        parser.add_argument(
            '--noreload', action='store_false', dest='use_reloader',
            help='Tells Django to NOT use the auto-reloader.',
        )

    def execute(self, *args, **options):# 調用處理方法
        if options['no_color']:# 檢查是否需要更改文字顏色
            # We rely on the environment because it's currently the only
            # way to reach WSGIRequestHandler. This seems an acceptable
            # compromise considering `runserver` runs indefinitely.
            os.environ["DJANGO_COLORS"] = "nocolor"
        # super().execute(*args, **options)會去父類BaseCommand中找到excute方法,
        # 該方法中的關鍵代碼爲output = self.handle(*args, **options),
        # 該self是Command類的對象,所以接着去Command類中找到handle方法
        super().execute(*args, **options)# 調用父類的執行方法

    def get_handler(self, *args, **options):
        """Return the default WSGI handler for the runner."""
        return get_internal_wsgi_application()

    def handle(self, *args, **options):# 調用處理方法
        # 檢查是否是debug模式,如果不是則ALLOWED_HOSTS不能爲空
        if not settings.DEBUG and not settings.ALLOWED_HOSTS:
            raise CommandError('You must set settings.ALLOWED_HOSTS if DEBUG is False.')

        self.use_ipv6 = options['use_ipv6']
        # 檢查輸入參數中是否是ipv6格式,檢查當前python是否支持ipv6
        if self.use_ipv6 and not socket.has_ipv6:
            raise CommandError('Your Python does not support IPv6.')
        self._raw_ipv6 = False
        # 如果輸入參數中沒有輸入端口則使用默認的端口
        if not options['addrport']:
            self.addr = ''
            self.port = self.default_port
        # 檢查匹配的ip格式
        else:
            m = re.match(naiveip_re, options['addrport'])
            if m is None:
                raise CommandError('"%s" is not a valid port number '
                                   'or address:port pair.' % options['addrport'])
            # 找出匹配的數據
            self.addr, _ipv4, _ipv6, _fqdn, self.port = m.groups()
            # 檢查端口是否爲數字
            if not self.port.isdigit():
                raise CommandError("%r is not a valid port number." % self.port)
            # 檢查解析出的地址是否合法的ipv6地址
            if self.addr:
                if _ipv6:
                    self.addr = self.addr[1:-1]
                    self.use_ipv6 = True
                    self._raw_ipv6 = True
                elif self.use_ipv6 and not _fqdn:
                    raise CommandError('"%s" is not a valid IPv6 address.' % self.addr)
        # 如果沒有輸入ip地址則使用默認的地址
        if not self.addr:
            self.addr = self.default_addr_ipv6 if self.use_ipv6 else self.default_addr
            self._raw_ipv6 = self.use_ipv6
        # 運行,調用run方法
        self.run(**options)

    def run(self, **options):
        """Run the server, using the autoreloader if needed."""
        # 根據配置是否自動加載,如果沒有輸入則default=True  
        use_reloader = options['use_reloader']
        # 當開啓了自動加載時,則調用自動啓動運行
        if use_reloader:
            autoreload.run_with_reloader(self.inner_run, **options)
        # 如果沒有開啓文件更新自動重啓服務功能則直接運行
        else:
            self.inner_run(None, **options)

    def inner_run(self, *args, **options):
        # If an exception was silenced in ManagementUtility.execute in order
        # to be raised in the child process, raise it now.
        autoreload.raise_last_exception()
        # 是否開啓多線程模式,當不傳入時則默認爲多線程模式運行
        threading = options['use_threading']
        # 'shutdown_message' is a stealth option.
        shutdown_message = options.get('shutdown_message', '')
        # 打印停止服務信息
        quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C'

        self.stdout.write("Performing system checks...\n\n")
        self.check(display_num_errors=True)
        # Need to check migrations here, so can't use the
        # requires_migrations_check attribute.
        # 檢查是否migrations是否與數據庫一致
        self.check_migrations()
        # 獲取當前時間
        now = datetime.now().strftime('%B %d, %Y - %X')
        # 解析當前時間
        self.stdout.write(now)
        # 打印時間等信息
        self.stdout.write((
            "Django version %(version)s, using settings %(settings)r\n"
            "Starting development server at %(protocol)s://%(addr)s:%(port)s/\n"
            "Quit the server with %(quit_command)s.\n"
        ) % {
            "version": self.get_version(),
            "settings": settings.SETTINGS_MODULE,
            "protocol": self.protocol,
            "addr": '[%s]' % self.addr if self._raw_ipv6 else self.addr,
            "port": self.port,
            "quit_command": quit_command,
        })

        try:
            # 獲取信息處理的handler,默認返回wsgi
            handler = self.get_handler(*args, **options)
            # 調用run方法
            run(self.addr, int(self.port), handler,
                ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
        except socket.error as e:
            # Use helpful error messages instead of ugly tracebacks.
            ERRORS = {
                errno.EACCES: "You don't have permission to access that port.",
                errno.EADDRINUSE: "That port is already in use.",
                errno.EADDRNOTAVAIL: "That IP address can't be assigned to.",
            }
            try:
                error_text = ERRORS[e.errno]
            except KeyError:
                error_text = e
            self.stderr.write("Error: %s" % error_text)
            # Need to use an OS exit because sys.exit doesn't work in a thread
            os._exit(1)
        except KeyboardInterrupt:
            if shutdown_message:
                self.stdout.write(shutdown_message)
            sys.exit(0)

執行無異常的話,最終都要定位到一個run方法,該方法就在本文件開頭位置導入過

from django.core.servers.basehttp import (
    WSGIServer, get_internal_wsgi_application, run,
)

截止到該部分,Command類實際上就是一個初始化過程,全部都爲'runserver'服務,雖然很多代碼我沒有列出來,但是它確實做了一些,例如參數解析、端口指定檢測、ipv4檢測、ipv6檢測、端口是否佔用、線程檢查、文件改動檢測自動重啓服務等工作。

接下來我把注意力放在django.core.servers.basehttp下的run函數上,代碼如下

# 形參wsgi_handler的值爲StaticFilesHandler
# run方法主要是知會各個對象啓動wsgi服務
def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
    # 服務監聽的地址和端口
    server_address = (addr, port)
    # 如果是多線程運行
    if threading:
        # 調用內置元類type創建一個類WSGIServer,該類繼承了
        #(socketserver.ThreadingMixIn, WSGIServer),去代碼塊WSGIServer類中查看它本身只繼承
        # wsgiref.simple_server.WSGIServer、object這兩個類,通過type重新創建一下是給
        #類WSGIServer強行添加了一個爹socketserver.ThreadingMixIn,這麼做的意義是每次調用類
        #WSGIServer的時候都會單獨啓用一個線程來處理,說完了WSGIServer的第一個基類,
        #我們再來說它的第二個基類WSGIServer完整的繼承家族
        """
        django.core.servers.basehttp.WSGIServer
        wsgiref.simple_server.WSGIServer、 socketserver.ThreadingMixIn
        http.server.HTTPServer
        socketserver.TCPServer
        socketserver.BaseServer
        object
        """
        # httpd_cls這個變量被定義完成之後,由於大量的繼承關係,它其實已經不單純的屬於django,
        # 它是一個傳統意義上的WSGI服務對象了。
        httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
    else:
        httpd_cls = server_cls
    # 實例化該類,它是WSGI服務器與django之間相互通信的唯一樞紐通道,也就是說,
    # 當WSGI服務對象收到socket請求後,會將這個請求傳遞給django的WSGIRequestHandler
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    if threading:
        # ThreadingMixIn.daemon_threads indicates how threads will behave on an
        # abrupt shutdown; like quitting the server by the user or restarting
        # by the auto-reloader. True means the server will not wait for thread
        # termination before it quits. This will make auto-reloader faster
        # and will prevent the need to kill the server manually if a thread
        # isn't terminating correctly.
        # 等到子線程執行完成
        httpd.daemon_threads = True
    # 是將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.set_app(wsgi_handler)
    # 啓動非堵塞網絡監聽服務。
    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

httpd.serve_forever調用的是socketserver.BaseServer.serve_forever方法(關於socketserver的源碼解析點擊這裏,下面我直接說流程,原理不再累述)。

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

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

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

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

  5. 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代碼如下

class WSGIRequestHandler(simple_server.WSGIRequestHandler):
    protocol_version = 'HTTP/1.1'

    def address_string(self):
        # Short-circuit parent method to not call socket.getfqdn
        return self.client_address[0]

    def log_message(self, format, *args):
        extra = {
            'request': self.request,
            'server_time': self.log_date_time_string(),
        }
        if args[1][0] == '4':
            # 0x16 = Handshake, 0x03 = SSL 3.0 or TLS 1.x
            if args[0].startswith('\x16\x03'):
                extra['status_code'] = 500
                logger.error(
                    "You're accessing the development server over HTTPS, but "
                    "it only supports HTTP.\n", extra=extra,
                )
                return

        if args[1].isdigit() and len(args[1]) == 3:
            status_code = int(args[1])
            extra['status_code'] = status_code

            if status_code >= 500:
                level = logger.error
            elif status_code >= 400:
                level = logger.warning
            else:
                level = logger.info
        else:
            level = logger.info

        level(format, *args, extra=extra)

    def get_environ(self):
        # Strip all headers with underscores in the name before constructing
        # the WSGI environ. This prevents header-spoofing based on ambiguity
        # between underscores and dashes both normalized to underscores in WSGI
        # env vars. Nginx and Apache 2.4+ both do this as well.
        for k in self.headers:
            if '_' in k:
                del self.headers[k]

        return super().get_environ()

    def handle(self): # 關鍵代碼
        self.close_connection = True
        self.handle_one_request()
        while not self.close_connection:
            self.handle_one_request()
        try:
            self.connection.shutdown(socket.SHUT_WR)
        except (socket.error, AttributeError):
            pass

    def handle_one_request(self):
        """Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
        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 & connection closing
        handler.run(self.server.get_app())

方法handle,至於如何調用到它,需要從WSGIRequestHandler的實例化說起,上面我們提到當執行self.RequestHandler(request, client_address, self)時等同於執行django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self),而WSGIRequestHandler的繼承的父類們如下:

1. django.core.servers.basehttp.WSGIRequestHandler
2. wsgiref.simple_server.WSGIRequestHandler
3. http.server.BaseHTTPRequestHandler
4. socketserver.StreamRequestHandler
5. socketserver.BaseRequestHandler
6. object

實例化類WSGIRequestHandler時發現它並沒有__init____call__方法,需要去父類中找,最終在socketserver.BaseRequestHandler中找到,它調用了self.hande方法,注意self.handle並不是直接調用BaseRequestHandler中的handle,根據對象屬性的查找關係,會去django.core.servers.basehttp.WSGIRequestHandler類中找,找到了handle,其實是相當於回調了handle,代碼如下

def handle(self):
    self.close_connection = True
    self.handle_one_request()
    while not self.close_connection:
        self.handle_one_request() # 關鍵代碼
    try:
        self.connection.shutdown(socket.SHUT_WR)
    except (socket.error, AttributeError):
        pass

關鍵代碼:self.handle_one_request()直接在當前類中找到,代碼如下

def handle_one_request(self):
    """Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
    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
    # 實例化了ServerHandler對象
    handler = ServerHandler(
        self.rfile, self.wfile, self.get_stderr(), self.get_environ()
    )
    # 將django.contrib.staticfiles.handlers.StaticFilesHandler轉交給ServerHandler去運行,
    # 而ServerHandler對象並沒有run方法,去它的父類們中去找
    handler.request_handler = self      # backpointer for logging & connection closing
    handler.run(self.server.get_app())

run方法位置:

1、django.core.servers.basehttp.ServerHandler
2、wsgiref.simple_server.ServerHandler
3、wsgiref.handlers.SimpleHandler
4、wsgiref.handlers.BaseHandler # 在此處找到run方法
5object

進入run方法:

 def run(self, application):
        """Invoke the application"""
        # Note to self: don't move the close()!  Asynchronous servers shouldn't
        # call close() from finish_response(), so if you close() anywhere but
        # the double-error branch here, you'll break asynchronous servers by
        # prematurely closing.  Async servers must return from 'run()' without
        # closing if there might still be output to iterate over.
        try:
            self.setup_environ()
            self.result = application(self.environ, self.start_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.

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

進入到StaticFilesHandler類中:

class StaticFilesHandler(WSGIHandler): # django專門用來處理靜態文件的類
    """
    WSGI middleware that intercepts calls to the static files directory, as
    defined by the STATIC_URL setting, and serves those files.
    """
    # May be used to differentiate between handler types (e.g. in a
    # request_finished signal)
    handles_files = True

    def __init__(self, application):
        self.application = application
        # 解析配置的靜態文件路徑
        self.base_url = urlparse(self.get_base_url())
        super().__init__()

    def load_middleware(self):
        # Middleware are already loaded for self.application; no need to reload
        # them for self.
        pass

    def get_base_url(self):
        # 檢查靜態文件相關配置是否正確
        utils.check_settings()
        # 返回配置中的靜態文件
        return settings.STATIC_URL

    def _should_handle(self, path):
        """
        Check if the path should be handled. Ignore the path if:
        * the host is provided as part of the base_url
        * the request's path isn't under the media path (or equal)
        """
        # 路徑是否以靜態路徑開頭,並且配置文件沒有給出靜態文件的Host
        return path.startswith(self.base_url[2]) and not self.base_url[1]

    def file_path(self, url):
        """
        Return the relative path to the media file on disk for the given URL.
        """
        # 獲取文件的相對路徑
        relative_url = url[len(self.base_url[2]):]
        return url2pathname(relative_url)

    def serve(self, request):
        """Serve the request path."""
        # 啓動server處理靜態文件
        return serve(request, self.file_path(request.path), insecure=True)

    def get_response(self, request):
        from django.http import Http404
        # 如果是靜態文件路徑則使用server處理
        if self._should_handle(request.path):
            try:
                return self.serve(request)
            except Http404 as e:
                return response_for_exception(request, e)
        return super().get_response(request)

    def __call__(self, environ, start_response):
        # 先判斷請求url是否是靜態文件路徑
        if not self._should_handle(get_path_info(environ)):
            # 如果不是靜態文件路徑,則正常處理
            return self.application(environ, start_response) # 關鍵代碼
        # 如果是靜態文件路徑則調用父方法處理
        return super().__call__(environ, start_response)

self.application(environ, start_response) ,先說self.application是個啥呢,可以看到在該類的__init__方法中執行了一個self.application = application,那它的值到底是啥呢?

讀源碼的竅門在於讀一點記錄一點,遇到看不懂的變量打印一下值看一下即可,最好不要重複回頭,那樣只會讓你更暈,例如我們用管理用戶(修改django源碼需要權限)修改文件django.contrib.staticfiles.handlers.StaticFilesHandler加一行打印代碼

def __init__(self, application):
    self.application = application
    print('django源碼打印--->self.application值爲',self.application) # 打印
    self.base_url = urlparse(self.get_base_url())
    super().__init__()

然後重啓django可以看到self.application的值爲<django.core.handlers.wsgi.WSGIHandler object at 0x106cf0278>,去查看類django.core.handlers.wsgi.WSGIHandler 的實例化發現加載了中間件self.load_middleware(),至此我們完成分析如何從wsgi服務到將url請求信息轉交給django,剩下的就是django的內部流程啦,我們有機會再繼續剖析吧

另外補充:可以用同樣的手法查看envion變量,該變量非常重要,http協議的請求信息都被放入了environ變量中。我們分析流程中的WSGIServer類主要用於處理socket請求和對接WSGIRequestHandlerWSGIRequestHandler類主要針對environ進行預處理和對接WSGIServerHandler,而ServerHandler類則主要用於執行應用程序(application)和返回響應給WSGIServer

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章