初探中间件(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
# 好经典的 handler
class 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

http://daoluan.net

 
 
 
标签: 源码剖析Django

初探中间件(middleware)的更多相关文章

  1. Django 源码小剖: 初探中间件(middleware)

    因为考虑到文章的长度, 所以 BaseHandler 的展开被推迟了. 在 BaseHandler 中隐藏着中间件的信息, 较常见的 SessionMiddleware 就已经默认安装.  BaseH ...

  2. ASP.NET Core 开发-中间件(Middleware)

    ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...

  3. ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析

    ASP.NET Core中间件(Middleware)进阶学习实现SOAP 解析. 本篇将介绍实现ASP.NET Core SOAP服务端解析,而不是ASP.NET Core整个WCF host. 因 ...

  4. laravel中间件-----------middleware

    middleware中间件 是访问到达服务器后在被对应的路由处理之前所经过的一层过滤层,故称中间件. 中间件是存放在app\http\middleware中,需要定一个 handle 处理方法,在ha ...

  5. 二、中间件(middleware)

    1.      中间件(middleware) Django中的中间件主要实现一些附加功能,在request被用户handler处理前,以及用户handler处理后生存的response进行处理.因此 ...

  6. 中间件(Middleware)

    中间件(Middleware) ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下 ...

  7. django中间件Middleware

    熟悉web开发的同学对hook钩子肯定不陌生,通过钩子可以方便的实现一些触发和回调,并且做一些过滤和拦截. django中的中间件(middleware)就是类似钩子的一种存在.下面我们来介绍一下,并 ...

  8. 如何传递参数给ASP.NET Core的中间件(Middleware)

    问题描述 当我们在ASP.NET Core中定义和使用中间件(Middleware)的时候,有什么好的办法可以给中间件传参数吗? 解决方案 在ASP.NET Core项目中添加一个POCO类来传递参数 ...

  9. ASP.NET Core -中间件(Middleware)使用

    ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...

随机推荐

  1. Python学习笔记21:数据库操作(sqlite3)

    Python自带一个轻量级的关系型数据库SQLite.这一数据库使用SQL语言. SQLite作为后端数据库,能够搭配Python建站点,或者制作有数据存储需求的工具. SQLite还在其他领域有广泛 ...

  2. 前端学习笔记(zepto或jquery)——对li标签的相关操作(三)

    对li标签的相关操作——八种方式遍历li标签并获取其值 $("ul>li").forEach(function(item,index){ alert(index+" ...

  3. 使用ArcGIS API for Silverlight实现地形坡度在线分析

    原文:使用ArcGIS API for Silverlight实现地形坡度在线分析 苦逼的研究生课程终于在今天结束了,也许从今以后再也不会坐在大学的课堂上正式的听老师讲课了,接下来的时间就得开始找工作 ...

  4. 【转】monkey工具简介

    原文地址:http://www.testwo.com/blog/6188   一.Monkey 简介 Android的SDK 里面,Monkey的tools是一个命令行工具,当连接Android设备时 ...

  5. Cookie基础

    周末百度笔试,答得题都会,就是不仔细不心细,提前一个小时交卷子,想起来就已经晚了.问了一个cookie的问题,我SB的蒙住了,于是乎,似乎是跪掉了,回来后总结了下Cooke的相关问题.###获取coo ...

  6. winhec

    #winhec# 开发人员刷屏看点 (视频) 今天大家已经被winhec刷屏了,本来不想写这篇了,但看了所有的文章,大家关注的都是windows 10的那些新功能,小米win10刷机,联想千元手机,小 ...

  7. Unity3D-RPG项目实战(3):整合Visual Studio 2013开发环境

    古人云:工欲善其事必先利其器,IDE尽管属于一个非常上层的工具,可是一个好的IDE对工作效率提高还是非常大的. 事实上我还是满想用一下官方推荐的Mono,毕竟跨平台如今还是非常重要的一个特性.尝试了这 ...

  8. JavaScript语言基础知识11

    JavaScript字符的比较. 在接下来的学习内容的开始,我们先来看一下alert()此功能,它是一个消息框. OK,接下来正式介绍代码: <HTML> <HEAD> < ...

  9. 一个简单的创建dom的函数

    var  regName = /^(div|a|p|ul|li|input|select|document|body|iframe)$/;function createDom(name, obj) { ...

  10. Java 之关键字 null 使用总结

    1.null的使用 Java中,null是一个关键字,用来标识一个不确定的对象.因此可以将null赋给引用类型变量,但不可以将null赋给基本类型变量.比如我们在定义一个变量的时候我们通过会这样做:X ...