Django runserver 源码流程分析

契机:Django 项目中对全局变量访问要加锁吗?其是怎么处理客户端的请求的,启进程还是线程?

  1. SocketServer 的 listen request_queue_size

  2. wsgiref: WSGIServer …-> BaseServer 的 serve_forever 中的 select.select 是怎么处理同时过来的请求, socket client 加个 sleep

  3. 新版的Django的中间件还处理 process_request吗?用 self._middleware_chain 处理

  4. 这是一个经典的 wsgiref 的经典例子,深入查看 make_server 的源码可知,其封装了 BaseHTTPServer -> SocketServer 这和 mega_server/mega_client 中用到的 SocketServer 的套路 是一样的

     from wsgiref.simple_server import make_server
    
     def application(environ, start_response):
     	start_response('200 OK', [('Content-Type', 'text/html')])
     	return [b'Hello, World']
    
     if __name__ == '__main__':
     	server = make_server('', 8888, application)
     	print("server running on port 8888")
     	server.serve_forever()
     
     而本文分析的 runserver 流程又封装了 wsgiref 模块,
     httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6),
     从而可知 WSGIRequestHandler 的 handle 函数会调用到 application 方法 (就是 application = get_wsgi_application()) 
    

结论:

  1. 用 python manage.py runserver 0.0.0.0:8080 启动服务后,Django 对每一个请求都会启动一个线程去执行 C:\Python27\Lib\site-packages\django\core\wsgi.py 中的函数 get_wsgi_application 返回的类 WSGIHandler 的对象即就是调用该对象的 call() 方法。而 call 的执行过程不在本文的讨论范围,请参考 Django请求生命周期的源码分析:中间件的process_request 、 process_view 、process_response的执行顺序和请求信号的发送
  2. 类 BaseHandler (C:\Python27\Lib\wsgiref\handlers.py) 的 run 除了调用 application 还调用 self.close() 其会发送 request_finished 信号

分析 python manage.py runserver 0.0.0.0:8080 的执行过程

manage.py:  execute_from_command_line -> utility.execute() ->self.fetch_command(subcommand).run_from_argv(self.argv)
对于该流程:
subcommand 就是 runserver, 
fetch_command 返回的是  django.contrib.staticfiles.management.commands.runserver 中的 Command 类
run_from_argv -> self.execute -> self.handle -> self.run -> self.inner_run -> run  然后就到 from wsgiref import simple_server 即 WSGIServer

part 1: xxx\management\commands\runserver

D:\WorkSpace\Archiver\archiver_gitcode\venv\Lib\site-packages\django\contrib\staticfiles\management\commands\runserver.py		
	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 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)   ----------------------step6_1
			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


D:\WorkSpace\Archiver\archiver_gitcode\venv\Lib\site-packages\django\core\management\commands\runserver.py
	from django.core.management.base import BaseCommand, CommandError
	from django.core.servers.basehttp import (WSGIServer, get_internal_wsgi_application, run,)
	
	class Command(BaseCommand):
		help = "Starts a lightweight Web server for development."
	
		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[str("DJANGO_COLORS")] = str("nocolor")
			super(Command, self).execute(*args, **options)                  ----------------------step2
		
		def get_handler(self, *args, **options):
			"""
			Returns the default WSGI handler for the runner.
			"""
			return get_internal_wsgi_application()                         -----------------------step6_2
		def handle(self, *args, **options):
			from django.conf import settings

			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 = '::1' if self.use_ipv6 else '127.0.0.1'
				self._raw_ipv6 = self.use_ipv6
			self.run(**options)                                 ----------------------------------step4
			
	    def run(self, **options):
			"""
			Runs the server, using the autoreloader if needed
			"""
			use_reloader = options['use_reloader']

			if use_reloader:
				autoreload.main(self.inner_run, None, options)
			else:
				self.inner_run(None, **options)                 ----------------------------------step5

		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.
			self.check_migrations()
			now = datetime.now().strftime('%B %d, %Y - %X')
			if six.PY2:
				now = now.decode(get_system_encoding())
			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 = self.get_handler(*args, **options)  ----------------------------------step6
				run(self.addr, int(self.port), handler,
					ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls) -------step7
					
					
