近接触到了 Mason,并且了解到了它基础之上的一个 MVC 框架实现,随即联想到做网站以来接触到的各种各样的页面聚合的场景,颇有意思。
页面聚合本身是一种 “分而治之” 的思想,把复杂的页面分割成可以被重用和独立维护的部分,这些部分的来源灵活,可以来自同一个 web app 中,也可以来自不同的域;可以聚合独立的子页面(页面集成),也可以聚合数据(数据集成),甚至可以聚合子呈现(模板集成)。
客户端聚合
这种聚合的最大好处在于把聚合的工作分散到如今越来越强势的客户端,减轻了服务端的压力;另一方面,也从一定程度上简化了服务端的设计。客户端聚合在互联网初期比较少见,如今随着客户端性能越来越优秀,客户端聚合的优势逐渐显示出来。
最简单的聚合形式:frame 聚合。请看谷百搜索。这种聚合形式的局限性在于,即便使用了 frame 标签,也依然是两个网站,那么两个子页面之间的交互就由于浏览器的安全限制变得困难。这是一种纯粹的静态聚合的形式,使用 HTML 的 include 标签亦类似。
客户端模板的聚合方式。例如 Velocity、FreeMarker 这些传统的模板技术,都可以做到客户端的聚合。一方面从服务端获取静态模板页,因为这些页面几乎是纯静态的,因此性能非常高;另一方面通过 ajax 技术从服务端获取变化的数据,优先展示主页面内容,优化页面展示体验,二者在页面上通过 JavaScript 的帮助形成最终的页面效果。
举例来说,比如服务端返回的模板片段是:
<div id="user.name">${user.name}</div>
再通过 ajax 从服务端获取到的页面数据是:
{user:{name:"Jim", age:"15"}}
聚合后的效果:
<div id="user.name">Jim</div>
例子非常简单,在这种模式下,对于变化数据的获取,可以使用 Node.js 接口来实现,并使用 JSON 作为数据传递形式,这个数据服务的子系统变成了一个完全独立于页面展示的数据服务提供者。
关于服务端推送技术:在页面聚合的过程中,有些数据实时性强,或者数据量大,无法一次获取完成,需要多次反复从服务端获取数据,而且,这部分数据产生的时间是由服务端确定的。看一看新浪微博、人人网,这些 SNS 网站中,都大量应用了这种技术。
服务端推送的方式有几种,而传统 BS 结构的特点是,数据都是去 “拉” 的,要服务端主动通知客户端需要绕一点点弯。
最简单的方式是轮询。客户端不断地 ajax 查询服务端(例如每隔 1 分钟查询一下是否有新的数据),甚至不断刷新页面或者子页面。但是这样的办法存在一个问题,就是大量的查询请求很可能是浪费掉的,例如一小时在线用户,每分钟 ajax 查询一次数据,查询了 60 次,只有一次是有数据的,那么剩余 59 次都是白白浪费的。
还有一个办法是被称为 “Long Pulling”(例如 pushlet 技术),服务端在接收到客户端的 ajax 查询请求时,如果没有数据,不要返回,而是 hold 住这个 HTTP 连接,直到有数据了再返回。这里的好处显而易见,但是问题也很明显——大量的连接,因此在这种情况下,多路复用技术(可以参考 NIO Server 的材料,也可以参见这里)就显得格外有用了。
第三种办法采用的比较少,就是采用第三方控件,比如Applet、ActiveX 等控件,和服务端交互,他们的交互可以超越网页传统的模型,甚至支持非 HTTP 的协议,由服务端主动推送数据。
服务端聚合
服务端聚合的本地模板聚合是最传统的聚合形式,是页面重用的基础,由模板引擎解析运算模板为最终的页面,写入输出流。这里以 SiteMesh 举例:
配置一个 url mapping 文件,再在模板上使用 SiteMesh 标签:
<div class='mainBody'> <sitemesh:write property='body'/> </div>
SSI:服务器端嵌入(Server Side Include),这也是为什么很多老网站的 URL 都是以.stm、.shtm 或.shtml 结尾的。它的嵌入和 html 标签里面的 include 不一样,SSI 是为 WEB 服务器提供的一套命令,这些命令只要直接嵌入到 HTML 文档的注释内容之中即可生效,但是它的解析需要特定的服务器支持。
<!--#include file="extend.htm"-->
随着页面缓存在互联网应用世界的称王,Oracle 定义了ESI作为一种缓存方式聚合页面的规范,它规定了将 Web 网页的页面的片段进行缓冲/缓存的技术方式。
<esi:include src="http://example.com/1.html" alt="http://bak.example.com/2.html" onerror="continue"/>
Portlet 聚合。Portlet 在早几年的企业门户应用中很常见,它本身是一组规范,也规定了一种聚合页面的方式,可以远程聚合,也可以本地聚合,它可以协助应用将数据实体和展现模板在组网上就分离开,业务节点部署可以非常灵活,Portlet 是网站解耦的一大利器。
Portal 使用 portlet 作为可插拔用户接口组件,提供信息系统的表示层。作为利用 servlets 进行 web 应用编程的下一步,portlets 实现了 web 应用的模块化和用户中心化。portlet 规范,即 jsr(Java Standardization Request)168,是为了实现 portal 和 portlet 的互操作。它定义了 portlet 和 portlet 容器之间的合约,让 portlet 实现个性化、表示和安全的 api 集。规范还定义了怎样在 portlets 应用中打包 portlets。
jsr168 的目标是:
定义 portlet 的运行时环境,即 portlet 容器
定义 portlet 和 portlet 容器之间的 api 集
提供 portlet 存储易失数据和持久数据的机制
提供 portlet 包含 servlet 和 jsp 的机制
定义方便部署的 portlet 打包方法
提供 jsr168 规范下的 portal 的二进制 portlet 便携性
通过 WSRP(web service for remote portlet)协议运行符合 jsr168 规范的远程 portlet
将复杂的页面代码交给程序开发人员进行处理,以降低网页设计的难度
假设我是一个 intranet 网站的管理员,我的公司买了一个能显示新闻信息的第三方 portlet 应用,该应用允许用户指定跟踪新 闻更新的 URL 地址,我想借助它为用户显示公司的内部新闻。另一个需求是我不想让用户通过该应用来跟踪任何其它的新闻信息来源。作为管理员,我可以为所有 的用户指定一个用于内部新闻更新的 URL 地址,同时通过改变 portlet 应用的部署描述符来取消其它人修改该地址的权限。
Portlet 就能实现这一需求,Portlet 是由 Portlet 容器聚合的,用户看到的是一个单一和完整的页面,他并不了解页面中的一个个 portlet 栏目到底来自何方。
Portlet 页面组装过程:
Portlet 由于在聚合中采用了两次请求转发的方式(一次 Action 请求、一次 render 请求),导致效率天然不高。
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》