django redirect传递参数_Django 源码分析(一):启动过程

fa8c1d6a00316f9be909e76a9ccd0d4f.png

Django启动过程

开发环境中我们通过‘python manage.py runserver ip:port’启动一个django服务,下面我们通过manage.py这个入口脚本,逐步解析django的启动过程。

1、manage.py

if __name__ == "__main__":
    # 设置环境变量(key:value)
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "SeMF.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        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?"
        ) from exc
    # 解析传递给manage.py的参数,执行具体操作
    execute_from_command_line(sys.argv)

2、django.core.management.init.execute_from_command_line

def execute_from_command_line(argv=None):
    """Run a ManagementUtility."""
    # 初始化ManagementUtility类
    utility = ManagementUtility(argv)
    # 解析传递仅来的参数,以[‘manage.py’, 'runserver','0.0.0.0:8000',...]为例,执行具体命令
    utility.execute()

访问参数继续交给了‘django.core.management.init.ManagementUtility’实例的‘execute’方法进行处理。

3、django.core.management.init.ManagementUtility.execute

# 非关键代码省略
...
# 获得操作命令,runserver
subcommand = self.argv[1]
...
# 解析参数中传递的环境设置,如--settings和--pythonpath
options, args = parser.parse_known_args(self.argv[2:])
# 设置环境变量
handle_default_options(options)
....
# 检查配置中是否存在INSTALLED_APPS,不存在设置异常内容
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.
    # 
    if subcommand == 'runserver' and '--noreload' not in self.argv:
        try:
        	# 如果启动没有添加--noreload,则代码变动时自动重载
            autoreload.check_errors(django.setup)()
        except Exception:
            ...
    # In all other cases, django.setup() is required to succeed.
    else:
        django.setup()    
...
# self.fetch_command(subcommand)通过get_command和argv[1]获得具体模块的方法
# 通过‘if isinstance(app_name, BaseCommand):klass = app_name;’过滤返回BaseCommand类型的类
# 然后执行BaseCommand类型方法的run_from_argv方法,这里获得了django.contrib.staticfiles.management.commands.runserver.Command
self.fetch_command(subcommand).run_from_argv(self.argv)   

'fetch_command'通django.core.management.init.ManagementUtility.get_commands()解析出运行的具体命令,然后通过命令的run_from_argv启动服务。

4、django.core.management.init.ManagementUtility.fetch_command

def fetch_command(self, subcommand):
    # Get commands outside of try block to prevent swallowing exceptions
    commands = get_commands()
    ... 

5、django.core.management.init.get_commands()

# 装饰器作用,暂时缓存get_commands结果
@functools.lru_cache(maxsize=None)
def get_commands():
    # find_commands找到该目录下的commands目录中所有模块的类
    # 这里找的是django.core.management.commands下的所有命令模块中的类
    commands = {name: 'django.core' for name in find_commands(__path__[0])}
	
	# 如果没有额外配置了,则返回commands
    if not settings.configured:
        return commands
	# 这里从配置中找到INSTALLED_APPS, 然后加载所有APP目录的management.commands目录下的命令模块中的类
	# 如果App中模块与前边的同名,会覆盖之前的
	# 由于'django.contrib.staticfiles'的management.commands目录下包含了runserver.py模块
	# 这里会覆盖 django.core.management.commands下的runserver.py模块
    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

django首先去‘django.core.management.commands’获取命令模块,

然后通过注册的app下面的management.commands目录下方获取命令模块,并且覆盖前面获取到的同名模块。

实际上runserver执行的就是注册的staticfiles这个应用下面的runserer模块,下面对该模块进行解读。

6、django.contrib.staticfiles.management.commands.runserver.py

from django.conf import settings
from django.contrib.staticfiles.handlers import StaticFilesHandler
from django.core.management.commands.runserver import (
    Command as RunserverCommand,
)


class Command(RunserverCommand):
    help = "Starts a lightweight Web server for development and also serves static files."

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

    def get_handler(self, *args, **options):
        """
        Return the static files serving handler wrapping the default handler,
        if static files should be served. Otherwise return the default handler.
        """
        handler = super().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’,没有run_from_argv方法,继续向下解读。

7、django.core.management.commands.runserver.Command

