djangoソース分析:HTTP要求プロセス(wsgi)
本環境python 3.5.2,django1.10.xシリーズpython manage.py runserverコマンドでローカルデバッグサーバを起動したら、djangoのリクエストリクエストリクエストに対する処理手順全体を分析します.
ここでdjangoソースコードで中小部屋の大侠の助けを学ぶことに感謝して、小部屋の大侠の博文の住所:https://blog.csdn.net/qq_33339479/article/category/6800733
一、WSGIServerの作成
前節ではrunserverを使用してDjangoプロジェクトを実行すると、起動時にdjangoが呼び出されます.core.servers.basehttpのrun()メソッドでdjangoを作成します.core.servers.basehttp.WSGIServerクラスのインスタンスを呼び出し、その後serve_を呼び出します.forever()メソッドでHTTPサービスを開始します.runメソッドのソースコードは次のとおりです.
以上のように、WSGIServerインスタンスを作成するときにHTTPリクエストのHandlerを指定します.上記のコードはWSGIRequestHandlerを使用します.ユーザのHTTPリクエストがサーバに到着すると、WSDGIServerはWSDGIRequestHandlerインスタンスを作成し、そのhandlerメソッドを使用してHTTPリクエストを処理します(最終的にはwsgiref.handlers.BaseHandlerのrunメソッド処理を呼び出します).WSGIServer経由set_appメソッドは、上記のhandlerメソッドが最終的に設定したアプリケーション処理requestを呼び出し、responseを返す呼び出し可能なオブジェクトをアプリケーションとして設定します.
ここで、WSDGIServerはwsgirefから継承する.simple_server.WSGIServer、WSGIRequestHandlerはwsgirefから継承する.simple_server.WSGIRequestHandler,wsgirefはPython標準ライブラリから与えられたWSGIの参照実装である.
二、Requestの処理
最初のステップで述べたアプリケーションはdjangoです.core.management.commands.runserverのget_handler()メソッド
settingsファイルにWSDI_が設定されていない場合APPLICATIONでは、djangoのデフォルトの内部wsgiメソッドが使用されます.get_wsgi_アプリケーション()メソッドは、djangoにあるこのメソッドを呼び出す.core.wsgi.pyファイル内
このメソッドは、django処理環境をロードした後、WSGIHandlerクラスを返します.次に、このプロセッサのコードを見てみましょう.
クラスはbaseから継承する.BaseHandlerクラス、load_middleware()とget_response(request)メソッドは、BaseHandlerクラスのメソッドを使用します.次に、BaseHandlerクラスのコードを分析します.
そのうち_get_responseメソッドはリクエストを処理し、応答を得る最も主要なメソッドであり、以下、このメソッドのget_について説明する.resolver()メソッドで、主に呼び出しが実際にdjangoを初期化する.urls.resolver.pyファイルの下のRegexURLResolver()クラスのresolve()メソッドを解析します.次にresolve()メソッドを解析します.
ここではurlが処理関数とurlのパラメータを取得する方法を簡略化し,urlで構成されたviewビュー関数を解析し続け,resrfulスタイルの例を挙げる
url関数の機能とas_について説明します.view()関数を解析する
url関数は主にurlが一致し、処理関数とnameがRegexURLPatternクラスに渡され、初期化され、そのクラスを通じてurlの一致を要求する作業を行う.
次はas_を分析しますview()メソッド、すべての処理関数がview関数から継承され、as_が呼び出されます.view()メソッドを呼び出し、その後、パケット関数view()を呼び出し、最後に処理要求のdispath関数処理要求を呼び出し、戻り値を得る
この章では、urlリクエストが一致し、wsgiでリクエストを処理して応答値を返す方法について説明します.
ここでdjangoソースコードで中小部屋の大侠の助けを学ぶことに感謝して、小部屋の大侠の博文の住所:https://blog.csdn.net/qq_33339479/article/category/6800733
一、WSGIServerの作成
前節ではrunserverを使用してDjangoプロジェクトを実行すると、起動時にdjangoが呼び出されます.core.servers.basehttpのrun()メソッドでdjangoを作成します.core.servers.basehttp.WSGIServerクラスのインスタンスを呼び出し、その後serve_を呼び出します.forever()メソッドでHTTPサービスを開始します.runメソッドのソースコードは次のとおりです.
def run(addr, port, wsgi_handler, ipv6=False, threading=False):
server_address = (addr, port) #
if threading: #
httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {}) # WSGIServer ,
else:
httpd_cls = WSGIServer
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) #
# Sets the callable application as the WSGI application that will receive requests
httpd.set_app(wsgi_handler) # handler, application
httpd.serve_forever()
以上のように、WSGIServerインスタンスを作成するときにHTTPリクエストのHandlerを指定します.上記のコードはWSGIRequestHandlerを使用します.ユーザのHTTPリクエストがサーバに到着すると、WSDGIServerはWSDGIRequestHandlerインスタンスを作成し、そのhandlerメソッドを使用してHTTPリクエストを処理します(最終的にはwsgiref.handlers.BaseHandlerのrunメソッド処理を呼び出します).WSGIServer経由set_appメソッドは、上記のhandlerメソッドが最終的に設定したアプリケーション処理requestを呼び出し、responseを返す呼び出し可能なオブジェクトをアプリケーションとして設定します.
ここで、WSDGIServerはwsgirefから継承する.simple_server.WSGIServer、WSGIRequestHandlerはwsgirefから継承する.simple_server.WSGIRequestHandler,wsgirefはPython標準ライブラリから与えられたWSGIの参照実装である.
class WSGIServer(HTTPServer): # WSGIServer HTTPServer,HTTPServer TCPServer
"""BaseHTTPServer that implements the Python WSGI protocol"""
application = None
def server_bind(self):
"""Override server_bind to store the server name."""
HTTPServer.server_bind(self)
self.setup_environ()
def setup_environ(self): # env
# Set up base environment
env = self.base_environ = {}
env['SERVER_NAME'] = self.server_name
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
env['SERVER_PORT'] = str(self.server_port)
env['REMOTE_HOST']=''
env['CONTENT_LENGTH']=''
env['SCRIPT_NAME'] = ''
def get_app(self): # handler
return self.application
def set_app(self,application): # handler
self.application = application
二、Requestの処理
最初のステップで述べたアプリケーションはdjangoです.core.management.commands.runserverのget_handler()メソッド
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 # project setting
app_path = getattr(settings, 'WSGI_APPLICATION') # settings “WSGI_APPLICATION”
if app_path is None: # , application
return get_wsgi_application()
try:
return import_string(app_path) # , application
except ImportError as e:
msg = (
"WSGI application '%(app_path)s' could not be loaded; "
"Error importing module: '%(exception)s'" % ({
'app_path': app_path,
'exception': e,
})
)
six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg),
sys.exc_info()[2])
settingsファイルにWSDI_が設定されていない場合APPLICATIONでは、djangoのデフォルトの内部wsgiメソッドが使用されます.get_wsgi_アプリケーション()メソッドは、djangoにあるこのメソッドを呼び出す.core.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) # django
return WSGIHandler() # WSGI
このメソッドは、django処理環境をロードした後、WSGIHandlerクラスを返します.次に、このプロセッサのコードを見てみましょう.
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest # WSGIResquest
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) # WSGIRequest , request
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) # get_response request
response._handler_class = self.__class__ # response _handler_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(): # cookies
response_headers.append((str('Set-Cookie'), str(c.output(header='')))) # cookies
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) # wsgi
return response # response
クラスはbaseから継承する.BaseHandlerクラス、load_middleware()とget_response(request)メソッドは、BaseHandlerクラスのメソッドを使用します.次に、BaseHandlerクラスのコードを分析します.
class BaseHandler(object):
def __init__(self):
self._request_middleware = None
self._view_middleware = None
self._template_response_middleware = None
self._response_middleware = None
self._exception_middleware = None
self._middleware_chain = None
def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE (or the deprecated
MIDDLEWARE_CLASSES).
Must be called after the environment is fixed (see __call__ in subclasses).
"""
self._request_middleware = [] # middleware
self._view_middleware = []
self._template_response_middleware = []
self._response_middleware = []
self._exception_middleware = []
if settings.MIDDLEWARE is None: # settings.MIDDLEWARE_CLASSES, settings.MMIDDLEWARE
warnings.warn( #
"Old-style middleware using settings.MIDDLEWARE_CLASSES is "
"deprecated. Update your middleware and use settings.MIDDLEWARE "
"instead.", RemovedInDjango20Warning
)
handler = convert_exception_to_response(self._legacy_get_response)
for middleware_path in settings.MIDDLEWARE_CLASSES:
mw_class = import_string(middleware_path)
try:
mw_instance = mw_class()
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if six.text_type(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue
if hasattr(mw_instance, 'process_request'):
self._request_middleware.append(mw_instance.process_request)
if hasattr(mw_instance, 'process_view'):
self._view_middleware.append(mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.insert(0, mw_instance.process_template_response)
if hasattr(mw_instance, 'process_response'):
self._response_middleware.insert(0, mw_instance.process_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.insert(0, mw_instance.process_exception)
else: #
handler = convert_exception_to_response(self._get_response) # ,self._get_response
for middleware_path in reversed(settings.MIDDLEWARE): #
middleware = import_string(middleware_path) #
try:
mw_instance = middleware(handler) #
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if six.text_type(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue
if mw_instance is None:
raise ImproperlyConfigured(
'Middleware factory %s returned None.' % middleware_path
)
if hasattr(mw_instance, 'process_view'): # ,
self._view_middleware.insert(0, mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.append(mw_instance.process_template_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.append(mw_instance.process_exception)
handler = convert_exception_to_response(mw_instance) # ,
# We only assign to this when initialization is complete as it is used
# as a flag for initialization being complete.
self._middleware_chain = handler #
def make_view_atomic(self, view):
non_atomic_requests = getattr(view, '_non_atomic_requests', set()) # view
for db in connections.all(): #
if db.settings_dict['ATOMIC_REQUESTS'] and db.alias not in non_atomic_requests: # ATOMIC_REQUESTS TRUE,
view = transaction.atomic(using=db.alias)(view) # view
return view
def get_exception_response(self, request, resolver, status_code, exception):
return get_exception_response(request, resolver, status_code, exception, self.__class__) #
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)
response = self._middleware_chain(request) # handler __call__ ,
# 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: # process_response ,
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 # urlconf, urlconf, , django
set_urlconf(urlconf)
resolver = get_resolver(urlconf) # django.urls.resolver.py RegexURLResolver()
else:
resolver = get_resolver()
resolver_match = resolver.resolve(request.path_info) # py RegexURLResolver.resolve() ,
callback, callback_args, callback_kwargs = resolver_match # ,
request.resolver_match = resolver_match
# Apply view middleware
for middleware_method in self._view_middleware: # ‘process_view’
response = middleware_method(request, callback, callback_args, callback_kwargs) #
if response:
break
if response is None:
wrapped_callback = self.make_view_atomic(callback) #
try:
response = wrapped_callback(request, *callback_args, **callback_kwargs) # request ,
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 process_exception_by_middleware(self, exception, request):
"""
Pass the exception to the exception middleware. If no middleware
return a response for this exception, raise it.
"""
for middleware_method in self._exception_middleware:
response = middleware_method(request, exception)
if response:
return response
raise
def handle_uncaught_exception(self, request, resolver, exc_info):
"""Allow subclasses to override uncaught exception handling."""
return handle_uncaught_exception(request, resolver, exc_info)
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: # ‘process_request’
response = middleware_method(request)
if response:
break
if response is None:
response = self._get_response(request) # _get_response()
return response
そのうち_get_responseメソッドはリクエストを処理し、応答を得る最も主要なメソッドであり、以下、このメソッドのget_について説明する.resolver()メソッドで、主に呼び出しが実際にdjangoを初期化する.urls.resolver.pyファイルの下のRegexURLResolver()クラスのresolve()メソッドを解析します.次にresolve()メソッドを解析します.
def resolve(self, path):
path = force_text(path) # path may be a reverse_lazy object #
tried = [] # ( )
match = self.regex.search(path) # LocaleRegexProvider.regex , RegexURLResolver , '^/'
if match:
new_path = path[match.end(): # , path
for pattern in self.url_patterns: # url_pattern
try:
sub_match = pattern.resolve(new_path) # pattern > ,pattern.resolve RegexURLResolver.resolve
except Resolver404 as e: # , django.urls.resolver.ResolverMatch
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:
# Merge captured arguments in match with submatch
sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
sub_match_dict.update(sub_match.kwargs) # kwargs
# If there are *any* named groups, ignore all non-named groups.
# Otherwise, pass all non-named arguments as positional arguments.
sub_match_args = sub_match.args
if not sub_match_dict:
sub_match_args = match.groups() + sub_match.args
return ResolverMatch( # ResolverMath
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})
ここではurlが処理関数とurlのパラメータを取得する方法を簡略化し,urlで構成されたviewビュー関数を解析し続け,resrfulスタイルの例を挙げる
from django.conf.urls import url #
urlpatterns = [
url(r'^detial/', view.DetailView.as_view(), name='detail'),
]
class DetailView(Apiview):
return Response({"detail":" "})
url関数の機能とas_について説明します.view()関数を解析する
def url(regex, view, kwargs=None, name=None): # url , regex r'^detial/', view view.DetailView.as_view(), name ‘detail’
if isinstance(view, (list, tuple)): # view ,
# For include(...) processing.
urlconf_module, app_name, namespace = view
return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
elif callable(view): # view
return RegexURLPattern(regex, view, kwargs, name) # , RegexURLPattern
else:
raise TypeError('view must be a callable or a list/tuple in the case of include().')
url関数は主にurlが一致し、処理関数とnameがRegexURLPatternクラスに渡され、初期化され、そのクラスを通じてurlの一致を要求する作業を行う.
次はas_を分析しますview()メソッド、すべての処理関数がview関数から継承され、as_が呼び出されます.view()メソッドを呼び出し、その後、パケット関数view()を呼び出し、最後に処理要求のdispath関数処理要求を呼び出し、戻り値を得る
class View(object):
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] #
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in six.iteritems(kwargs): # ,
setattr(self, key, value)
@classonlymethod
def as_view(cls, **initkwargs):
"""
Main entry point for a request-response process.
"""
for key in initkwargs:
if key in cls.http_method_names: #
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key): #
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'): # ‘get’ ‘head’, head get
self.head = self.get
self.request = request #
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs) #
view.view_class = cls # view
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=()) # view , cls view
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=()) #
return view
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names: # ,
handler = getattr(self, request.method.lower(), self.http_method_not_allowed) # view , ,
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs) # handler request
def http_method_not_allowed(self, request, *args, **kwargs): # ,
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return http.HttpResponseNotAllowed(self._allowed_methods())
def options(self, request, *args, **kwargs):
"""
Handles responding to requests for the OPTIONS HTTP verb.
"""
response = http.HttpResponse()
response['Allow'] = ', '.join(self._allowed_methods())
response['Content-Length'] = '0'
return response
def _allowed_methods(self): #
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
この章では、urlリクエストが一致し、wsgiでリクエストを処理して応答値を返す方法について説明します.