使用 Meteor 轻松开发实时网站
简介: 由于 Web 无处不在,即便是很小的数据交付延迟都有可能刺激到用户。他们希望数据即时更新。不幸的是,Web 技术无法实现这种实时访问。尽管数据访问正快速标准化为一些对象关系映射 (Object-Relational Mapping, ORM) 模型,但实时通信没有任何类似的解决方案。本文将讨论 Meteor,这是一个旨在解决此问题的激动人心的新 JavaScript 框架。
什么是 Meteor?
Meteor 是一种新的 JavaScript 框架,用于自动化和简化实时运行的 Web 应用程序的开发。它使用一个名为分布式数据协议 (Distributed Data Protocol, DDP) 的协议来处理实时通信,使用 WebSockets 的新 浏览器以及使用 Asynchronous JavaScript + XML (Ajax) 长轮询的旧浏览器来支持这种协议。在这两种情况下,浏览器到服务器的通信是透明的。
DDP 协议旨在处理 JavaScript Serialized Object Notation (JSON) 文档集合,使 JSON 文档容易创建、更新、删除、查询和访问。因为 DDP 是一种开源协议,所以您可将它连接到任何客户端或数据存储。它为 MongoDB 提供了开箱即使用支持。
事实上,Meteor 提供了两个 MongoDB 数据库:一个客户端缓存数据库和服务器上的一个 MongoDB 数据库。当一个用户更改一些数据时(例如通过单击 Save),在浏览器中运行的 JavaScript 代码会更新本地 MongoDB 中的相应的数据库项,然后向服务器发出一个 DDP 请求。该代码立即像操作已获得成功那样继续运行,因为它不需要等待服务器回复。与此同时,服务器在后台更新。如果服务器操作失败或返回一个意外结果,那么客户端 JavaScript 代码会依据从服务器新返回的数据立即进行调整。这种调整称为 延迟补偿,向用户提供了更高的认知速度。
显然,甚至连 Meteor 的模板系统也是为简化实时通信而设计的。在大多数 Web 框架中,您可以轻松地混合使用超文本标记语言 (HTML) 和代码,或者与 HTML 等效的标记,比如 HTML 抽象标记语言 (Haml)。这使您能够轻松地将来自数据库的动态值插入发送给用户的页面中。在这之后,您应该负责准备提供一个系统来观察对数据的更改,然后更新您的标记。但是,Meteor 中的模板系统用于记录访问了模板中的哪些数据,并自动回调,以便在底层数据更改时调用此 HTML,使实时模板变得更加简单快捷。
示例:链接流行度竞赛
Meteor 的模板功能可使众多实时应用程序更容易编写。例如,假设您希望创建一个的站点,用户可在其中输入链接(即统一资源定位符,URL),并投票肯定和否决它们,而且赢得流行度竞赛的 URL 会显示在一个列表顶部。通过使用 Meteor,您可以轻松地实时编写这样一个应用程序,以便用户可在其他用户投票时看见他们的 65 张选票。
安装 Meteor
要安装 Meteor,可以将 清单 1 中所示的代码键入到一个 Linux® 或 Mac OS® X 终端中。Meteor 不支持 Microsoft® Windows®。
清单 1. 安装 Meteor
curl https://install.meteor.com > install_meteor.sh chmod u+x install_meteor.sh ./install_meteor.sh
现在您可创建一个新项目。
创建一个新项目
meteor
命令可自动化包含新项目创建过程中 Meteor 需要操作的一切内容的。键入 清单 2 中所示的命令,以便创建一个名为 realtime_links 的项目。
清单 2. 创建您的 Meteor 项目
meteor realtime_links cd realtime_links
Meteor 创建了一个目录,其中包含一个 HTML 文件、一个 JavaScript 文件和一个级联样式表 (CSS) 文件。最后一个文件是一个标准 CSS 文件,但前两个值得讨论一下。您可以从 下载 一节下载 realtime_links.html 和 realtime_links.js 文件的完整版本。
realtime_links.html 文件
清单 3 显示了 realtime_links.html 文件的标头和正文片段。
清单 3. realtime_links.html 标头和正文片段
<head> <title>Realtime Links Demo</title> </head> <body> {{> header }} {{> link_list }} {{> add_new_link }} </body>
可以看到,HTML 模板的开头非常简单。无需担忧如何包含 BODY
标记、 DOCTYPE
修饰符甚至 JavaScript 和 CSS 文件。Meteor 会为您处理所有这些操作。有关 Meteor 的 JavaScript 和 CSS 包的更多信息,请参阅 参考资料,获取 Meteor 网站的链接。
{{>
语法表示 “呈现此模板”。可以看到,realtime_links.html 呈现了 3 个模板:
-
header
是一个简单头部,显示了数据库中的链接数量。 -
link_list
显示了链接的列表和它们的相关投票。 -
add_new_link
是添加新链接的表单。
清单 4 显示了 header
模板。
清单 4. realtime_links.html header
模板
<template name="header"> <h1>The Link Collection</h1> <p>We currently have {{collection_size}} links.</p> </template>
header
模板呈现了一个 h1
标记以及对集合大小的简短描述。 collection_size
方法是在 JavaScript 文件 realtime_links.js 中定义的(这将在 下一节 中详细讨论)。Meteor 自动观察某个模板插入了哪些数据片段。所以,在更新集合大小时, header
模板会自动更新。
请注意,这里使用的 {{ ... }
语法类似于 Ruby on Rails 中的 <%= ... %>
或 PHP 中的 <?= ... ?>
。它可插入任意代码,所以能够以这种方式插入任何有用的动态表达式。
清单 5 显示了 link_list
模板。
清单 5. realtime_links.html link_list
模板
<template name= "link_list"> <ul> {{#each links }} <li> {{> link_detail }} </li> {{/each }} </ul> </template>
如您所见,清单 5 中的代码是一个链接列表。realtime_links.js JavaScript 文件中的 links
方法提供了此列表。系统会向每个链接呈现 link_detail
模板。请注意,无需传递任何参数,因为 Handlebars 的 #each
循环会将每次迭代的当前上下文设置为当前对象。换句话说,会将 link_detail
模板的本地方法正确解释为每个链接对象的方法。
清单 6 显示了 link_detail
模板,它控制了为每个链接显示的数据。
清单 6. realtime_links.html link_detail
模板
<template name="link_detail"> <div id="link-{{id}}"> <h1>{{url}}</h1> <p><strong>Stats:</strong> up: {{thumbs_up}} down: {{thumbs_down}} net score: {{score}}</p> <input type="button" value="Thumbs Up" class="thumbs_up" url="{{url}}" /> <input type="button" value="Thumbs Down" class="thumbs_down" url="{{url}}" /> </div> </template>
h1
元素简单地显示当前链接的 URL。然后会提供一个间断的统计清单,其中包含一个链接被支持的次数、被否决的次数和它的净分数(也就是两个值的差)。最后,有两个按钮:一个用于投赞成或支持票,另一个用于投反对或否决票。JavaScript 文件定义这些按钮的行为,但是在深入介绍此主题前,还有一个模板需要了解。
清单 7 显示了 add_new_link
模板。
清单 7. realtime_links.html add_new_link
模板
<template name="add_new_link"> <div id="new_link_form"> URL: <input id="url"> <input type="button" value="Click" id="add_url" /> </div> </template>
该模板只是一个文本输入字段和一个按钮,它们共同形成了向您列表中添加新 URL 的界面。
realtime_links.js 文件
realtime_links.js
中的 JavaScript 代码控制来自您程序的数据访问和事件回调,无论是在客户端还是在服务器上。 if (Meteor.is_client)
语句用于标记客户端部分, if (Meteor.is_server)
语句用于标记服务器部分。Meteor 提供了一种保护敏感代码的方式,阻止恶意客户端看到源代码。参见 参考资料 中的 Meteor 文档链接,了解有关的更多细节。
清单 8 显示了标头和链接列表帮助器函数。
清单 8. 标头和链接列表帮助器函数
Template.header.collection_size = function () { return Links.find({}).count(); }; Template.link_list.links = function () { return Links.find({}, {sort : {score: -1} }); };
header
模板使用清单中的第一个帮助函数,该函数返回 links
集合的大小。 link_list
模板使用第二个帮助器函数,它返回从最高分到最低分排序的所有链接。
清单 9 拥有对 link_detail
模板的两个事件回调。
清单 9. link_detail
事件回调
Template.link_detail.events = { 'click input.thumbs_up' : function () { Meteor.call('vote', this.url, 'thumbs_up'); }, 'click input.thumbs_down' : function () {Meteor.call('vote', this.url, 'thumbs_down');} };
每个事件回调处理一个支持或反对单击事件。在每种情况下,它们使用客户端上的 Meteor.call
在服务器上执行一次函数调用。可以看到从客户端对服务器执行调用很简单。例如,序列化会自动处理。
清单 10 显示了对用户可添加新链接的表单的事件回调。
清单 10. 对添加新链接表单的事件回调
Template.add_new_link.events = { 'click input#add_url' : dfunction () { var new_url = $('#url').val(); var url_row = Links.findOne( {url:new_url} ); if(!url_row){ Links.insert( { url : new_url, score: 0, thumbs_up: 0, thumbs_down: 0 }); } Meteor.call('vote', url, 'thumbs_up'); } };
首先,该表单会尝试找到一个具有所请求 URL 的现有链接对象。如果它找到这样一个对象,那么它会将该请求统计为对这个现有链接对象的一次投票。如果未找到,那么它会创建一个新链接对象,并对这个新对象执行一次 thumbs_up
投票。
这部分代码演示了 Meteor 作为一种一流技术的优点和不足,它还不适合用于生产。如您所见,客户端可在 links
集合上调用 insert
。尽管这对开发人员很有用,但从安全角度讲,这是一个问题。幸运的是,开发人员正在积极研究该代码的一个 auth
分支,这个分支可以实现强大的身份验证功能,同时仍然保持 Meteor 富有吸引力的很多强大功能和灵活性。
此外,需要认识到 Meteor 目前没有实现所有 MongoDB 功能。例如,Meteor 不支持 MongoDB upsert,该操作插入新数据或修改旧数据。如果 Meteor 支持 upsert,那么您可以编写 清单 11 中所示的函数。
清单 11. 一个使用 upsert 的假想的添加新链接表单事件回调
Template.add_new_link.events = { 'click input#add_url' : function () { var new_url = $('#url').val(); Links.update( { url : new_url}, { $set: {url : new_url}, $inc: { votes : 1 } } , true ); } };
如您所见,使用 upsert 的代码更短。它运行得可能更快,因为它仅需要与服务器执行一次往返传输。如果顺利的话,Meteor 很快就会实现对 upsert 和其他新功能的支持。
清单 12 中的代码在服务器上运行。它是一个可由客户端代码调用的方法。此方法( vote
)允许客户端对一个流行的 URL 投 thumbs_up
或 thumbs_down
票。它使用 Mongo 的 $inc
运算符递增适当的投票计数器。它还在必要时递增或递减总数。 Meteor.startup
方法仅支持代码在服务器启动时运行。 Meteor.methods
函数然后定义可在客户端上调用 Meteor.call
的函数,如前面的 清单 9 中所述。
清单 12. 服务器端的 vote
方法代码
if (Meteor.is_server) { Meteor.startup(function () { Meteor.methods({ vote: function (url, field){ new_obj = { $inc: { } }; if(field =='thumbs_up'){ new_obj['$inc']['thumbs_up'] = 1; new_obj['$inc']['score'] = 1; }else{ new_obj['$inc']['thumbs_down'] = 1; new_obj['$inc']['score'] = -1; } Links.update( { url : url}, new_obj ); } }); }); }
与 清单 10 一样,您可在客户端上运行 清单 12 中的代码。但是,出于演示目的,将会在服务器上运行该代码。随着 Meteor 的安全模型得到改进,可能会将敏感的代码开发为服务器端函数,如下面清单所示。
查看您的应用程序的实际效果
现在可启动您的 Meteor 应用程序(如 清单 13 中所示)并查看它的实际效果。
清单 13. 启动您的系统
meteor
在启动后,Meteor 会在端口 3000 上运行。打开一个 Web 浏览器并访问 http://localhost:3000/。
如果在 Add a URL 下输入一个 URL 并单击 Add,则看到一个 URL 和评分 one
。然后您可以单击 Thumbs Up 或 Thumbs Down 按钮对该 URL 进行投票。这会实时进行,而不会刷新页面。如果打开一个新 Web 浏览器,可在另一个窗口中执行相同操作,这时第一个窗口会立即更改。
当添加多个 URL 时,可以看到一个具有最高评分的 URL(分数被定义为支持票数减去反对票数)显示在最顶部。在投支持或反对票时,URL 会随着其评分更改而在列表中向上或向下移动。这是实时进行的,在输入投票时,多个浏览器上的多个用户都会收到相同的数据。
结束语
Meteor 是一个极具优势的 Web 框架,它包含了许多有趣的概念。它对实时数据的支持既富有吸引力又至关重要,特别是在考虑到其他技术实时支持充其量是一种事后行为时。随着实时交互变得对未来的 Web 更加重要,Meteor 轻松快速地实时处理复杂数据集的能力也将变得更加重要。