初探中间件(middleware)
初探中间件(middleware)
因为考虑到文章的长度, 所以 BaseHandler 的展开被推迟了. 在 BaseHandler 中隐藏着中间件的信息, 较常见的 SessionMiddleware 就已经默认安装. BaseHandler 的展开主要是以代码为主, 但已经加入了注释; 文章的最后附一张美图 .
最后, 祝程序员们节日快乐, 别太宅了 ;)
BaseHandler 详解
BaseHandler 在 django.core.handlers.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
|
# 好经典的 handlerclass BaseHandler(object): # Changes that are always applied to a response (in this order). response_fixes = [ http.fix_location_header, http.conditional_content_removal, http.fix_IE_for_attach, http.fix_IE_for_vary, ] 初始化函数, 初始化请求中间件, 视图中间件, 模版中间件, 响应中间件和异常中间件. def __init__(self): self._request_middleware = self._view_middleware = self._template_response_middleware = self._response_middleware = self._exception_middleware = None 视图, 模版相应, 相应, 异常中间件, 请求中间件 根据 mysite.settings.py 中的 `MIDDLEWARE_CLASSES` 添加所有的中间件. def load_middleware(self): """ Populate middleware lists from settings.MIDDLEWARE_CLASSES. 从 settings 中加载各种中间件 Must be called after the environment is fixed (see __call__ in subclasses). """ # 初始化四种中间件 self._view_middleware = [] self._template_response_middleware = [] self._response_middleware = [] self._exception_middleware = [] # 临时的请求中间件, 因为在加入中间件的过程中, 可能会出现异常, 而出现异常都导致加载中间件的不成功, 因此将 self._request_middleware 的赋值放在最后, 表示已经成功. request_middleware = [] # settings.MIDDLEWARE_CLASSES 设置项指定需要预装的中间件 for middleware_path in settings.MIDDLEWARE_CLASSES: try: mw_module, mw_classname = middleware_path.rsplit('.', 1) except ValueError: raise exceptions.ImproperlyConfigured('%s isn\'t a middleware module' % middleware_path) try: 尝试导入中间件所在模块. mod = import_module(mw_module) except ImportError as e: raise exceptions.ImproperlyConfigured('Error importing middleware %s: "%s"' % (mw_module, e)) try: 尝试得到某种中间件类 mw_class = getattr(mod, mw_classname) except AttributeError: raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname)) try: 尝试实例化 mw_instance = mw_class() except exceptions.MiddlewareNotUsed: continue 和 urllib 的处理方法类似: 请求预处理, 视图处理?, 模版处理, 相应处理, 错误处理(详见我的 urllib 源码剖析) if hasattr(mw_instance, 'process_request'): # 这里 request_middleware 用的是 append(), 这里是有讲究的: # django 规定, 多个请求中间件调用的次序是其出现的次序, 下同 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'): # 这里 _template_response_middleware 用的是 insert() 头插法, 这里是有讲究的: # django 规定, 多个模版相应中间件调用的次序是其出现次序的逆序, 下同 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) # We only assign to this when initialization is complete as it is used # as a flag for initialization being complete. # 结束的标识, 表明中间件加载成功 self._request_middleware = request_middleware # 处理请求的函数, 并返回 response def get_response(self, request): "Returns an HttpResponse object for the given HttpRequest" 根据请求, 得到响应 try: 为该线程提供默认的 url 处理器 # Setup default url resolver for this thread, this code is outside # the try/except so we don't get a spurious "unbound local # variable" exception in the event an exception is raised before # resolver is set #ROOT_URLCONF = 'mysite.urls' urlconf = settings.ROOT_URLCONF # set_urlconf() 会设置 url 配置即 settings.ROOT_URLCONF urlresolvers.set_urlconf(urlconf) # 实例化 RegexURLResolver, 暂且将其理解为一个 url 的匹配处理器, 下节展开 resolver = urlresolvers.RegexURLResolver(r'^/', urlconf) try: response = None # Apply request middleware 调用请求中间件 for middleware_method in self._request_middleware: response = middleware_method(request) # 如果此 response 有效, 即不走下面的逻辑 if response: break # 如果没有结果 if response is None: # 尝试 request 中是否有 urlconf, 一般没有, 可以忽略此段代码!!! if hasattr(request, 'urlconf'): # Reset url resolver with a custom urlconf. 自定义的 urlconf urlconf = request.urlconf urlresolvers.set_urlconf(urlconf) resolver = urlresolvers.RegexURLResolver(r'^/', urlconf) # 调用 RegexURLResolver.resolve(), 可以理解为启动匹配的函数; 返回 ResolverMatch 实例 resolver_match = resolver.resolve(request.path_info) # resolver_match 对象中存储了有用的信息, 譬如 callback 就是我们在 views.py 中定义的函数. callback, callback_args, callback_kwargs = resolver_match # 将返回的 resolver_match 挂钩到 request request.resolver_match = resolver_match # Apply view middleware 调用视图中间件 for middleware_method in self._view_middleware: response = middleware_method(request, callback, callback_args, callback_kwargs) # 如果此 response 有效, 即不走下面的逻辑 if response: break # response 还是为空 if response is None: try: # 这里调用的是真正的处理函数, 我们一般在 view.py 中定义这些函数 response = callback(request, *callback_args, **callback_kwargs) except Exception as e: # If the view raised an exception, run it through exception # middleware, and if the exception middleware returns a # response, use that. Otherwise, reraise the exception. # 出现异常, 调用异常中间件 for middleware_method in self._exception_middleware: response = middleware_method(request, e) # 如果此 response 有效, 即不走下面的逻辑 if response: break if response is None: raise # response 还是为空, 可能就要异常了 # 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." % (callback.__module__, view_name)) # If the response supports deferred rendering, apply template # response middleware and the render the response 如果 response 实现了 render, 那么渲染返回. if hasattr(response, 'render') and callable(response.render): for middleware_method in self._template_response_middleware: response = middleware_method(request, response) response = response.render() except http.Http404 as e: logger.warning('Not Found: %s', request.path, extra={ 'status_code': 404, 'request': request }) # 如果是调试下, 直接要返回 404 页面 if settings.DEBUG: response = debug.technical_404_response(request, e) else: try: # 非调试模式下, 获取 url 处理器的默认 404 处理 callback, param_dict = resolver.resolve404() response = callback(request, **param_dict) except: signals.got_request_exception.send(sender=self.__class__, request=request) response = self.handle_uncaught_exception(request, resolver, sys.exc_info()) # 访问拒绝 except exceptions.PermissionDenied: logger.warning( 'Forbidden (Permission denied): %s', request.path, extra={ 'status_code': 403, 'request': request }) try: callback, param_dict = resolver.resolve403() response = callback(request, **param_dict) except: signals.got_request_exception.send( sender=self.__class__, request=request) response = self.handle_uncaught_exception(request, resolver, sys.exc_info()) except SystemExit: # Allow sys.exit() to actually exit. See tickets #1023 and #4701 raise except: # Handle everything else, including SuspiciousOperation, etc. # Get the exception info now, in case another exception is thrown later. signals.got_request_exception.send(sender=self.__class__, request=request) response = self.handle_uncaught_exception(request, resolver, sys.exc_info()) finally: # Reset URLconf for this thread on the way out for complete # isolation of request.urlconf 重置, 因为前面有两种 url resolver 的可能, 拒绝混淆 urlresolvers.set_urlconf(None) try: # Apply response middleware, regardless of the response 调用响应中间件 for middleware_method in self._response_middleware: response = middleware_method(request, response) response = self.apply_response_fixes(request, response) except: # Any exception should be gathered and handled signals.got_request_exception.send(sender=self.__class__, request=request) response = self.handle_uncaught_exception(request, resolver, sys.exc_info()) return response def handle_uncaught_exception(self, request, resolver, exc_info): """ 处理未能捕捉的错误 Processing for any otherwise uncaught exceptions (those that will generate HTTP 500 responses). Can be overridden by subclasses who want customised 500 handling. 子类中可以重写 500 状态的处理 Be *very* careful when overriding this because the error could be caused by anything, so assuming something like the database is always available would be an error. """ if settings.DEBUG_PROPAGATE_EXCEPTIONS: raise logger.error('Internal Server Error: %s', request.path, exc_info=exc_info, extra={ 'status_code': 500, 'request': request } ) 调试模式特殊处理 if settings.DEBUG: return debug.technical_500_response(request, *exc_info) # If Http500 handler is not installed, re-raise last exception 如果http500 处理器都没有安装, 可能会崩溃 if resolver.urlconf_module is None: six.reraise(*exc_info) # Return an HttpResponse that displays a friendly error message. #这是自定义的 500 处理器 callback, param_dict = resolver.resolve500() return callback(request, **param_dict) def apply_response_fixes(self, request, response): """ Applies each of the functions in self.response_fixes to the request and response, modifying the response in the process. Returns the new response. """ for func in self.response_fixes: response = func(request, response) return response |
故此总结
load_middleware() 函数会根据 mysite.settings.py 中的 MIDDLEWARE_CLASSES 导入所有的中间件. 在 eclipse + pydev 创建 django 的默认设置当中就有默认的中间件:
|
1
2
3
4
5
6
7
8
9
|
MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware',) |
每一个中间件都是一个类, 其内部会实现 process_request(),process_view(),process_template_response(),process_response() 或者 process_exception() 方法. 不一定都实现, 看需求. 而这些方法如果存在, 都会被保存响应的函数列表中, 待将来调用.
get_response() 方法, 中间件调用执行的顺序是请求中间件, 视图中间件, 模版中间件, 异常中间件(可选), 响应中间件. 习惯上, 我把这些简称为请求预处理和响应善后处理.get_response() 返回了 response, 但一长串的 url 是如何匹配的, 并且自己在 views.py 中的函数是在什么时候调用的?
我已经在 github 备份了 Django 源码的注释: Decode-Django, 有兴趣的童鞋 fork 吧.
图解中间件:

