契机:Django 项目中对全局变量访问要加锁吗?其是怎么处理客户端的请求的,启进程还是线程?
-
SocketServer 的 listen request_queue_size
-
wsgiref: WSGIServer …-> BaseServer 的 serve_forever 中的 select.select 是怎么处理同时过来的请求, socket client 加个 sleep
-
新版的Django的中间件还处理 process_request吗?用 self._middleware_chain 处理
-
这是一个经典的 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())
结论:
- 用 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的执行顺序和请求信号的发送
- 类 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()
时间比较仓促,先写个大概,有机会再详细描述!
也可以留言感兴趣的某个细节共同切磋!