Django源码分析

WSGI:全称是Web Server Gateway Interface,是一种规范,只适用于Python语言。要实现WSGI协议,必须同时实现web serverweb application,当前运行在WSGI协议之上的web框架有Bottle, Flask, Django
uwsgi:与WSGI一样是一种通信协议,是uWSGI服务器的独占协议,用于定义传输信息的类型(type of information),每一个uwsgi packet4byte为传输信息类型的描述,与WSGI协议是两种东西,据说该协议是fcgi协议的10倍快。
uWSGI:是一个web服务器,实现了WSGI协议、uwsgi协议、http协议等。

入口

入口函数在manage.py中,从execute_from_command_line(sys.argv)开始,这时候会传入[manage.py文件在的位置,command(runserver), 端口号]

def execute_from_command_line(argv=None):
    """
    A simple method that runs a ManagementUtility.
    """
    # 使用argv进行实例化
    utility = ManagementUtility(argv)
    utility.execute()

接下来调用execute()方法,根据注释,这个方法根据subcommand解析出需要的操作:

def execute(self):
    """
    Given the command-line arguments, this figures out which subcommand is
    being run, creates a parser appropriate to that command, and runs it.
    """
    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:
            # check_errors是一个闭包,中间执行的`django.setup`进行了一系列的导包操作
            # 包括`INSTALLED_APPS`,检查是否有重复的
            # 会有apps_ready,models_ready, ready三个状态
               autoreload.check_errors(django.setup)()

# 略去对help version命令的处理代码
    self.fetch_command(subcommand).run_from_argv(self.argv)

关键在于最后一句,首先看fetch_command(subcommand此处为runserver),用来获取执行命令所需要的类:

    def fetch_command(self, subcommand):
        """
        Tries 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
        commands = get_commands()
        try:
            app_name = commands[subcommand]
        except KeyError:
            # ...
        if isinstance(app_name, BaseCommand):
            # If the command is already loaded, use it directly.
            klass = app_name
        else:
            klass = load_command_class(app_name, subcommand)
        return klass

接下来是run_from_argv

def run_from_argv(self, argv):
    self._called_from_command_line = True
    parser = self.create_parser(argv[0], argv[1])
    options = parser.parse_args(argv[2:])  # 返回一个NameSpace
    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)
    except Exception as e:
        # ...
    finally:
       # ...

主要看execute

def execute(self, *args, **options):
  # 略去进行一些基本的输出设置
  try:
      if self.requires_system_checks and not options.get('skip_checks'):
          self.check()
      if self.requires_migrations_checks:
          self.check_migrations()
      output = self.handle(*args, **options)

  finally:
      if saved_locale is not None:
          translation.activate(saved_locale)
  return output

通过调用handle,对地址端口进行检查:

def handle(self, *args, **options):
    # 对DEBUG和ALLOWED_HOSTS进行检查
    # ...
        
        # 对端口 地址合法性进行检查
        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 not self.addr:
        self.addr = '::1' if self.use_ipv6 else '127.0.0.1'
        self._raw_ipv6 = self.use_ipv6
    self.run(**options)

Pycharm中,最后调用runuser_reloaderTrue时,会通过restart_with_reloader开启一个新的进程,这个进程再次重复上面的调用过程,当再次调用到python_reloader时,开启一个新的线程:

def run(self, **options):
    # 如果use_reloader为True,则会在`autoreload.main`中开启一个新的线程
    if use_reloader:
        autoreload.main(self.inner_run, None, options)
    else:
        self.inner_run(None, **options)

新开的线程运行inner_run,输出我们常见到的信息:

def inner_run(self, *args, **options):
   try:
       # 通过调用get_internal_wsgi_application获得一个WSGIHandler Object
       # django.core.handlers.wsgi.py中进行WSGIHandler实例化
       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:
      # ...

最终调用run方法(django.core.servers.basehhtp.py):

def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
    server_address = (addr, port)
    if threading:
        # httpd_cls的类是wsgiserver
        httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, server_cls), {})
    else:
        httpd_cls = server_cls
    # 实例化一个WSGIServer, django.core.servers.basehttp.py
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    if threading:
        httpd.daemon_threads = True
    # wsgi_handler实际上就是application
    httpd.set_app(wsgi_handler)
    httpd.serve_forever()

请求与响应

serve_forever中通过select来监听是否有请求到达:

   def serve_forever(self, poll_interval=0.5):
   # ... 
      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()

一旦有请求到达,则执行_handle_request_noblock

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 Exception:
            self.handle_error(request, client_address)
            self.shutdown_request(request)
        except:
            self.shutdown_request(request)
            raise
    else:
        self.shutdown_request(request)

主要在process_request中处理请求:

def process_request(self, request, client_address):
    self.finish_request(request, client_address)
    self.shutdown_request(request)

finish_request会直接实例化BaseRequestHandler

class BaseRequestHandler:
    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

主要是通过handle()中的run()

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

因为WSGIHandler实现了__call__方法,所以可以直接调用:

class WSGIHandler(base.BaseHandler):
    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

这就是一个熟悉的application格式了。流程如图:

通过run方法,会创建WSGIServer实例,通过set_appget_app方法设置和获取wsgi_handler
当来新请求时,调用handler_request_noblock方法,创建WSGIRequestHandler实例处理请求(finish_request)
WSGIRequestHandler在实例化的时候,会调用自身的handle方法,这个方法会创建一个ServerHandler实例,调用其run方法
run方法中使用get_app,获得WSGIHandler,获取response,传入start_response回调,用来处理返回的headerstatus,使用finish_response()返回response

Reference: 做python Web开发你要理解:WSGI & uwsgi

posted @ 2019-04-11 19:25  yuyinzi  阅读(4162)  评论(1)    收藏  举报