django中间件工作原理
整体流程:
在接受一个Http请求之前的准备
- 启动一个支持WSGI网关协议的服务器监听端口等待外界的Http请求,比如Django自带的开发者服务器或者uWSGI服务器。
- 服务器根据WSGI协议指定相应的Handler来处理Http请求,并且初始化该Handler,在Django框架中由框架自身负责实现这一个Handler。
- 此时服务器已处于监听状态,可以接受外界的Http请求
当一个http请求到达服务器的时候
- 服务器根据WSGI协议从Http请求中提取出必要的参数组成一个字典(environ)并传入Handler中进行处理。
- 在Handler中对已经符合WSGI协议标准规定的http请求进行分析,比如加载Django提供的中间件,路由分配,调用路由匹配的视图等。
- 返回一个可以被浏览器解析的符合Http协议的HttpResponse。
工作流程解析
1、在默认项目的wsgi.py文件中,application是由一个get_wsgi_application的函数返回的。
auto_server\auto_server\wsgi.py
def get_wsgi_application():
"""
The public interface to Django's WSGI support. Should return a WSGI
callable. Allows us to avoid making django.core.handlers.WSGIHandler public API, in
case the internal WSGI implementation changes or moves in the future.
"""
django.setup(set_prefix=False)
return WSGIHandler()
总而言之,WSGIHandler在初始化的时候做了两件事情:
- 初始化所有符合Django文档定义的中间件的钩子,比如process_view, process_request等。
- 将self._middleware_chain属性赋值为经过convert_exception_to_response函数装饰的self._legacy_get_response。
2、初始化WSGIHandler
django\core\wsgi.py
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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 = list(response.items())
for c in response.cookies.values():
response_headers.append(('Set-Cookie', c.output(header='')))
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 def get_path_info(environ):
"""Return the HTTP request's PATH_INFO as a string."""
path_info = get_bytes_from_wsgi(environ, 'PATH_INFO', '/') return repercent_broken_unicode(path_info).decode() def get_script_name(environ):
"""
Return the equivalent of the HTTP request's SCRIPT_NAME environment
variable. If Apache mod_rewrite is used, return what would have been
the script name prior to any rewriting (so it's the script name as seen
from the client's perspective), unless the FORCE_SCRIPT_NAME setting is
set (to anything).
"""
if settings.FORCE_SCRIPT_NAME is not None:
return settings.FORCE_SCRIPT_NAME # If Apache's mod_rewrite had a whack at the URL, Apache set either
# SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any
# rewrites. Unfortunately not every Web server (lighttpd!) passes this
# information through all the time, so FORCE_SCRIPT_NAME, above, is still
# needed.
script_url = get_bytes_from_wsgi(environ, 'SCRIPT_URL', '')
if not script_url:
script_url = get_bytes_from_wsgi(environ, 'REDIRECT_URL', '') if script_url:
if b'//' in script_url:
# mod_wsgi squashes multiple successive slashes in PATH_INFO,
# do the same with script_url before manipulating paths (#17133).
script_url = _slashes_re.sub(b'/', script_url)
path_info = get_bytes_from_wsgi(environ, 'PATH_INFO', '')
script_name = script_url[:-len(path_info)] if path_info else script_url
else:
script_name = get_bytes_from_wsgi(environ, 'SCRIPT_NAME', '') return script_name.decode() def get_bytes_from_wsgi(environ, key, default):
"""
Get a value from the WSGI environ dictionary as bytes. key and default should be strings.
"""
value = environ.get(key, default)
# Non-ASCII values in the WSGI environ are arbitrarily decoded with
# ISO-8859-1. This is wrong for Django websites where UTF-8 is the default.
# Re-encode to recover the original bytestring.
return value.encode('iso-8859-1') def get_str_from_wsgi(environ, key, default):
"""
Get a value from the WSGI environ dictionary as str. key and default should be str objects.
"""
value = get_bytes_from_wsgi(environ, key, default)
return value.decode(errors='replace')
总而言之,WSGIHandler在初始化的时候做了两件事情:
- 初始化所有符合Django文档定义的中间件的钩子,比如process_view, process_request等。
- 将self._middleware_chain属性赋值为经过convert_exception_to_response函数装饰的self._legacy_get_response。
3、当WSGIHandler遇到Http请求
根据WSGI协议规定,application可以为一个函数,一个类,或者一个类的的实例
在get_wsgi_application函数中,可以看到application被指定为WSGIHandler类的实例,因此根据WSGI协议WSGIHanler类需要定义__call__方法。
django\core\handlers\wsgi.py
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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 = list(response.items())
for c in response.cookies.values():
response_headers.append(('Set-Cookie', c.output(header='')))
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做的主要事情:调用了BaseHandler中的self.get_response方法。
笔者在这里稍微提及一下魔术方法__call__的用法,它的作用是让类的实例能像函数一样被调用,就像重载了()运算符
4、__call__方法
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 = list(response.items())
for c in response.cookies.values():
response_headers.append(('Set-Cookie', c.output(header='')))
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
举个例子
class Hello(object): def __init__(self):
print("Class Hello instance init.") def __call__(self, *args, **kwargs):
print('Hello instance call') hello = Hello()
hello() # output
Class Hello instance init.
Hello instance call
5、分析BaseHandler中的self.get_response方法
接下来分析BaseHandler中的self.get_response方法,在上文的BaseHandler的源代码中提及过,在get_response中调用了self._legacy_get_response方法,笔者从方法的名字推测这应该是Django的一个作为向前兼容的方法。
django\core\handlers\base.py
class BaseHandler(object): def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
# Setup default url resolver for this thread
set_urlconf(settings.ROOT_URLCONF) # 在初始化load_middleware的时候self._middleware_chain属性被指定为handler,即经过convert_exception_to_response装饰的self._legacy_get_response,在这里执行了该方法
response = self._middleware_chain(request) # This block is only needed for legacy MIDDLEWARE_CLASSES; if
# MIDDLEWARE is used, self._response_middleware will be empty.
try:
# Apply response middleware, regardless of the response
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
# Complain if the response middleware returned None (a common error).
if response is None:
raise ValueError(
"%s.process_response didn't return an "
"HttpResponse object. It returned None instead."
% (middleware_method.__self__.__class__.__name__))
except Exception: # Any exception should be gathered and handled
signals.got_request_exception.send(sender=self.__class__, request=request)
response = self.handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info()) response._closable_objects.append(request) # If the exception handler returns a TemplateResponse that has not
# been rendered, force it to be rendered.
if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
response = response.render() if response.status_code == 404:
logger.warning(
'Not Found: %s', request.path,
extra={'status_code': 404, 'request': request},
) return response def _get_response(self, request):
"""
Resolve and call the view, then apply view, exception, and
template_response middleware. This method is everything that happens
inside the request/response middleware.
"""
response = None if hasattr(request, 'urlconf'):
urlconf = request.urlconf
set_urlconf(urlconf)
resolver = get_resolver(urlconf)
else:
resolver = get_resolver() # 从request中获取url,然后经过路由解析,匹配到对应的view
resolver_match = resolver.resolve(request.path_info)
callback, callback_args, callback_kwargs = resolver_match
request.resolver_match = resolver_match # 加载process_view钩子
# Apply view middleware
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
break # 执行开发者自己定义的业务逻辑,即view
if response is None:
wrapped_callback = self.make_view_atomic(callback)
try:
response = wrapped_callback(request, *callback_args, **callback_kwargs)
except Exception as e:
response = self.process_exception_by_middleware(e, request) # Complain if the view returned None (a common error).
if response is None:
if isinstance(callback, types.FunctionType): # FBV
view_name = callback.__name__
else: # CBV
view_name = callback.__class__.__name__ + '.__call__' raise ValueError(
"The view %s.%s didn't return an HttpResponse object. It "
"returned None instead." % (callback.__module__, view_name)
) # If the response supports deferred rendering, apply template
# response middleware and then render the response
elif hasattr(response, 'render') and callable(response.render):
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
# Complain if the template response middleware returned None (a common error).
if response is None:
raise ValueError(
"%s.process_template_response didn't return an "
"HttpResponse object. It returned None instead."
% (middleware_method.__self__.__class__.__name__)
) try:
response = response.render()
except Exception as e:
response = self.process_exception_by_middleware(e, request) return response def _legacy_get_response(self, request):
"""
Apply process_request() middleware and call the main _get_response(),
if needed. Used only for legacy MIDDLEWARE_CLASSES.
"""
response = None
# Apply request middleware
for middleware_method in self._request_middleware:
response = middleware_method(request)
if response:
break if response is None:
response = self._get_response(request)
return response
BaseHandler三个方法之间的关系
self.get_response调用了self._legacy_get_response,self._legacy_get_response在加载了所有process_request钩子之后,调用了self._get_response。
仔细分析一下这三个方法都干了什么事情:
self.get_response
- 调用了self._legacy_get_response方法
- 得到self._legacy_get_response方法返回的结果之后加载process_response钩子
self._get_response
- 路由解析
- 加载process_view钩子
- 执行view(开发者自行定义的业务逻辑)
- 加载process_template_response钩子
self._legacy_get_response
- 加载process_request钩子
- 调用了self._get_response方法
最后,HttpResponse被传送回WSHIHandler的__call__方法中,并按照HTTP协议返回给浏览器。
高度可扩展,可插拔式插件,参考Django源码中的中间件
流程图
django\utils\module_loading.py
def import_string(dotted_path):
"""
Import a dotted module path and return the attribute/class designated by the
last name in the path. Raise ImportError if the import failed.
"""
try:
module_path, class_name = dotted_path.rsplit('.', 1)
except ValueError as err:
raise ImportError("%s doesn't look like a module path" % dotted_path) from err module = import_module(module_path) try:
return getattr(module, class_name)
except AttributeError as err:
raise ImportError('Module "%s" does not define a "%s" attribute/class' % (
module_path, class_name)
) from err
小结:
请求来了,先到达wsgi(因为django没有socket用的是wsgi)
1、manage.py 创建完WSGIHandler()这个对象后什么都不做了停了,
- 只要一起启动,就会加载配置文件加载到内存
- 对象后面加()是执行call方法 def __call__
- 类后面加()是执行构造方法
2、停了等待什么?等待用户发来请求
3、只要有一个用户发来请求,整个call方法就会开始执行
4、返回给用户的 return response
5、你如果把源码的call方法删除,它肯定就运行不小了
配置文件
1、auto_server\auto_server\viwes.py
from django.conf import settings
2、django\conf\__init__.py
settings = LazySettings() #是某个类的对象
3、django\conf\__init__.py 中的class LazySettings(LazyObject)类
def _setup(self, name=None):
settings_module = os.environ.get(ENVIRONMENT_VARIABLE) #这里读了一下配置文件 ......
django启动auto_server\manage.py
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "auto_server.settings")
# 启动的时候os.environ.setdefault赋值 key:DJANGO_SETTINGS_MODULE vlales:auto_server.settings是我写的配置文件的路径
4、django\conf\__init__.py 中的class LazySettings(LazyObject)类
class LazySettings(LazyObject): def _setup(self, name=None):
settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
...... self._wrapped = Settings(settings_module)
#实例化了一个对象
5、 django\conf\global_settings.py是个什么?
class Settings(BaseSettings):
def __init__(self, settings_module):
# update this dict from global settings (but only for ALL_CAPS settings)
for setting in dir(global_settings):
#dir找到它里面所有的变量,global_settings是个什么鬼?是全局配置变量
if setting.isupper():
setattr(self, setting, getattr(global_settings, setting)) # store the settings module in case someone later cares
self.SETTINGS_MODULE = settings_module mod = importlib.import_module(self.SETTINGS_MODULE)
#导入了这个路径
6、你的配置如果写成小写,就是因为这它是不是没有读
class Settings(BaseSettings):
def __init__(self, settings_module):
# update this dict from global settings (but only for ALL_CAPS settings)
for setting in dir(global_settings):
#dir找到它里面所有的变量,global_settings是个什么鬼?是全局配置变量
if setting.isupper():
setattr(self, setting, getattr(global_settings, setting)) # store the settings module in case someone later cares
self.SETTINGS_MODULE = settings_module mod = importlib.import_module(self.SETTINGS_MODULE) #导入了这个路径 tuple_settings = (
"ALLOWED_INCLUDE_ROOTS",
"INSTALLED_APPS",
"TEMPLATE_DIRS",
"LOCALE_PATHS",
)
self._explicit_settings = set()
for setting in dir(mod):
if setting.isupper():
#你的配置如果写成小写,就是因为这没有通过
setting_value = getattr(mod, setting) if (setting in tuple_settings and
isinstance(setting_value, six.string_types)):
raise ImproperlyConfigured("The %s setting must be a tuple. "
"Please fix your settings." % setting)
setattr(self, setting, setting_value)
self._explicit_settings.add(setting) if not self.SECRET_KEY:
raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.") if ('django.contrib.auth.middleware.AuthenticationMiddleware' in self.MIDDLEWARE_CLASSES and
'django.contrib.auth.middleware.SessionAuthenticationMiddleware' not in self.MIDDLEWARE_CLASSES):
warnings.warn(
"Session verification will become mandatory in Django 1.10. "
"Please add 'django.contrib.auth.middleware.SessionAuthenticationMiddleware' "
"to your MIDDLEWARE_CLASSES setting when you are ready to opt-in after "
"reading the upgrade considerations in the 1.8 release notes.",
RemovedInDjango110Warning
) if hasattr(time, 'tzset') and self.TIME_ZONE:
# When we can, attempt to validate the timezone. If we can't find
# this file, no check happens and it's harmless.
zoneinfo_root = '/usr/share/zoneinfo'
if (os.path.exists(zoneinfo_root) and not
os.path.exists(os.path.join(zoneinfo_root, *(self.TIME_ZONE.split('/'))))):
raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
# Move the time zone info into os.environ. See ticket #2315 for why
# we don't do this unconditionally (breaks Windows).
os.environ['TZ'] = self.TIME_ZONE
time.tzset() def is_overridden(self, setting):
return setting in self._explicit_settings
- 读默认的配置
- 读我们导入的配置
- 默认的即使有了,我后来的还能给他覆盖,所以说用户定义的优先级更高
配置文件:为了让用户使用方便,将默认配置文件,放在内部;只让用户做常用配置
__init__.py
import os
import importlib
from . import global_settings class Settings(object):
"""
global_settings,配置获取
settings.py,配置获取
"""
def __init__(self): for item in dir(global_settings):
if item.isupper():
k = item
v = getattr(global_settings,item)
'''
这就是getattr的本质
我给你一个py文件,是不是一个对象
我要去对象里面拿它那的元素怎么拿?
'''
setattr(self,k,v) setting_path = os.environ.get('AUTO_CLIENT_SETTINGS')
md_settings = importlib.import_module(setting_path)
for item in dir(md_settings):
if item.isupper():
k = item
v = getattr(md_settings,item)
setattr(self,k,v) settings = Settings()
- 设置环境变量:os.environ['AUTO_CLIENT_SETTINGS'] = "conf.settings"
- 默认配置+用户配置
- importlib 和 getattr setattr
global_settings.py
TEST = True NAME = "GAOXU"
test.py
import sys
import os
import importlib
import requests BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASEDIR) from lib.config import settings
拿到所有的变量
__init__.py
__init__.py from . import global_settings class Settings(object):
"""
global_settings 获取
settings 获取 """
def __init__(self):
for items in dir(global_settings):
#items 方法和属性
print(items) settings = Settings()
test.py
import sys
import os
import importlib
import requests BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASEDIR) from lib.config import settings
global_settings.py
TEST = True NAME = "GAOXU"
截图
只拿大写
__init__.py
from . import global_settings class Settings(object): def __init__(self):
for item in dir(global_settings):
if item.isupper():
#items 方法和属性
print(item,getattr(global_settings,item)) settings = Settings()
截图
打印API
用户设置优先级高
文件形式实现:单例模式
src.a1.py
class Foo:
pass obj = Foo()
src.a2.py
from src.a1 import obj
print(obj)
src.a2.py
#单例模式 不管怎么玩,用的都是同一个对象
from src.a1 import obj
print(obj)
from src.a1 import obj
print(obj)