from django.core.management.base import BaseCommand, CommandError

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):
		# 这里使用的是django.contrib.staticfiles.management.commands.runserver.Command.add_arguments
		...

    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)

    def get_handler(self, *args, **options):
        # 这里使用的是django.contrib.staticfiles.management.commands.runserver.Command.get_handler
        ...

    def handle(self, *args, **options):
        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']
        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
        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)
            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)
        if not self.addr:
            self.addr = self.default_addr_ipv6 if self.use_ipv6 else self.default_addr
            self._raw_ipv6 = self.use_ipv6
        self.run(**options)

    def run(self, **options):
        """Run the server, using the autoreloader if needed."""
        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...nn")
        self.check(display_num_errors=True)
        # Need to check migrations here, so can't use the
        # requires_migrations_check attribute.
        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)rn"
            "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 = self.get_handler(*args, **options)
            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)

django.contrib.staticfiles.management.commands.runserver继承自django.core.management.commands.runserver

django.core.management.commands.runserver继承自django.core.management.base.BaseCommand

下面从run_from_argv方法继续解析剩下的启动过程

8、django.core.management.base.BaseCommand.run_from_argv

def run_from_argv(self, argv):
    ...
    # 进行一系列环境准备后,正式开始启动
    # 首先执行django.core.management.commands.runserver.Command.execute,在这里判断是否设置‘DJANGO_COLORS’这个环境变量
    # 然后执行自身的execute
    self.execute(*args, **cmd_options)
    # 这里尝试关闭数据库引擎连接
    connections.close_all()

启动参数交给了‘django.core.management.base.BaseCommand.execute’继续处理。

9、django.core.management.base.BaseCommand.execute

def execute(self, *args, **options):
    ...
    # 执行系统检查
    # 这里因为django.core.management.commands.runserver.Command.requires_system_checks=False,因此不执行
    if self.requires_system_checks and not options.get('skip_checks'):
        self.check()
    # 检查数据库迁移,这里好像也没有执行
    if self.requires_migrations_checks:
        self.check_migrations()
    # 重点来了,这里调用django.core.management.commands.runserver.Command.handle正式进入启动流程
   output = self.handle(*args, **options)
    ...

启动参数交给了django.core.management.commands.runserver.Command.handle继续处理。

P.s:django.contrib.staticfiles.management.commands.runserver继承自django.core.management.commands.runserver,同时重写了父类的excute和handle方法,因此此处调用的是子类的handle方法。

关于父类继承和方法重写的分析见该文:

白帽青年:Python父类继承和方法重写​zhuanlan.zhihu.com

10、django.core.management.commands.runserver.Command.handle

    def handle(self, *args, **options):
    	# 非DEBUG模式,必须在settings中设置ALLOWED_HOSTS
        if not settings.DEBUG and not settings.ALLOWED_HOSTS:
            raise CommandError('You must set settings.ALLOWED_HOSTS if DEBUG is False.')
		# 判断socket是否支持ipv6
        self.use_ipv6 = options['use_ipv6']
        if self.use_ipv6 and not socket.has_ipv6:
            raise CommandError('Your Python does not support IPv6.')
        self._raw_ipv6 = False
        # 如果没有传入ip:port,设置默认的ip和port,默认ip后续还有设置
        if not options['addrport']:
            self.addr = ''
            self.port = self.default_port
        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'])
			# 从传入参数中获取IP和端口及其他参数                                   
            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)
            # ip的相关判断
            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)

参数进过校验过后交给了django.core.management.commands.runserver.Command.run继续处理。

11、django.core.management.commands.runserver.Command.run

    def run(self, **options):
        """Run the server, using the autoreloader if needed."""
        # 代码变更是否自动重载
        use_reloader = options['use_reloader']
		# 自动重载启动,调用自己的inner_run方法
        if use_reloader:
            autoreload.run_with_reloader(self.inner_run, **options)
        else:
        	# 普通启动
            self.inner_run(None, **options)

调用‘django.core.management.commands.runserver.Command.inner_run’开始启动服务。

