关于遍历的很多麻烦

(或者,你为什么要关心它。)

备注

这一章经过RobMiller的一篇博文的允许而改编。

遍历是 URL dispatch 允许 Pyramid 应用程序将URL映射到代码。

备注

已经熟悉遍历和视图查找概念的前Zope用户可能希望直接跳到 遍历 第章,讨论技术细节。本章主要针对的是以前 Pylons 在另一个不提供遍历的框架中的经验或经验,需要介绍遍历的“为什么”。

一些长期使用桥塔及其基于路径的URL匹配的人第一次被曝光,通过 Pyramid ,以新的思路,如“:term:traversal”和“:term:view lookup”,将传入的HTTP请求路由到可调用代码。一些同样的人认为遍历很难理解。其他人质疑它的实用性;到目前为止,URL匹配已经对他们起到了作用,那么为什么他们还要考虑使用另一种方法,一种不适合他们大脑的方法,而这种方法不能立即提供任何明显的价值呢?

你可以确信,如果你不想理解遍历,你就不必去理解它。你可以快乐地建造 Pyramid 仅带 URL dispatch . 然而,有一些直接的、真实的用例,与模式匹配机制相比,基于遍历的方法更容易提供服务。即使你自己还没有接触到这些用例中的一个,了解这些新的想法对于任何一个Web开发人员来说都是值得的,这样你就知道什么时候需要使用它们了。 Traversal 事实上,这是一个简单的比喻,任何使用过带有文件夹和文件的运行工厂文件系统的人都很容易理解。

URL调度

让我们退后一步,考虑一下我们要解决的问题。特定路径的HTTP请求已路由到我们的Web应用程序。请求的路径可能会调用特定的 view callable 在我们的应用程序中定义的函数。我们正在努力确定 哪一个 对于给定的请求URL,应调用可调用函数(如果有)。

许多系统,包括金字塔,提供了一个简单的解决方案。它们提供了“URL匹配”的概念。URL匹配通过解析URL路径并将结果与一组注册的“模式”(由一组正则表达式或其他URL路径模板语法定义)进行比较来解决此问题。每个模式都映射到某个可调用函数;如果请求路径与特定模式匹配,则调用相关联的函数。如果请求路径与多个模式匹配,则使用一些冲突解决方案,通常是简单的顺序优先级,以便第一个匹配将优先于任何后续匹配。如果请求路径与任何定义的模式都不匹配,则返回“404未找到”响应。

在金字塔中,我们提供一个URL匹配的实现,我们称之为 URL dispatch . 使用 Pyramid 语法,我们可能有匹配模式,例如 /{{userid}}/photos/{{photoid}} ,映射到 photo_view() 函数在代码中的某个地方定义。然后请求一个路径,例如 /joeschmoe/photos/photo1 会是一场比赛, photo_view() 将调用函数来处理请求。同样地, /{{userid}}/blog/{{year}}/{{month}}/{{postid}} 可能映射到 blog_post_view() 函数,所以 /joeschmoe/blog/2010/12/urlmatching 将触发函数,该函数可能知道如何查找和呈现 urlmatching 博客帖子。

历史复习课

现在我们重新理解了 URL dispatch 我们将深入探讨遍历的概念。不过,在我们开始之前,让我们沿着记忆之路走一趟。如果你已经做了一段时间的网络工作,你可能还记得有一段时间我们没有像 PylonsPyramid . 相反,我们有一些通用的HTTP服务器,它们主要从文件系统提供文件。给定站点的“根”映射到文件系统中某个特定文件夹。请求URL路径的每个段表示一个子目录。最后的路径段可以是一个目录或一个文件,一旦服务器找到了正确的文件,它将把它打包成一个HTTP响应并发送回客户机。所以提供一个请求 /joeschmoe/photos/photo1 字面意思是 joeschmoe 文件夹,其中包含 photos 文件夹,其中依次包含 photo1 文件。如果在路上的任何时候,我们发现没有与请求的路径匹配的文件夹或文件,我们将返回404响应。

