本文翻译自 James Bennett 的 How Django processes a request ,对于学习 Django 的朋友,我想能有所助益。翻译有不妥当的地方,请留言告诉我。
以下是译文。
在 Jonathan Snook 昨天的评论中,他提出了一个很棒的挑战 :说说 Django 是如何处理一个 request 的,从开始到结束,对于内部调用的各种东西要有足够的细节,并且要链接到恰当的文档。
Simon Willison 曾经写过这样的文档 ,但它是从一个很高层的角度而且从那以来很多东西都有变化,因此我决定自己写一篇,希望它易于理解。
注意:这是第一份草稿,不是完成的产品,也不是完整的列表。随着工作的进行,它会经常改变。理想情况下,我会得到一些帮助来产生一个某种程度上的可视化的文档,但现在我坚持用纯文本。
有 官方文档的条目,我会为它做链接,没有的,我会链接到 Django 仓库中相关代码的位置──这些位置经常改变,特别是因为我总是链接到各自文件的行号,但我会尽力保持它们是最新的。如果你看到错误的地方,或者我遗漏的东 西,或者应该解释的更好的地方,请留言让我知道。
更新 2006.10.2:我刚刚回顾了这篇文章并作了一些修改。更新了好几个代码引用;重写了一些最近发生的关于内部处理的含糊不清的地方。
更新 2006.11.28:增加了关于数据库何时关闭的注释。
更新 2006.12.20:链接到 Django wiki 上关于调度者的注释。
我们开始吧。
Request 来了!
首先发生的是一些和 Django 有关的其他事情,分别是:
这两个类都继承自 django.core.handlers.base.BaseHandler ,它包含对任何类型的 request 来说都需要的公共代码。
有一个处理器了
当上面其中一个处理器实例化后,紧接着发生了一系列的事情:
最后一条有点复杂,我们仔细瞧瞧。
一 个 middleware 类可以渗入处理过程的四个阶段:request,view,response 和 exception。要做到这一点,只需要定义指定的、恰当的方法:process_request,process_view, process_response 和 process_exception。middleware 可以定义其中任何一个或所有这些方法,这取决于你想要它提供什么样的功能。
当处理器内省 middleware 时,它查找上述名字的方法,并建立四个列表作为处理器的实例变量:
绿灯:现在开始
现在处理器已经准备好真正开始处理了,因此它给调度程序发送一个信号 request_started(Django 内部的调度程序允许各种不同的组件声明它们正在干什么,并可以写一些代码监听特定的事件。关于这一点目前还没有官方的文档,但在 wiki 上有一些注释 。)。接下来它实例化一个 django.http.HttpRequest 的子类。根据不同的处理器,可能是 django.core.handlers.modpython.ModPythonRequest 的一个实例,也可能是 django.core.handlers.wsgi.WSGIRequest 的一个实例。需要两个不同的类是因为 mod_python 和 WSGI API s 以不同的格式传入 request 信息,这个信息需要解析为 Django 能够处理的一个单独的标准格式。
一旦一个 HttpRequest 或者类似的东西存在了,处理器就呼叫它自己的 get_response 方法,传入这个 HttpRequest 作为唯一的参数。这里就是几乎所有真正的活动发生的地方。
Middleware,第一回合
get_response 做的第一件事就是遍历处理器的 _request_middleware 实例变量并呼叫其中的每一个方法,传入 HttpRequest 的实例作为参数。这些方法可以选择短路剩下的处理并立即让 get_response 返回,通过返回自身的一个值(如果它们这样做,返回值必须是 django.http.HttpResponse 的一个实例,后面会讨论到 )。如果其中之一这样做了,我们会立即回到主处理器代码,get_response 不会等着看其它 middleware 类想要做什么,它直接返回,然后处理器进入 response 阶段。
然而,更一般的情况是,这里应用的 middleware 方法简单地做一些处理并决定是否增加,删除或补充 request 的属性。
关于解析
假设没有一个作用于 request 的 middleware 直接返回 response,处理器下一步会尝试解析请求的 URL 。它在配置文件中寻找一个叫做 ROOT_URLCONF 的配置,用这个配置加上根 URL / ,作为参数来创建 django.core.urlresolvers.RegexURLResolver 的一个实例,然后呼叫它的 resolve 方法来解析请求的 URL 路径。
URL resolver 遵循一个相当简单的模式。对于在 URL 配置文件中根据 ROOT_URLCONF 的配置产生的每一个在 urlpatterns 列表中的条目,它会检查请求的 URL 路径是否与这个条目的正则表达式相匹配,如果是的话,有两种选择:
注意这一过程会在匹配到第一个指定了 view 的条目时停止,因此最好让你的 URL 配置从复杂的正则过渡到简单的正则,这样能确保 resolver 不会首先匹配到简单的那一个而返回错误的 view function。
如果没有找到匹配的条目,resolver 会产生 django.core.urlresolvers.Resolver404 例外,它是 django.http.Http404 例外的子类。后面我们会知道它是如何处理的。
Middleware,第二回合
一旦知道了所需的 view function 和相关的参数,处理器就会查看它的 _view_middleware 列表,并呼叫其中的方法,传入 HttpRequst,view function,针对这个 view 的位置参数列表和关键字参数字典。
还有,middleware 有可能介入这一阶段并强迫处理器立即返回。
进入 View
如果处理过程这时候还在继续的话,处理器会呼叫 view function。Django 中的 Views 不很严格因为它只需要满足几个条件:
除了这些,你就可以天马行空了。尽管如此,一般来说,views 会使用 Django 的 database API 来创建,检索,更新和删除数据库的某些东西,还会加载并渲染一个模板来呈现一些东西给最终用户。
模板
Django 的模板系统有两个部分:一部分是给设计师 使用的混入少量其它东西的 HTML ,另一部分是给程序员 使用纯 Python。
从一个 HTML 作者的角度,Django 的模板系统非常简单,需要知道的仅有三个结构:
变量引用以一种非常简单的方式工作。如果你只是要打印变量,只要 {{ foo }},模板系统就会输出它。这里唯一的复杂情况是 {{ foo.bar }},这时模板系统按顺序尝试几件事:
如果所有这些都失败了,模板系统输出配置 TEMPLATE_STRING_IF_INVALID 的值,默认是空字符串。
模 板过滤就是简单的 Python functions,它接受一个值和一个参数,返回一个新的值。比如,date 过滤用一个 Python datetime 对象作为它的值,一个标准的 strftime 格式化字符串作为它的参数,返回对 datetime 对象应用了格式化字符串之后的结果。
模板标签用在事情有一点点复杂的地方,它是你了解 Django 的模板系统是如何真正工作的地方。
Django 模板的结构
在内部,一个 Django 模板体现为一个 “nodes” 集合,它们都是从基本的 django.template.Node 类继承而来。Nodes 可以做各种处理,但有一个共同点:每一个 Node 必须有一个叫做 render 的方法,它接受的第二个参数(第一个参数,显然是 Node 实例)是 django.template.Context 的一个实例,这是一个类似于字典的对象,包含所有模板可以获得的变量。Node 的 render 方法必须返回一个字符串,但如果 Node 的工作不是输出(比如,它是要通过增加,删除或修改传入的 Context 实例变量中的变量来修改模板上下文),可以返回空字符串。
Django 包含许多 Node 的子类来提供有用的功能。比如,每个内置的模板标签 都被一个 Node 的子类处理(比如,IfNode 实现了 if 标签,ForNode 实现了 for 标签,等等)。所有内置标签可以在 django.template.defaulttags 找到。实际上,上面介绍的所有模板结构都是某种形式的 Nodes,纯文本也不例外。变量查找由 VariableNode 处理,出于自然,过滤也应用在 VariableNode 上,标签是各种类型的 Nodes,纯文本是一个 TextNode。
一般来说,一个 view 渲染一个模板要经过下面的步骤,依次是:
Template 的 render 方法的返回值是一个字符串,它由 Template 中所有 Nodes 的 render 方法返回的值连接而成,呼叫顺序为它们出现在 Template 中的顺序。
关于 Response,一点点
一旦一个模板完成渲染,或者产生了其它某些合适的输出,view 就会负责产生一个 django.http.HttpResponse 实例,它的构建器接受两个可选的参数:
Middleware,第三回合:例外
如果 view 函数,或者其中的什么东西,发生了例外,那么 get_response(我知道我们已经花了些时间深入 views 和 templates,但是一旦 view 返回或产生例外,我们仍将重拾处理器中间的 get_response 方法)将遍历它的 _exception_middleware 实例变量并呼叫那里的每个方法,传入 HttpResponse 和这个 exception 作为参数。如果顺利,这些方法中的一个会实例化一个 HttpResponse 并返回它。
仍然没有响应?
这时候有可能还是没有得到一个 HttpResponse,这可能有几个原因:
这时候,get_response 会回到自己的例外处理机制中,它们有几个层次:
此 外,对于除了 django.http.Http404 或 Python 内置的 SystemExit 之外的任何例外,处理器会给调度者发送信号 got_request_exception,在返回之前,构建一个关于例外的描述,把它发送给列在 Django 配置文件的 ADMINS 配置中的每一个人。
Middleware,最后回合
现在,无论 get_response 在哪一个层次上发生错误,它都会返回一个 HttpResponse 实例,因此我们回到处理器的主要部分。一旦它获得一个 HttpResponse 它做的第一件事就是遍历它的 _response_middleware 实例变量并应用那里的方法,传入 HttpRequest 和 HttpResponse 作为参数。
注意对于任何想改变点什么的 middleware 来说,这是它们的最后机会。
The check is in the mail
是该结束的时候了。一旦 middleware 完成了最后环节,处理器将给调度者发送信号 request_finished,对与想在当前的 request 中执行的任何东西来说,这绝对是最后的呼叫。监听这个信号的处理者会清空并释放任何使用中的资源。比如,Django 的 request_finished 监听者会关闭所有数据库连接。
这件事发生以后,处理器会构建一个合适的返回值送返给实例化它的任何东西(现在,是一个恰当的 mod_python response 或者一个 WSGI 兼容的 response,这取决于处理器)并返回。
呼呼
结束了,从开始到结束,这就是 Django 如何处理一个 request。