Django WSGI Application

Django WSGI Application

WSGI Handler

Django 自带的 WSGIHandler 实际上在 wsgi 规范中是作为一个 WSGI application ,它是一个定义了 __call__ 的类。

涉及的几个关键性文件

  • django/core/handler/base.py
  • django/core/handler/execption.py
  • django/core/handler/wsgi.py

我们首先关注 wsgi.py 里定义的类

wsgi.py

LimitedStream 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 截取部分代码

class LimitedStream:
"""Wrap another stream to disallow reading it past a number of bytes."""
def __init__(self, stream, limit, buf_size=64 * 1024 * 1024):
self.stream = stream
self.remaining = limit
self.buffer = b''
self.buf_size = buf_size

def _read_limited(self, size=None):
if size is None or size > self.remaining:
size = self.remaining
if size == 0:
return b''
result = self.stream.read(size)
self.remaining -= len(result)
return result

def read(self, size=None):
if size is None:
result = self.buffer + self._read_limited()
self.buffer = b''
elif size < len(self.buffer):
result = self.buffer[:size]
self.buffer = self.buffer[size:]
else: # size >= len(self.buffer)
result = self.buffer + self._read_limited(size - len(self.buffer))
self.buffer = b''
return result
  • 一个包装了一个流的类,它的函数 read 当 size 的大小未指定的时候只读取限定大小的流,当给出 size 大小时先从流中读取,如果流中的数据不够再使用 read_limited 读取指定大小的流,同时 buffer 永远保存着没有读取的流。

WSGIHandler 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 = [
*response.items(),
*(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
]
# server 提供的回调方法,将响应的 header 和 status 返回给 server
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

  • list = [*dict.items()] 这种写法,list 最后会是一个包含元组的列表,每个元组为字典中的 key, value 的组合
  • 我们通过前面的学习知道了请求被交给这个类作为 WSGI 中的 Application 来处理请求最终也通过它来返回请求(start_response),返回响应的正文
  • 我们发现__init__() 函数除了调用父类的初始化,又调用了 load_middleware() 这个函数在 BaseHandler 中定义,猜测一下是加载 Django 的中间件在处理请求的时候使用
  • Set-Cookis 这个头属性在这个类中被加入返回头里
  • 处理请求调用 get_response() 方法,该方法的的主要逻辑是通过 urlconf 找到对应的 view,按顺序执行 middleware 和 view 函数,具体在 View 模块详细的研究

WSGIRequset 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class WSGIRequest(HttpRequest):
def __init__(self, environ):
script_name = get_script_name(environ)
path_info = get_path_info(environ) or '/'
self.environ = environ
self.path_info = path_info
self.path = '%s/%s' % (script_name.rstrip('/'),
path_info.replace('/', '', 1))
self.META = environ
self.META['PATH_INFO'] = path_info
self.META['SCRIPT_NAME'] = script_name
self.method = environ['REQUEST_METHOD'].upper()
self.content_type, self.content_params = cgi.parse_header(environ.get('CONTENT_TYPE', ''))
if 'charset' in self.content_params:
try:
codecs.lookup(self.content_params['charset'])
except LookupError:
pass
else:
self.encoding = self.content_params['charset']
try:
content_length = int(environ.get('CONTENT_LENGTH'))
except (ValueError, TypeError):
content_length = 0
self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
self._read_started = False
self.resolver_match = None

def _get_scheme(self):
return self.environ.get('wsgi.url_scheme')

@cached_property
def GET(self):
# The WSGI spec says 'QUERY_STRING' may be absent.
raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '')
return QueryDict(raw_query_string, encoding=self._encoding)

def _get_post(self):
if not hasattr(self, '_post'):
self._load_post_and_files()
return self._post

def _set_post(self, post):
self._post = post

@cached_property
def COOKIES(self):
raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
return parse_cookie(raw_cookie)

@property
def FILES(self):
if not hasattr(self, '_files'):
self._load_post_and_files()
return self._files

POST = property(_get_post, _set_post)

# views/generic/base.py

from django.core.handlers.wsgi import WSGIRequest
class View(object):
def __init__(self, *args, **kwargs):
self.request = WSGIRequest() if False else None
self.args = list()
self.kwargs = dict()
  • wsgi.url_scheme:http 或者 https (URL 方案嘛), wsgi.input:一个类文件的输入流,application 可以通过这个获取 HTTP 请求的 body
  • 看见GET, POST, FILES, Cookies 我们可以大胆的猜测,我们的 view 函数中传入的 request 就是这个类的实例,具体是不是这样我们需要详细的阅读 view 部分。
  • @cached_property 是一个缓存装饰器, @property 我们应该很熟悉,当一个函数被这个装饰器装饰的时候,我们可以把这个函数返回值当做属性来获取,而 @cached_property 就和名字一样,加了缓存,当被调用第一次会进行计算,计算完之后把实例的 __dict__[‘xxx’] 设置为计算后的值。下次读值的时候会直接从其中读取,避免了多次计算。
  • 具体的部分我看不懂 =_= 但我们知道了 self.method = environ[‘REQUEST_METHOD’] 中保存的是请求的方法,这样我们就可以使用中间件来扩充 http 请求的类型,比如 DELETE 类型
  • get,post 应均返回一个 QueryDict 类型,它被定义在 django.http.QueryDict
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 自定义中间件

from django.http import QueryDict
from django.utils.deprecation import MiddlewareMixin

class HttpOtherMethodMiddleware(MiddlewareMixin):

def process_request(self, request):
try:
request_method = request.META['REQUEST_METHOD']
if requset_method.upper() not in ('GET', 'POST'):
setattr(request, requset_method.upper(), QueryDict(request.body))
except Exception:
pass
finally:
return

base.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

# 省略部分代码

class BaseHandler:
_view_middleware = None
_template_response_middleware = None
_exception_middleware = None
_middleware_chain = None

def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE.

Must be called after the environment is fixed (see __call__ in subclasses).
"""
self._view_middleware = []
self._template_response_middleware = []
self._exception_middleware = []
# 将函数发生的异常转变为 response 返回,避免无法正常返回
handler = convert_exception_to_response(self._get_response)
for middleware_path in reversed(settings.MIDDLEWARE):
# 又看见了 import_string 这个函数,用来导入模块
middleware = import_string(middleware_path)
try:
mw_instance = middleware(handler)
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if str(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())
for db in connections.all():
if db.settings_dict['ATOMIC_REQUESTS'] and db.alias not in non_atomic_requests:
view = transaction.atomic(using=db.alias)(view)
return view

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)
response._closable_objects.append(request)
if response.status_code >= 400:
log_response(
'%s: %s', response.reason_phrase, request.path,
response=response,
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()
resolver_match = resolver.resolve(request.path_info)
callback, callback_args, callback_kwargs = resolver_match
request.resolver_match = resolver_match
# Apply view middleware
for middleware_method in self._view_middleware:
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)
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
  • 我的理解是 middleware 分为3类,view_middleware(视图中间件),template_response_middleware(模板中间件)
    ,exception_middleware(异常中间件),在我们的 view 函数处理前,先通过视图中间件,然后当响应返回渲染的 html,这时会通过模板中间件进行进一步处理,当发生异常时,会通过异常中间件一步一步处理响应(process_exception_by_middleware 函数)
    // TODO: 这里的中间件和我们 Django setting 中的中间件有什么区别?
Author

Ctwo

Posted on

2020-02-12

Updated on

2020-10-25

Licensed under

Comments