D:\WorkSpace\Archiver\archiver_gitcode\venv\Lib\site-packages\django\core\management\base.py
	class BaseCommand(object):
		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)                           -----------------step1
				pass
		
		def execute(self, *args, **options):
			"""
			Try to execute this command, performing system checks if needed (as
			controlled by the ``requires_system_checks`` attribute, except if
			force-skipped).
			"""
			if options['no_color']:
				self.style = no_style()
				self.stderr.style_func = None
			if options.get('stdout'):
				self.stdout = OutputWrapper(options['stdout'])
			if options.get('stderr'):
				self.stderr = OutputWrapper(options['stderr'], self.stderr.style_func)

			saved_locale = None
			if not self.leave_locale_alone:
				# Deactivate translations, because django-admin creates database
				# content like permissions, and those shouldn't contain any
				# translations.
				from django.utils import translation
				saved_locale = translation.get_language()
				translation.deactivate_all()

			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)                  ----------------------step3
				pass

part 2: 封装 wsgiref

D:\WorkSpace\Archiver\archiver_gitcode\venv\Lib\site-packages\django\core\servers\basehttp.py				
	from wsgiref import simple_server
	from django.core.wsgi import get_wsgi_application
	# Inheriting from object required on Python 2.
	class ServerHandler(simple_server.ServerHandler, object):
		def handle_error(self):
			# Ignore broken pipe errors, otherwise pass on
			if not is_broken_pipe_error():
				super(ServerHandler, self).handle_error()
				
	class WSGIServer(simple_server.WSGIServer, object):
		pass
		
	class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
		def handle(self):
			"""Copy of WSGIRequestHandler, 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(                             -------------------------step15 生成 ServerHandler 对象,主要是为了调用其 run 方法,传入注册过的符合 wsgi 规范的 WSGIHandler 对象
				self.rfile, self.wfile, self.get_stderr(), self.get_environ()
			)
			handler.request_handler = self      # backpointer for logging
			handler.run(self.server.get_app())                   -------------------------step16
	
	def get_internal_wsgi_application():
		"""
		Loads and returns the WSGI application as configured by the user in
		``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
		this will be the ``application`` object in ``projectname/wsgi.py``.

		This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
		for Django's internal server (runserver); external WSGI servers should just
		be configured to point to the correct application object directly.

		If settings.WSGI_APPLICATION is not set (is ``None``), we just return
		whatever ``django.core.wsgi.get_wsgi_application`` returns.
		"""
		from django.conf import settings
		app_path = getattr(settings, 'WSGI_APPLICATION')
		if app_path is None:
			return get_wsgi_application()                           ------------------------值得深究1,返回符合 wsgi 规范的 WSGIHandler 对象
		pass
	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), {}) ---扩展:猜测 socketserver.ThreadingMixIn 大概率是 C:\Python27\Lib\SocketServer.py 中的 ThreadingMixIn
		else:
			httpd_cls = server_cls
		httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)   ------------------step8 注册 WSGIRequestHandler 后来回过神来这就是SocketServer 的套路
		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                              ------------------------值得深究2
		httpd.set_app(wsgi_handler)                                  ------------------------step9 注册符合 wsgi 规范的 WSGIHandler 对象
		httpd.serve_forever()

par3: wsgiref -> BaseHTTPServer -> SocketServer

C:\Python27\Lib\wsgiref\simple_server.py
	from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
	from wsgiref.handlers import SimpleHandler
	class ServerHandler(SimpleHandler):
		server_software = software_version
		pass
	class WSGIServer(HTTPServer):
		pass
	class WSGIRequestHandler(BaseHTTPRequestHandler):
		pass

C:\Python27\Lib\wsgiref\handlers.py
	class SimpleHandler(BaseHandler):
		pass
	class BaseHandler:
		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) ------------step17 application 就是注册的符合 wsgi 规范的 WSGIHandler 对象, 非常重要的结论
				self.finish_response()                                                          就是把 视图函数里返回的 HttpResponse 对象赋值给 self.result
			    .......
			def finish_response(self):
				"""Send any iterable data, then close self and the iterable

				Subclasses intended for use in asynchronous servers will
				want to redefine this method, such that it sets up callbacks
				in the event loop to iterate over the data, and to call
				'self.close()' once the response is finished.
				"""
				try:
					if not self.result_is_file() or not self.sendfile():
						for data in self.result:
							self.write(data)
						self.finish_content()
				finally:
					self.close()
					
			def close(self):
				"""Close the iterable (if needed) and reset all instance vars

				Subclasses may want to also drop the client connection.
				"""
				try:
					if hasattr(self.result,'close'):
						self.result.close()                                ----------------step18 这里的 self.result 就是视图函数里返回的 HttpResponse 对象 from django.http import HttpResponse
				finally:                                                                   这会调用到视图函数  HttpResponse 类的的 close() 函数, close 会通过
					self.result = self.headers = self.status = self.environ = None         signals.request_finished.send(sender=self._handler_class) 发送 request_finished 信号
					self.bytes_sent = 0; self.headers_sent = False
				
C:\Python27\Lib\BaseHTTPServer.py
	class HTTPServer(SocketServer.TCPServer):
		pass
	class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
		pass

C:\Python27\Lib\SocketServer.py
	class TCPServer(BaseServer):
	    def server_activate(self):
			"""Called by constructor to activate the server.

			May be overridden.

			"""
			self.socket.listen(self.request_queue_size)             ----------------------值得深究3
		def get_request(self):
			"""Get the request and client address from the socket.

			May be overridden.

			"""
			return self.socket.accept()
	class BaseServer:
		def serve_forever(self, poll_interval=0.5):
			"""Handle one request at a time until shutdown.

			Polls for shutdown every poll_interval seconds. Ignores
			self.timeout. If you need to do periodic tasks, do them in
			another thread.
			"""
			self.__is_shut_down.clear()
			try:
				while not self.__shutdown_request:
					# XXX: Consider using another file descriptor or
					# connecting to the socket to wake this up instead of
					# polling. Polling reduces our responsiveness to a
					# shutdown request and wastes cpu at all other times.
					r, w, e = _eintr_retry(select.select, [self], [], [],     ------------值得深究4
										   poll_interval)
					if self in r:
						self._handle_request_noblock()                     ----------------step10
			finally:
				self.__shutdown_request = False
				self.__is_shut_down.set()
				
		def _handle_request_noblock(self):
			"""Handle one request, without blocking.

			I assume that select.select has returned that the socket is
			readable before this function was called, so there should be
			no risk of blocking in get_request().
			"""
			try:
				request, client_address = self.get_request()
			except socket.error:
				return
			if self.verify_request(request, client_address):
				try:
					self.process_request(request, client_address)         ----------------step11
				except:
					self.handle_error(request, client_address)
					self.shutdown_request(request)
			else:
				self.shutdown_request(request)
				
		def process_request(self, request, client_address):
			"""Call finish_request.

			Overridden by ForkingMixIn and ThreadingMixIn.              ------------------step12_a 如果 python manage.py runserver 0.0.0.0:8080 启动默认是启用多线程,
																								   所以应该走 类 ThreadingMixIn 的 process_request,正好呼应上文提到的: 扩展
			"""                                                                                    参考 https://blog.csdn.net/cpxsxn/article/details/106102441
			self.finish_request(request, client_address)                 -----------------step12_b 如果 python manage.py runserver 0.0.0.0:8080 --nothreading 则禁用多线程,
			self.shutdown_request(request)                                                         所以走的是 类 BaseServer 的 process_request 即右边的这个函数
		
		def finish_request(self, request, client_address):           
			"""Finish one request by instantiating RequestHandlerClass."""
			self.RequestHandlerClass(request, client_address, self)    -------------------step13 调用上文注册的 WSGIRequestHandler

	
	class ThreadingMixIn:
		"""Mix-in class to handle each request in a new thread."""

		# Decides how threads will act upon termination of the
		# main process
		daemon_threads = False

		def process_request_thread(self, request, client_address):
			"""Same as in BaseServer but as a thread.

			In addition, exception handling is done here.

			"""
			try:
				self.finish_request(request, client_address)
				self.shutdown_request(request)
			except:
				self.handle_error(request, client_address)
				self.shutdown_request(request)

		def process_request(self, request, client_address):
			"""Start a new thread to process the request."""
			t = threading.Thread(target = self.process_request_thread,
								 args = (request, client_address))
			t.daemon = self.daemon_threads
			t.start()


	class StreamRequestHandler(BaseRequestHandler):
		pass
	class BaseRequestHandler:

		"""Base class for request handler classes.

		This class is instantiated for each request to be handled.  The
		constructor sets the instance variables request, client_address
		and server, and then calls the handle() method.  To implement a
		specific service, all you need to do is to derive a class which
		defines a handle() method.

		The handle() method can find the request as self.request, the
		client address as self.client_address, and the server (in case it
		needs access to per-server information) as self.server.  Since a
		separate instance is created for each request, the handle() method
		can define other arbitrary instance variables.

		"""

		def __init__(self, request, client_address, server):
			self.request = request
			self.client_address = client_address
			self.server = server
			self.setup()
			try:
				self.handle()                                       ---------------------step14 正好就到了 step15 同时呼应上文提到的:  mega_server/mega_client 中用到的 SocketServer 的套路
			finally:
				self.finish()

时间比较仓促,先写个大概,有机会再详细描述!
也可以留言感兴趣的某个细节共同切磋!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值