捣乱 2013-9-14
初探中间件(middleware)的更多相关文章
- Django 源码小剖: 初探中间件(middleware)
因为考虑到文章的长度, 所以 BaseHandler 的展开被推迟了. 在 BaseHandler 中隐藏着中间件的信息, 较常见的 SessionMiddleware 就已经默认安装. BaseH ...
- ASP.NET Core 开发-中间件(Middleware)
ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...
- ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析
ASP.NET Core中间件(Middleware)进阶学习实现SOAP 解析. 本篇将介绍实现ASP.NET Core SOAP服务端解析,而不是ASP.NET Core整个WCF host. 因 ...
- laravel中间件-----------middleware
middleware中间件 是访问到达服务器后在被对应的路由处理之前所经过的一层过滤层,故称中间件. 中间件是存放在app\http\middleware中,需要定一个 handle 处理方法,在ha ...
- 二、中间件(middleware)
1. 中间件(middleware) Django中的中间件主要实现一些附加功能,在request被用户handler处理前,以及用户handler处理后生存的response进行处理.因此 ...
- 中间件(Middleware)
中间件(Middleware) ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下 ...
- django中间件Middleware
熟悉web开发的同学对hook钩子肯定不陌生,通过钩子可以方便的实现一些触发和回调,并且做一些过滤和拦截. django中的中间件(middleware)就是类似钩子的一种存在.下面我们来介绍一下,并 ...
- 如何传递参数给ASP.NET Core的中间件(Middleware)
问题描述 当我们在ASP.NET Core中定义和使用中间件(Middleware)的时候,有什么好的办法可以给中间件传参数吗? 解决方案 在ASP.NET Core项目中添加一个POCO类来传递参数 ...
- ASP.NET Core -中间件(Middleware)使用
ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...
随机推荐
- 在LINQ中实现多条件联合主键LEFT JOIN
我昨天遇到一个LINQ下使用多条件比对产生LEFT JOIN的问题,经过深入研究,终于解决了,也让我学到了新的东西,特地拿来分享. 实例:有一张库存异常变更视图KCYD,仓库ID[Ckid]和物品ID ...
- oracle_利用闪回功能恢复数据
方便起见一般:执行如下即可不用往下看: ① 启用行移动功能 alter table tbl_a enable row movement; ② 闪回表数据到某个时间点 flashback table t ...
- Linux-常用命令1---对文件进行查看、复制、移动和分割
基于Linux的操作系统是一种自由和开放源代码的类UNIX操作系统. Linux的几大特点决定了它的不可代替和无法超越性: (1)免费的/开源的:(2)支持多线程/多用户: (3)安全性好; (4)对 ...
- c# 自定义数据类型
定义引用类型用 class ,值类型 用 struct ,涉及数据转换就用 上一篇的方法做 ,涉及 泛型就用 in 关键字 不用 in interface IContravariant<A& ...
- 【Eclipse提高开发速度-插件篇】Eclipse插件安装慢得几个原因
1.改动"Available Softeware Site" ,降低关联,详细做法 Install New Software >> Available Softewar ...
- 2014阿里实习生面试题——mysql如何实现的索引
这是2014北京站的两副面孔阿里实习生问题扯在一起: 在MySQL中.索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的,比方MyISAM和InnoDB存储引擎. MyISAM索引实现: ...
- post与get,这两人到底神马区别??
甲曰:“1. GET使用URL或Cookie传参.而POST将数据放在BODY中. 2. GET的URL会有长度上的限制,则POST的数据则可以非常大. 3. POST比GET安全,因为数据在地址栏上 ...
- C语言库函数大全及应用实例六
原文:C语言库函数大全及应用实例六 [编程资料]C语言库函数大全及应用实例六 函数名: getlinesett ...
- Visual Studio 2013进行单元测试
使用Visual Studio 2013进行单元测试--初级篇 1.打开VS2013 --> 新建一个项目.这里我们默认创建一个控制台项目.取名为UnitTestDemo 2.在解决方案里面 ...
- String 的intern() 方法说明
1.说明 Java中string.intern()方法调用会先去字符串常量池中查找相应的字符串,如果字符串不存在,就会在字符串常量池中创建该字符串然后再返回. 2.源码说明 public native ...