从请求到响应:Drupal 8 机制概览

《Drupal 8 模块开发》的第一篇文章中,我们对路由方面做了一些了解。我们看到,现在是通过声明路由,配合Controler,来创建一个 Path 对应的页面。后面我们看到,可以返回一个渲染数组,这个数组将被还原为标记,并展示在页面的内容区域中。然而,你是否注意到在这水面之下,Drupal 实际上是把这个数组根据 SymfonyHTTPKernelInterface 转换为一个 Response 对象?

本文中,我将会深入到 Drupal 8( 以及 Symfony 2 )之中,看看从用户发起请求,到看到响应的过程中,到底发生了什么。上面的例子是这一过程的可能性之一,今天我们会看一下其他的可能。本文目标是理解系统的弹性如何帮助我们制作应用。

在继续之前,我强烈建议你研究一下这张图,这里综合了渲染线(render pipeline)中的常见内容。在我看来,这张图所表达的内容已经超出了他的名字申明的范围,渲染系统只是其中的一个部分而已。

##前端控制器(index.php)

Symfony 2 现在是 Drupal 的重要组成部分。后面会看到很多 Symfony 的组件,在本文中是 HTTPKernel 以及 HTTPFoundation 。二者结合用于处理用户请求,把请求传递给应用,然后以面向对象的方式返回给用户。

HTTPKernelInterface(你可能在其他地方听说过)是这个过程的胶水,他接收一个 Request 对象,返回一个 Response 对象,用这种简单而又强大的机制,把整个过程连接在一起。

这个过程在 index.php 文件中启动,他生成了一个 Request 对象,并传递给 HTTPKernel::handle() 方法。后面就是用返回 Response 对象的方式进行响应。概括说来,这既是 Drupal 的,也是所有使用 Symfony 的 HTTPKernel 组件的应用的通用过程。

##HTTPKernel 和 事件

HTTPKernel 是 Symfony 应用的心脏。我们会看到,他的 handle() 方法对响应内容的生成做了大量工作,这些工作以一个事件驱动的工作流形式完成。这样的应用,通过对事件进行委托的方式完成,为大负载网站提供了极具弹性的架构。

如果你看了前面的图,你会发现第二列里面描述了这个工作流,这部分构成了 Symfony 和 Drupal 之间的连接。

名为 kernel.request 的事件启动了这一过程。订阅这个事件可以处理很多任务。不过 Drupal 8 中需要注意的是格式的协商和路由。格式问题决定了返回的类型(html, json, image, pdf 等);而第二个问题决定了响应这一请求需要执行哪些代码(routing.yml文件中路由定义部分的_controller键)。不过跟事件工作流里面的步骤一样,如果一个监听器返回了一个响应对象,这个过程就会跳过后面的大多数步骤,直达 kernel.response

第二个事件是 kernel.controller,在应用找到了用于处理请求的控制器之后,就会调用这个事件。在这里,监听器还可以执行一些重载方法。紧接这个步骤,核心开始解析传递给控制器的参数。Drupal 中的一个操作就是根据请求中找到的 ID 载入对象,提供给控制器。最后控制器使用给定参数开始执行。

控制器用来返回某种类型的响应。如果返回一个Response对象,这一过程会跳过后面的kernel.response事件。监听器可以进行发送前的最后修改,例如修改内容头等。前端控制器通过handle()方法获取Response对象之后,使用Responsesend()方法发送给用户,结束这一过程。

workflow

##了解渲染数组

如果控制器返回的不是Response对象。核心会触发最后一个事件:kernel.view。这一事件的订阅者负责把控制器的结果转换为Response对象。所以这意味着通过对这一事件的响应,你可以在控制器中返回任何类型的对象。

然而,正如我们在 example 中所见,多数控制器会返回一个渲染数组。一般来说,这一结构成仙了页面的主要内容(类似 Drupal 7 中的page callback)。

Drupal 8 中提供了 MainContentViewSubscriber,用来把这一数组转换为对应的Response对象。他根据之前我们提到的内容格式协商,使用 MainContentRenderer 来完成这一任务。Drupal 8 提供了一系列的渲染器,缺省使用的是 HtmlRenderer

##HtmlRenderer

这是最常用的内容渲染器,因此我们需要深入一点来研究它创建页面的方法。

这个步骤中的一个有趣的部分就是页面的选择过程。HTMLRenderer发起一个事件用来查找用于包装内容的页面类型:RenderEvents::SELECT_PAGE_DISPLAY_VARIANT。没有启用 Block 模块的缺省情况下会使用SimplePageVariant,如果启用了 Block 模块,则会引入 BlockPageVariant,他会允许把 Block 放置到内容中的区域之中。如果有需要,你可以订阅这一事件来提供自己的选项。

所有这些都在HTMLRendererprepare()方法中,HTMLRenderer提供了renderResponse()方法,会把带有 #type => 'page'的渲染数组用主内容进行封装,转换为 #type => 'html'的渲染数组,最后由 Renderer类 (相当于 Drupal 7 中的 drupal_render())。最后生成的 HTML 字符串被加入到 Response 对象,返回给前端控制器。

虽然这只是非常概要的一个描述,不过还是比较清楚的描述了这个过程。现在我们有了一个Response对象,这意味着 Kernel 能够开始触发 kernel.response 事件。然后,前端控制器就可以把Response对象发送给用户,结束这一过程。

##结论

本文中,我们通过从用户发起请求,到获得响应的整个管线的浏览,对 Drupal 8 (以及 Symfony 2)的内部机制进行了一些了解。我们看到 Drupal 8 是如何使用 HTTPKernel 以及 HTTPFoundation 这两个 Symfony 2 组件的。另外我们看到了 Drupal 的订阅者如何同事件进行互动来完成各种功能。最后,我们看到了 HTML 页面如何构建,并在渲染管线的帮助下返回给用户。

我认为了解上述内容会对建立应用产生帮助。如果你在本文中只学会一样东西,我认为应该是弹性,这正是 Drupal 8 优于 Drupal 7 的地方,真正的现代设计。

Avatar
崔秀龙

简单,是大师的责任;我们凡夫俗子,能做到清楚就很不容易了。

comments powered by Disqus
下一页
上一页

相关