12、django.core.management.commands.runserver.Command.inner_run

    def inner_run(self, *args, **options):
    	...
        # 执行系统检查
        self.check(display_num_errors=True)
        # 执行数据库迁移检查
        self.check_migrations()
		...
		# 执行‘django.contrib.staticfiles.management.commands.runserver.get_handler’方法;
		# 该方法先调用‘django.core.management.commands.runserver.Command.get_handler’方法;
		# ‘get_handler’方法调用‘django.core.servers.basehttp.get_internal_wsgi_application’方法;
		# ‘get_internal_wsgi_application’获取到settings文件中设置的'WSGI_APPLICATION';
		# 如果没有设置则得到‘django.core.wsgi.get_wsgi_application’;
		# ‘get_wsgi_application’进行了django启动前的一系列操作,如加载setting配置,进行app注册和模块导入等;
		# 实际得到'django.core.handlers.wsgi.WSGIHandler'对象的实例,然后赋值给handler变量;
		# 如果是处理静态文件服务,且为DEBUG模式或者启动参数传入了‘--insecure’,则继续调用StaticFilesHandler对handler进行处理;
                # (这也是为什么关闭了DEBUG模式后,需要为静态文件配置路由的原因);
        handler = self.get_handler(*args, **options)
        # 执行‘django.core.servers.basehttp.run’方法,用WSGIServer启动服务
        run(self.addr, int(self.port), handler, ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)

经过系统检查,数据库迁移检查,

调用django.core.wsgi.get_wsgi_application()函数来获取WSGIHandler处理器对象;

获取WSGIHandler实例对象后交由django.core.servers.basehttp.run启动一个WSGIServer服务。

先介绍WSGIHandler处理器:

13、django.core.handlers.wsgi.WSGIHandler

class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 调用父类的load_middleware加载中间件
        # 通过装饰器convert_exception_to_response修饰过的self._get_response作为初始handler,
        # 从settings.MIDDLEWARE中导入中间件,用handler去实例化中间件列表中最后一个中间件,
        # 并将实例化后的结果再次用convert_exception_to_response修饰以后赋值给handler,
        # 再如此往复用handler按照中间件列表从下往上的次序实例化剩下的中间件,
        # 这里实际上利用了python的闭包原理,将中间件的方法进行封包。
        # 实际上就是将中间件的self.get_response=self._get_response得到中间件实例mw_instance,
        # 把mw_instance件的‘process_view’方法按照‘settings.MIDDLEWARE’列表从上往下放入self._view_middleware
        # 把mw_instance的‘process_template_response’方法按照‘settings.MIDDLEWARE’列表从下往上放入self._template_response_middleware
        # 把mw_instance的‘process_exception’方法按照‘settings.MIDDLEWARE’列表从下往上放入self._exception_middleware
        # 中间件的执行顺序依次process_request->process_view->如果response有render方法执行process_template_response->过程中如果有错执行process_exception->process_response
        # convert_exception_to_response(mw_instance)封装中间件,赋值给self._middleware_chain
        # convert_exception_to_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)
        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 = [
            *response.items(),
            *(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
        ]
        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

WSGIHandler在实例化过程中加载了中间件。

接着介绍WSGIServer服务

14、django.core.servers.basehttp.run

def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
	# 封装地址对象
    server_address = (addr, port)
    # 启用多线程,启动参数添加--nothreading则不启用多线程
    if threading:
    	# 用socketserver.ThreadingMixIn和WSGIServer定义启动类
    	# httpd_cls = class WSGIServer(socketserver.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:
        # 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
    # WSGIHandler
    httpd.set_app(wsgi_handler)
    # 这里用socketserver.TCPServer.serve_forever()持续监听server_address
    # 当接收到请求时候调用WSGIHandler的__call__(),封装request和response
    httpd.serve_forever()

run方法启动了一个TCPServer服务,该服务将监听内容交由WSGIRequestHandler处理,

并将self.application设置为前面获取到的WSGIHandler实例。

到这里基本完成了django启动过程的源码解读,也从中了解了中间件的加载过程。

总结

python manage.py runserver命令执行后主要做了两件事(uWSGI运行Django项目也一样):

  • 调用django.core.wsgi.get_wsgi_application()函数来获取WSGIHandler处理器对象
  • 通过调用basehttp.run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer)方法,创建WSGIServer实例,并通过调用socketserver.Baseserver.serve_forever()方法,实现请求的监听。

至此,django项目已启动,并且实现了端口监听


原文链接

【Django】源码解析django启动和访问过程(一)_weixin_39974140的博客-CSDN博客_django 程序启动​blog.csdn.net
b65502e3d3802782b49444f34f353d43.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值