WSGI
,uwsgi
和uWSGI
WSGI
:全稱是Web Server Gateway Interface
,是一種規范,只適用於Python
語言。要實現WSGI
協議,必須同時實現web server
和web application
,當前運行在WSGI
協議之上的web框架有Bottle
, Flask
, Django
。
uwsgi
:與WSGI
一樣是一種通信協議,是uWSGI
服務器的獨占協議,用於定義傳輸信息的類型(type of information
),每一個uwsgi packet
前4byte
為傳輸信息類型的描述,與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
中,最后調用run
,user_reloader
為True
時,會通過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_app
和get_app
方法設置和獲取wsgi_handler
當來新請求時,調用handler_request_noblock
方法,創建WSGIRequestHandler
實例處理請求(finish_request
)
WSGIRequestHandler
在實例化的時候,會調用自身的handle
方法,這個方法會創建一個ServerHandler
實例,調用其run
方法
在run
方法中使用get_app
,獲得WSGIHandler
,獲取response
,傳入start_response
回調,用來處理返回的header
和status
,使用finish_response()
返回response
Reference
: 做python Web開發你要理解:WSGI & uwsgi