然而,随着网络的动态发展,增加了一点额外的复杂性。开发了CGI和HTTP服务器模块等技术。文件仍然在文件系统中查找,但如果文件以(例如)结尾, .cgi.php 或者,如果它存在于一个特殊的文件夹中,而不是简单地将文件发送到客户机,那么服务器将读取该文件,使用某种类型的解释器执行它,然后将此进程的输出作为最终结果发送到客户机。服务器配置指定了哪些文件将触发一些动态代码,默认情况是只服务于静态文件。

遍历(又名,资源位置)

信不信由你,如果你了解如何从文件系统服务文件,你了解遍历。如果您了解服务器可能会根据给定请求指定的文件类型执行不同的操作,那么您就了解了视图查找。

文件系统查找和遍历的主要区别在于,文件系统查找是通过文件系统树中的嵌套目录和文件进行的,而遍历是通过 resource tree . 让我们详细看看我们的一个示例路径,这样我们就可以了解我的意思了。

小径 /joeschmoe/photos/photo1, has four segments: /, `` 乔伊斯莫 photos and photo1. With file system lookup we might have a root folder (/) containing a nested folder (`` joeschmoe```),其中包含另一个嵌套文件夹 (photos ,最后包含一个JPG文件 (photo1 )通过遍历,我们有了一个类似于字典的根对象。请求 joeschmoe key给了我们一个类似字典的对象。依次要求 photos key给了我们另一个映射对象,它最终(希望)包含了我们在其值中寻找的资源,由 photo1 关键。

在纯Python术语中,满足 /joeschmoe/photos/photo1 请求将类似于此伪代码:

get_root()['joeschmoe']['photos']['photo1']

get_root() 是返回根遍历的函数 resource . 如果所有指定的键都存在,那么返回的对象将是正在请求的资源,类似于在文件系统示例中检索的JPG文件。如果A KeyError 一路上的任何地方都会产生, Pyramid 将返回404。(这并不完全正确,正如我们在下面了解视图查找时所看到的那样,但基本思想是正确的。)

什么是“资源”?

你可能会说,“我理解的文件系统上的文件。”但是这些嵌套字典的东西是什么?这些“资源”对象在哪里?什么 are 他们?”

自从 Pyramid 不是高度固执己见的框架,它不限制 resource 是实现的;开发人员可以根据需要实现它们。使用的一种常见模式是将数据库中的所有资源(包括根)作为图形持久化。根对象是类似字典的对象。python中类似字典的对象提供 __getitem__ 完成键查找时调用的方法。在引擎盖下面,当 adict 是一个类似字典的对象,python翻译 adict['a']adict.__getitem__('a') . 如果您不相信我们,请尝试在python解释器提示中执行此操作:

>>> adict = {}
>>> adict['a'] = 1
>>> adict['a']
1
>>> adict.__getitem__('a')
1

类似于字典的根对象将其所有子资源的ID存储为键,并提供 __getitem__ 获取它们的实现。所以 get_root() 获取唯一的根对象,而 get_root()['joeschmoe'] 返回另一个对象,该对象也存储在数据库中,而数据库又有自己的子资源和 __getitem__ 实现等等。这些资源可能被保存在一个关系数据库中,这是当今或其他任何地方流行的众多“nosql”解决方案之一;这无关紧要。只要返回的对象提供类似于字典的API(即只要它们有一个适当的实现 __getitem__ 方法),然后遍历将工作。

实际上,您根本不需要“数据库”。您可以使用简单的字典,直接在Python源代码中硬编码站点的URL结构。或者你可以用 __getitem__ 方法在特定目录中搜索文件,从而精确地重新创建将URL路径直接映射到文件系统上的文件夹结构的传统机制。遍历实际上是文件系统查找的超集。

备注

参见标题为 资源 有关资源的更多技术概述。

视图查找

现在我们就快到了。我们已经介绍了遍历,它是根据特定的URL路径检索特定资源的过程。但是什么是“视图查找”?

视图查找的需求很简单:在找到 resource . 例如,对于我们的照片示例,您可能希望在页面中查看照片,但也可能希望为用户提供编辑照片和任何相关元数据的方法。我们会把前者称为 view 视图,后者将是 edit 查看。(原版,我知道。) Pyramid 具有集中视图 application registry 其中命名视图可以与特定资源类型关联。所以在我们的示例中,我们假设我们已经注册了 viewedit 照片对象的视图,并且我们已经指定了 view 作为默认视图,以便 /joeschmoe/photos/photo1/view/joeschmoe/photos/photo1 是等效的。编辑视图将由 /joeschmoe/photos/photo1/edit .

希望很明显,编辑视图的URL路径的第一部分将解析为与非编辑版本相同的资源,特别是由 get_root()['joeschmoe']['photos']['photo1'] . 但横穿结束于此; photo1 资源没有 edit 关键。事实上,它甚至可能不是像字典一样的对象,在这种情况下 photo1['edit'] 会毫无意义。当 Pyramid 资源位置已解析为 leaf 资源,但整个请求路径尚未被占用, 非常下一个 路径段被视为 view name . 然后检查注册表,查看是否为给定类型的资源指定了给定名称的视图。如果是这样,则调用视图可调用,并将传入的资源作为相关的 context 对象(也可用作 request.context )如果找不到可调用视图, Pyramid 将返回“404未找到”响应。

您可以将 /joeschmoe/photos/photo1/edit 最终转换成下面的一段 Python 伪代码:

context = get_root()['joeschmoe']['photos']['photo1']
view_callable = get_view(context, 'edit')
request.context = context
view_callable(request)

这个 get_rootget_view 函数实际上不存在。内部, Pyramid 做一些更复杂的事情。但是上面的例子是伪代码中视图查找算法的合理近似。

用例

为什么我们要关心遍历?URL匹配更容易解释,而且已经足够好了,对吧?

在某些情况下,是的,但肯定不是在所有情况下。到目前为止,我们已经有了非常结构化的URL,其中我们的路径有一个特定的、少量的片段,比如:

/{userid}/{typename}/{objectid}[/{view_name}]

到目前为止,在所有的示例中,我们都硬编码了typename值,假设我们在开发时知道将使用哪些名称(“photos”、“blog”等)。但是如果我们不知道这些名字是什么呢?或者,更糟的是,如果我们不知道 任何东西 关于用户文件夹中URL的结构?我们可以编写一个CMS,让最终用户能够在其中任意添加内容和其他文件夹。他可能会决定将文件夹嵌套数十层。您将如何构建匹配模式,以解释可能开发的每种可能的路径组合?

这可能是有可能的,但肯定不容易。当您尝试处理所有边缘情况时,匹配模式将很快变得复杂。

然而,对于遍历来说,这很简单。二十层筑巢是没有问题的。 Pyramid 很高兴打电话来 __getitem__ 尽可能多次,直到路径段用完或资源引发 KeyError . 每个资源只需要知道如何获取其直接子资源,而遍历算法则负责剩下的资源。此外,由于资源树的结构可以存在于数据库中,而不是代码中,因此很容易让用户在运行时修改树,以建立自己的个性化“目录”结构。

Traversal发光的另一个用例是,当需要支持上下文相关的安全策略时。一个例子可能是大型公司的文档管理基础架构,其中不同部门的成员对不同其他部门的文件具有不同的访问级别。合理地说,即使是特定的文件也可能需要提供给特定的个人。如果您的资源实际上代表了与文档相关的数据对象,那么遍历在这里做得很好,因为资源授权的思想直接嵌入到代码解析和调用过程中。资源对象可以存储ACL,这些ACL可以由子资源继承和/或重写。

如果每个资源都可以生成一个基于上下文的ACL,那么每当视图代码试图执行敏感操作时,它就可以对照该ACL检查是否允许当前用户执行该操作。通过这种方式,您可以实现所谓的“基于实例”或“行级”安全性,而使用传统的表格方法对其进行建模则相当困难。 Pyramid 积极支持这种方案,事实上,如果您以受保护的权限注册视图并使用授权策略, Pyramid 在决定视图本身是否可供当前用户使用时,可以检查资源的ACL。

总的来说,遍历和视图查找比 URL dispatch . 如果你的问题不需要,很好,坚持下去 URL dispatch . 但是如果你用 Pyramid 你会发现你 do 需要支持其中一个用例,您将很高兴在工具箱中有遍历功能。

备注

甚至可以混合搭配 traversal 具有 URL dispatch 在同一 Pyramid 应用。见 结合遍历和URL调度 详情请参阅第章。