回顧上一章
wsgi通過ServerHandler來執行django的應用程序,第一個落地對象是,django.contrib.staticfiles.handlers.StaticFilesHandler
。
class StaticFilesHandler(WSGIHandler):
def __init__(self, application):
self.application = application
super(StaticFilesHandler, self).__init__()
def _should_handle(self, path):
return path.startswith(self.base_url[2]) and not self.base_url[1]
def get_response(self, request):
from django.http import Http404
if self._should_handle(request.path):
try:
return self.serve(request)
except Http404 as e:
if settings.DEBUG:
from django.views import debug
return debug.technical_404_response(request, e)
return super(StaticFilesHandler, self).get_response(request)
def __call__(self, environ, start_response):
if not self._should_handle(get_path_info(environ)):
return self.application(environ, start_response)
return super(StaticFilesHandler, self).__call__(environ, start_response)
django.contrib.staticfiles.handlers.StaticFilesHandler
的作用是當請求出現時,先檢查url是不是一個靜態文件請求,如果是的話則進入靜態文件(圖片、css樣式文件、js腳本文件等等)處理的view,如果不是的話則將該請求提交給Django的handler來進行處理(解析url、執行對應的view代碼塊、渲染和返回template)。
補充說明
__call__
方法中比較有意思的是根據條件進行不同的返回。
- self.application(environ, start_response)
執行的是django.core.handlers.wsgi.WSGIHandler.__call__
對象,對象的繼承集合中不包含django.contrib.staticfiles.StaticFilesHandler
,也就是說后續如果調用了self.get_response
,那么它執行的是django.core.handlers.base.BaseHandler.get_response
方法。 - super(StaticFilesHandler, self).call(environ, start_response)
雖然執行的也是django.core.handlers.wsgi.WSGIHandler.__call__
對象,但是這個對象的繼承集合里面包含django.contrib.staticfiles.StaticFilesHandler
,因此后續如果調用了self.get_response
,那么它執行的是django.contrib.staticfiles.StaticFilesHandler.get_response
方法。
?
?
URL解析
django.core.handlers.wsgi.py
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super(WSGIHandler, self).__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)
try:
request = self.request_class(environ)
except UnicodeDecodeError:
logger.warning(
'Bad Request (UnicodeDecodeError)',
exc_info=sys.exc_info(),
extra={
'status_code': 400,
}
)
response = http.HttpResponseBadRequest()
else:
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
request = self.request_class(environ)
負責初始化 WSGIRequest對象(這個對象有點想wsgi接口中的WSGIRequestHandler對象做的工作,針對environ進行預處理),在初始化過程中主要是在原本environ中加工處理一些django程序能讀得懂的參數(例如header、PATH_INFO、REQUEST_METHOD、charset等等)。
response = self.get_response(request)
正如上面的補充說明
環節說明一樣,針對不同請求執行不同的方法,我主要是跟常規請求而不是靜態文件請求,因此這個self.get_response實際上指的是django.core.handlers.base.BaseHandler.get_response
。
django.core.handlers.base.py
class BaseHandler(object):
def __init__(self):
...
self._middleware_chain = None
def load_middleware(self):
...
if settings.MIDDLEWARE is None:
...
else:
handler = convert_exception_to_response(self._get_response)
...
self._middleware_chain = handler
def get_response(self, request):
...
response = self._middleware_chain(request)
...
return response
def _get_response(self, request):
response = None
if hasattr(request, 'urlconf'):
urlconf = request.urlconf
set_urlconf(urlconf)
resolver = get_resolver(urlconf)
else:
resolver = get_resolver() # 初始化RegexURLResolver對象
resolver_match = resolver.resolve(request.path_info)
callback, callback_args, callback_kwargs = resolver_match
request.resolver_match = resolver_match
...
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)
...
return response
response = self._middleware_chain(request)
,當執行完這行代碼時,response變量就是一個html了,也就是說,所有的工作都隱藏在這行代碼中。然而self._middleware_chain
變量初始化的時候是None,然后在get_response
方法中卻可以被調用,這表明它是至少是一個方法或函數,也就是說,在調用get_response
方法之前,這個self._middleware_chain
變量已經被賦值過了,所以我要去找哪里有針對這個self._middleware_chain
處理的地方。
-
django.core.handlers.base.BaseHandler.load_middleware
方法中將convert_exception_to_response(self._get_response)
賦值給了self._middleware_chain
。 -
convert_exception_to_response
是一個裝飾器,當self._get_response
執行過程中遇到錯誤時,則try住這個錯誤并根據當前錯誤代碼返回一個對應的錯誤信息頁面。
django.core.handlers.base.BaseHandler._get_response
負責解析URL和傳遞參數給view。
-
resolver = get_resolver()
初始化RegexURLResolver對象。 -
resolver_match = resolver.resolve(request.path_info)
通過path_info
解析url所綁定的對象,并將解析結果對象賦值給resolver_match
變量。 -
callback, callback_args, callback_kwargs = resolver_match
其中callback
是具體的view,callback_args
是執行這個view需要提供的列表參數(類似與tornado中的self.path_args
),callback_kwargs
是執行這個view需要提供的字典參數。 -
wrapped_callback = self.make_view_atomic(callback)
是針對數據庫事務支持所封裝的一個對象。 -
response = wrapped_callback(request, *callback_args, **callback_kwargs)
是執行這個具體的view(request, *callback_args, **callback_kwargs)。
小結
流程性的記錄了request從初始化到url解析、再到response執行和返回過程。
?
?
?
深入理解URL解析
上一節有提及到resolver = get_resolver()
、resolver_match = resolver.resolve(request.path_info)
這兩行代碼,它就是理解URL解析的入口,另外一個理解URL解析的入口在項目文件的urls.py文件中(也就是我當前HelloWorld項目的HelloWorld.urls.py
)。
HelloWorld.urls.py
from django.conf.urls import url # 這里是重點
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
url(r'^admin/', admin.site.urls)
這里采用了url()函數對正則表達式的url和視圖進行包裹,反過來看就是將正則表達式url和視圖當作參數傳遞給url()函數。
備注: admin.site.urls是一個tuple,里面包含一個url集合(另外一組urlpatterns)。
?
django.conf.urls.__init__.py
from django.urls import (
LocaleRegexURLResolver, RegexURLPattern, RegexURLResolver,
)
def url(regex, view, kwargs=None, name=None):
if isinstance(view, (list, tuple)):
# For include(...) processing.
urlconf_module, app_name, namespace = view
return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
elif callable(view):
return RegexURLPattern(regex, view, kwargs, name)
else:
raise TypeError('view must be a callable or a list/tuple in the case of include().')
通過查看url()函數的定義,regex參數可以看作是r'^admin/', view參數可以看作是admin.site.urls。接下來是根據view參數的對象類型來調用不同的對象進行解析,因此這里臨時插入一段代碼來看看admin.site.urls。
django.contrib.admin.sites.py
class AdminSite(object): def __init__(self, name='admin'): self.name = name @property def urls(self): return self.get_urls(), 'admin', self.name def get_urls(self): from django.conf.urls import url, include def wrap(view, cacheable=False): def wrapper(*args, **kwargs): return self.admin_view(view, cacheable)(*args, **kwargs) wrapper.admin_site = self return update_wrapper(wrapper, view) urlpatterns = [ url(r'^$', wrap(self.index), name='index'), url(r'^login/$', self.login, name='login'), url(r'^logout/$', wrap(self.logout), name='logout'), url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'), url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True), name='password_change_done'), url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'), url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut), name='view_on_site'), ] valid_app_labels = [] for model, model_admin in self._registry.items(): urlpatterns += [ url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)), ] if model._meta.app_label not in valid_app_labels: valid_app_labels.append(model._meta.app_label) if valid_app_labels: regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$' urlpatterns += [ url(regex, wrap(self.app_index), name='app_list'), ] return urlpatterns site = AdminSite()
透過這個代碼片段可以清晰的看到admin.site.urls 實際上是一個元祖(tuple)對象,因此會匹配到
if isinstance(view, (list, tuple))
條件,并執行該條件下的代碼塊(返回RegexURLResolver初始化后的對象)。
urlconf_module, app_name, namespace = view
這里將view拆分成了三個變量(對象)。
urlconf_module
=self.get_urls()
= 整個admin的所有定義的urls.app_name
='admin'
= 常量變量'admin'字符串namespace
=self.name
='admin'
= 常量變量'admin'字符串,由于site = AdminSite()
初始化過程中并沒有提供任何參數,因此采用了默認的def __init__(self, name='admin')
。
另外一個情況是當view是單個函數時,則會匹配到elif callable(view)
并執行該條件下的代碼塊(返回RegexURLPattern初始化后的對象)。
小結
上面三個代碼片段主要是為了接下來的django.urls.resolvers.RegexURLResolver對象的原理理解做一個鋪墊,因此下面我會言歸正傳,接著django.core.handlers.base.BaseHandler._get_response
往下走。
?
django.urls.resolvers.py
class ResolverMatch(object):
def __init__(self, func, args, kwargs, url_name=None, app_names=None, namespaces=None):
self.func = func
self.args = args
self.kwargs = kwargs
self.url_name = url_name
self.app_names = [x for x in app_names if x] if app_names else []
self.app_name = ':'.join(self.app_names)
self.namespaces = [x for x in namespaces if x] if namespaces else []
self.namespace = ':'.join(self.namespaces)
if not hasattr(func, '__name__'):
self._func_path = '.'.join([func.__class__.__module__, func.__class__.__name__])
else:
self._func_path = '.'.join([func.__module__, func.__name__])
view_path = url_name or self._func_path
self.view_name = ':'.join(self.namespaces + [view_path])
def __getitem__(self, index):
return (self.func, self.args, self.kwargs)[index]
def __repr__(self):
return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name=%s, app_names=%s, namespaces=%s)" % (
self._func_path, self.args, self.kwargs, self.url_name,
self.app_names, self.namespaces,
)
@lru_cache.lru_cache(maxsize=None)
def get_resolver(urlconf=None):
if urlconf is None:
from django.conf import settings
urlconf = settings.ROOT_URLCONF
return RegexURLResolver(r'^/', urlconf)
class LocaleRegexProvider(object):
def __init__(self, regex):
self._regex = regex
self._regex_dict = {}
@property
def regex(self):
language_code = get_language()
if language_code not in self._regex_dict:
regex = self._regex if isinstance(self._regex, six.string_types) else force_text(self._regex)
try:
compiled_regex = re.compile(regex, re.UNICODE) # initially regex -> '^/'
except re.error as e:
raise ImproperlyConfigured(
'"%s" is not a valid regular expression: %s' %
(regex, six.text_type(e))
)
self._regex_dict[language_code] = compiled_regex
return self._regex_dict[language_code]
class RegexURLResolver(LocaleRegexProvider):
def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
LocaleRegexProvider.__init__(self, regex)
self.urlconf_name = urlconf_name
self.callback = None
self.default_kwargs = default_kwargs or {}
self.namespace = namespace
self.app_name = app_name
self._reverse_dict = {}
self._namespace_dict = {}
self._app_dict = {}
self._callback_strs = set()
self._populated = False
self._local = threading.local()
def resolve(self, path):
path = force_text(path) # path may be a reverse_lazy object
tried = []
match = self.regex.search(path)
if match:
new_path = path[match.end():]
for pattern in self.url_patterns:
try:
sub_match = pattern.resolve(new_path)
except Resolver404 as e:
sub_tried = e.args[0].get('tried')
if sub_tried is not None:
tried.extend([pattern] + t for t in sub_tried)
else:
tried.append([pattern])
else:
if sub_match:
sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
sub_match_dict.update(sub_match.kwargs)
sub_match_args = sub_match.args
if not sub_match_dict:
sub_match_args = match.groups() + sub_match.args
return ResolverMatch(
sub_match.func,
sub_match_args,
sub_match_dict,
sub_match.url_name,
[self.app_name] + sub_match.app_names,
[self.namespace] + sub_match.namespaces,
)
tried.append([pattern])
raise Resolver404({'tried': tried, 'path': new_path})
raise Resolver404({'path': path})
@cached_property
def urlconf_module(self):
if isinstance(self.urlconf_name, six.string_types):
return import_module(self.urlconf_name)
else:
return self.urlconf_name
@cached_property
def url_patterns(self):
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
try:
iter(patterns)
except TypeError:
msg = (
"The included URLconf '{name}' does not appear to have any "
"patterns in it. If you see valid patterns in the file then "
"the issue is probably caused by a circular import."
)
raise ImproperlyConfigured(msg.format(name=self.urlconf_name))
return patterns
django.core.handlers.base.BaseHandler._get_response
方法中通過resolver = get_resolver()
來初始化RegexURLResolver對象,它調用了當前代碼片段中的get_resolver
,返回值是RegexURLResolver(r'^/', urlconf)相當于RegexURLResolver(r'^/', 'HelloWorld.urls')。
RegexURLResolver.__init__
初始化過程中,會將r'^/'賦值給self._regex
(以super的形式在父類中賦值),將'HelloWorld.urls'賦值給self.urlconf_name
。RegexURLResolver.urlconf_module
利用python Lib庫中的高級特性import_module,先創建一個空的對象,然后將'HelloWorld.urls'這個字符串當作一個模塊文件來執行,并將所有出現在這個模塊文件中的變量以attribute的形式set到這個對象中,最后把這個對象返回給調用者。
通過上面提供的HelloWorld.urls.py文件代碼塊的內容可以看的出來, urlpatterns 是一個列表對象(變量),還有url和admin這兩個對象(變量),因此這個import_module創建的變量就包含了urlpatterns、url、admin這三個attribute。RegexURLResolver.url_patterns
這個方法調用了urlconf_module
方法,然后提取出urlpatterns的值,最后把這個值返回給調用者。
django.core.handlers.base.BaseHandler._get_response
方法中通過resolver_match = resolver.resolve(request.path_info)
來返回url解析結果,該url解析結果包含了對應的view、該view執行需要用到的args和該view執行需要用到的kwargs。resolver.resolve(request.path_info)
相當于是RegexURLResolver(r'^/', 'HelloWorld.urls').resolve(request.path_info)
。
-
RegexURLResolver.resolve
這個函數非常燒腦,我盡量列詳細記錄,避免以后忘了可以再過來看快速記起來。path = force_text(path)
將路徑強制轉換成字符串.tried = []
用于記錄已解析對象(路徑).match = self.regex.search(path)
通過繼承對象LocaleRegexProvider.regex
方法來匹配路徑,由于整個當前對象RegexURLResolver
初始化時,傳遞的參數是'^/',因此不論請求過來的URL是什么,都會匹配成功,因此if match是肯定通過的。它還有另外一層作用(職責),就是專門負責匹配到'模塊',例如: 請求的url是</admin/auth/user/>,那么它將會匹配到'^/'。new_path = path[match.end():]
這行代碼的用意是將上級匹配到的'模塊'給移除掉。for pattern in self.url_patterns
這里self.url_patterns
返回的是一個包裹了HelloWorld.urls的RegexURLResolver
對象集合(由于我沒有自定義填寫任何新的url,因此它只有[url(r'^admin/', admin.site.urls)]
,經過url這個函數執行過后,它實際上返回的是這么一個東西:[<RegexURLResolver <RegexURLPattern list> (admin:admin) ^admin/>]
)。
通過for循環將這個[<RegexURLResolver <RegexURLPattern list> (admin:admin) ^admin/>]
進行分解,其實當前狀態下列表只有一個元素,因此這個for循環只會執行一次,但是它的作用不止于此,還需要耐心往下看。-
sub_match = pattern.resolve(new_path)
在這里,這個pattern就是<RegexURLResolver <RegexURLPattern list> (admin:admin) ^admin/>
這個對象,pattern.resolve
相當于RegexURLResolver.resolve,看起來像是自己把自己再次調用了一遍死循環的節奏,但是由于pattern
是外部重新實例化的一個對象,因此這里并不是遞歸,也不是死循環。admin.site.urls
==<RegexURLResolver <RegexURLPattern list> (admin:admin) ^admin/>
,再次執行上面所提到的代碼,但是當再次遇到for pattern in self.url_patterns
返回的對象就不一樣了,這次它返回的是下面這些對象(為什么會是這些東西,請看前面有列出來的get_urls()的源碼):[
<RegexURLPattern index ^$>,
<RegexURLPattern login ^login/$>,
<RegexURLPattern logout ^logout/$>,
<RegexURLPattern password_change ^password_change/$>,
<RegexURLPattern password_change_done ^password_change/done/$>,
<RegexURLPattern jsi18n ^jsi18n/$>,
<RegexURLPattern view_on_site ^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$>,
<RegexURLResolver <RegexURLPattern list> (None:None) ^auth/group/>,
<RegexURLResolver <RegexURLPattern list> (None:None) ^auth/user/>,
<RegexURLPattern app_list ^(?P<app_label>auth)/$>
]這次pattern.resolve的對象就不一樣了,當pattern對象是
RegexURLPattern
時,執行的其實RegexURLPattern.resolve
,它的作用是利用每個RegexURLPattern中的正則表達式來匹配被裁(new_path = path[match.end():]
)剪過的url; 當pattern對象還是RegexURLResolver
時,再去找到該對象下的所有urlpatterns集合。 剩下的就不是很難,所以就不列出來了。