使用 XMPP 构建一个基于 web 的通知工具javascript
使用 XMPP、PHP 和 JavaScript 编写实时 web 应用程序php
Ben Werdmuller, 顾问和做者, Freelancehtml
Ben Werdmuller 是一位 Web 策划师和开发人员,他专一于开放源码平台。他是开源社交网络框架 Elgg 的共同创始人和技术带头人。Ben 的博客 http://benwerd.com/。前端
简介: 实时 web 应用程序是联网的应用程序,带有基于 web 的用户界面,可以及时显示刚刚发布的 Internet 信息。这样的应用程序示例包括社会新闻聚合器和监控工具,它们可以使用来自外部源的数据持续更新。在本教程中,您将建立一个小型通知工具 Pingstream,它使用 PHP 和 JavaScript 经过 Extensible Messaging and Presence Protocol (XMPP) 进行通讯,XMPP 是一组设计用于支持联机状态和实时通讯功能的 XML 技术。java
本教程将向您介绍实时 web,并详细介绍之因此要构建实时 web 应用程序的几个缘由。您将学到一些技术,这些技术将帮助您建立响应及时、持续更新的 web 应用程序,这些应用程序既能保护服务器资源,又能提供良好的用户体验。node
DOM: 文档对象模型mysql
HTML: 超文本标记语言jquery
HTTP: 超文本传输协议web
REST: 具象状态传输ajax
RSS: 真正简单聚合
URL: 统一资源定位符
XML: 可扩展标记语言
实时 web 应用程序容许用户在信息发布时及时接收通知,无需手动检查原始源获取更新。经过 Twitter 和 Friendfeed 这样的社交通知工具,Google Wave 这样的基于 web 的协做工具,以及 Meebo 这样的基于 web 的聊天客户端,实时 web 应用程序逐渐流行起来。
Extensible Messaging and Presence Protocol (XMPP) 是一组基于 XML 的技术,用于实时应用程序,定义为持续更新以响应新数据或更改数据的联网应用程序。它最初做为一个框架研发,以支持企业环境内的实时消息传递和联机状态(presence)应用程序。
在本教程中,您将构建一个简单的工具 Pingstream,它在 RSS 提要更新发布时使用它们持续更新自身(参见 下载 获取 Pingstream 源代码)。在此过程当中,您将:
了解 XMPP 为什么特别适合 web 应用程序;
了解 XMPP 通讯的组件;
安装和配置 Openfire XMPP 服务器;
使用 PHP 和 XMPPHP 库链接到 XMPP 服务器;
检查并经过 XMPP 传输 RSS 提要中的新项目;
借助 Bidirectional-streams Over Synchronous HTTP (BOSH),使用 Strophe 和 jQuery 经过 HTTP 链接到 XMPP 服务器;
在 web 页面中显示 XMPP 通知。
本教程假定您比较熟悉使用 PHP 开发 web 应用程序,但也会涉及一些高级编程方法。您还应该拥有必定的 HTML 和 JavaScript 经验。拥有 jQuery JavaScript 框架经验可能会有所帮助。但本教程不要求熟悉 XMPP 或相似的技术。
要跟随本教程操做,必须安装和运行如下服务器软件:
PHP 5.2 或更高版本
Apache HTTP Server
MySQL
在本教程中,您还将下载和安装如下软件和库:
Openfire
jQuery
Strophe
XMPPHP
Last RSS
您可能会发现 MySQL 服务器工具 phpMyAdmin 可以派上用场。若是您使用一台桌面机器来本地测试您的实时 web 应用程序,您可能会发现 XAMPP 对于管理一个测试 web 服务器基础设施的安装和运行颇有用。
实时 web 简介
在本小节中,您将了解什么是实时 web 应用程序,您为什么可能须要构建这样的应用程序,以及它们与典型的现代 web 应用程序模型的区别何在。
实时 web 并不实时
持续更新的 web 应用程序 这个词汇比实时 web 应用程序 更贴切。在计算机科学中,硬 和软 实时系统都必须知足操做期限的要求。当任务不能在其分配时间内完成时,硬实时系统将失败。实时 web 应用程序与软实时系统更类似,在软实时系统中,某个功能的延迟并不会致使系统失败(但可能会下降性能)。但您不该对实时 web 应用程序分配严格的任务计划。目前,web 并非适合时间关键型应用程序的平台。
应用程序是帮助用户执行任务的专门软件,其特征是:从用户或其余源接收输入,而后提供可读的输出。应用程序也可能动态响应 — 以可视或编程方式 — 自动接收的输入数据中的变化。例如,当包含特定关键字的新闻出如今一个新闻监控应用程序链接到的新闻专线中时,该应用程序可能会通知用户。
因为 web 的起源是做为一个文档服务平台,所以它没有针对应用程序优化。HTML 很是适合表示和超连接文本内容,但不适合建立动态界面。web 应用程序可以接收和响应用户输入,这要归功于 PHP 这样的服务器端脚本语言与表单和 JavaScript 这样的 web 输入技术的结合。可是,要建立自动 更新的界面,您必须克服一些障碍。这些障碍比较难以克服,由于没有任何 web 技术在研发时考虑到这个功能。相比之下(以新闻监控为例),桌名软件无需刷新其界面就可以将通知发送给用户;桌面软件能够持续更新自身。相反,web 则局限于基于页面的模型。
然而,基于 web 的实时应用程序仍是能够实现的,并且它们的好处显而易见。这样的应用程序包括企业聊天工具、联网的实时文档协做工具、以及搜索界面,它们可以及时显示新发布的内容。
一般,web 应用程序经过使用 Asynchronous JavaScript and XML (Ajax) 工具来模拟持续更新的界面。在这个模型中,应用程序的 web 页面中包含 JavaScript,它在后台反复请求一个服务器回拨。尽管 Ajax 应用程序的响应性在不少状况下都够用,但这种技术仍是有一些缺陷。
Ajax 不能容忍不稳定的 Internet 链接:一次临时掉线可能会致使整个界面失败。它在服务器负载方面也效率低下。假设您的后台 Ajax 轮询函数每 10 秒检查一次服务器。每一次都将创建一个新的 HTTP 链接,包括初始化服务请求所需的资源,即便没有新数据能够向用户显示。结果是应用程序没必要要地使用过多的处理器时间和带宽。
基于 XML 的技术向 web 应用程序提供了巨大的优点。XML 解析器如今是大多数环境的一个标准组成部分;无需其余软件就能支持以适当的格式读写数据。XML 是自我描述型语言;使用它的文档不须要外部架构。最后,正如 web 是独立于平台的同样,XML 做为一种技术支持在不一样平台间互操做。所以,开发人员能够将精力集中于特定于他们的应用程序的逻辑。
web 基于 HTML、Cascading Style Sheets (CSS) 和 JavaScript 等可互操做的免费开源标准。若是针对 web 上的实时通讯的新标准出现,那么该标准也应是免费、开源和可互操做的。基于 XML 的 XMPP 知足这些标准。在本教程中,您将使用 XMPP 来构建一个客户端库,它经过标准方法(好比一个 web hook)来接收输入,并将适当的数据实时中继到用户。
本小节将简要介绍 XMPP,它的起源,以及为什么它是一个适合实时 web 通讯的协议。您将检查 XMPP 通讯设置的组件,并查看展现这些组件如何使用的示例。
XMPP 是一组基于 XML 的技术,用于实时应用程序。最初,XMPP 做为一个框架开发,目标是支持企业环境内的即时消息传递和联机状态应用程序。当时的即时消息传递网络是私有的,很是不适合企业使用。例如,AOL Instant Messenger 不能针对公司内的安全通讯进行调整。尽管存在一些商业解决方案,但它们固定的特性集一般不能进行调整,以知足组织的特殊需求。XMPP,当时名为 Jabber,容许组织构建本身的定制工具来促进实时通讯,并容许安装现成的第三方解决方案。
XMPP 是一个分散型通讯网络,这意味着,只要网络基础设施容许,任何 XMPP 用户均可以向其余任何 XMPP 用户传递消息。多个 XMPP 服务器也能够经过一个专门的 “服务器-服务器” 协议相互通讯,提供了建立分散型社交网络和协做框架的有趣可能性,但这个主题已超出了本教程的讨论范围。
顾名思义,XMPP 可用于知足普遍的、对时间敏感的特性要求。实际上,Google Wave,一个大型多用户协做环境,将 XMPP 做为其联合协议的基础。尽管 XMPP 的出现是为了知足 “我的-我的” 即时消息传递的要求,但它彻底没必要局限于此任务。
要促进消息传递,每一个 XMPP 客户端用户必须拥有一个全局唯一标识符。基于历史缘由,这些标识符称为 Jabber IDs,或称为 JIDs。鉴于这个协议的分布式特征,重要的是 JID 应包含联系用户所需的全部信息:不存在将用户连接到他们链接到的服务器的中央知识库。JID 的结构相似于电子邮件地址(但不要求 JID 同时也是有效的电子邮件收件人)。
客户端和服务器节点,我将它们统称为 XMPP 实体,都拥有 JIDs。SomeCorp 公司的员工 John Doe 可能拥有 JID John.Doe@somecorp.com。这里,somecorp.com 是 SomeCorp 公司的 XMPP 服务器的地址,John.Doe 是 John Doe 的用户名。
JIDs 还拥有链接到它们的资源。这容许在一个 XMPP 实体标识符以外进一步处理细粒度;例如,尽管上面的示例整体上可以表示 John Doe,但 John.Doe@somecorp.com/Work 能够用于将数据发送到与他的工做相关的工具。
这些资源能够采用任意用户定义的名称,一个 XMPP 实体能够拥有任意数量的资源。除了能够是上下文依赖的外,它们还能够绑定到设备、工具或工做站。对于您的 Pingstream 示例,web 站点的每一个访问者都将做为同一个用户登陆 XMPP 服务器,但他们拥有不一样的资源。
使用 XMPP 的实时消息传递系统包含三大通讯类别:
消息传递,其中数据在有关各方之间传输;
联机状态,它容许用户广播其在线状态和可用性;
信息/查询请求,它容许 XMPP 实体发起请求并从另外一个实体接收响应。
这些类别是互补的。例如,若是用户或实体离线(尽管在许多用例中,理想的状态是服务器在用户返回以前一直持有用户的消息),则没有将数据发送给用户或发起一个实体的信息/查询请求的点。这些消息中的每一条都将经过一个完整的 XML 节 传递 — XML 节是以 XML 表达的独立信息项。
这三种类型的 XMPP 节都拥有如下公共属性:
from:源 XMPP 实体的 JID;
to:目标接收者的 JID;
id:此次对话的可选标识符;
type:节的可选子类型;
xml:lang:若是内容是人们可读的,则为消息语言的描述。
基于 XMPP 的数据传输发生在一些 XML 流上,默认在端口 5222 上操做。这些 XML 流其实是两个完整的 XML 文档,每一个文档对应一个通讯方向。一旦会话创建,stream 元素将打开。这个元素将封装整个通讯文档。而后,一些节被注入这个文档的第二层。最后,一旦通讯结束,stream 元素将关闭,造成一个完整的文档。
例如,清单 1 展现了一个 stream 元素,它创建了从客户端到服务器的通讯。
清单 1. 创建从客户端到服务器的通讯的 stream 标记
<stream:stream from="[server]" id="[unique ID over conversation]" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0"> |
一旦通讯创建,客户端就能使用 message 元素将消息发送到另外一个用户,message 元素包含如下任意子元素:
subject:一个可读的字符串,表示消息主题。
body:一个可读的字符串,表示消息体。若是每一个消息体标记都拥有一个不一样的 xml:lang 值,那么能够包含多个消息体标记。(xml:lang 是唯一可能的属性。)
thread:一个唯一标识符,表示一个消息线程。客户端软件可使用这个子元素将相关消息串联在一块儿。
可是,消息也能够很是简单,如 清单 2 所示:
<message from="sendinguser@somedomain" to="recipient@somedomain" xml:lang='en'> <body> Body of message </body> </message> |
对于提供实时 web 界面而言,消息节是最有用的节。“发布-订阅” 模型 — 在实时 web 应用程序中使用消息来传输数据的一种替代方法 — 将稍后介绍。
信息/查询节拥有普遍的功能。一个例子就是 “发布-订阅” 模型,在该模型中,发布者通知服务器某个特定资源进行了更新,服务器则通知已选择订阅这些通知并拥有适当受权的全部 XMPP 用户。
来自发布者的一系列项目被编码为一些节,格式为基于 XML 的 Atom 发布格式。每一个项目都包含在一个 item 元素内,而后合并到一个 pubsub 元素中,最后成为一个信息/查询节。在 清单 3(选自 XMPP 发布-订阅规范)中,Shakespeare's Hamlet(JID 为 hamlet@denmark.lit/blogbot)用他著名的独白发布一个更新到 pubsub.shakespeare.lit pubsub 更新节点:
清单 3. 对 pubsub.shakespeare.lit pubsub 更新节点的更新
<iq type="set" from="hamlet@denmark.lit/blogbot" to="pubsub.shakespeare.lit" id="pub1"> <pubsub xmlns="http://jabber.org/protocol/pubsub"> <publish node="princely_musings"> <item> <entry xmlns="http://www.w3.org/2005/Atom"> <title>Soliloquy</title> <summary> To be, or not to be: that is the question: Whether 'tis nobler in the mind to suffer The slings and arrows of outrageous fortune, Or to take arms against a sea of troubles, And by opposing end them? </summary> <link rel="alternate" type="text/html" href="http://denmark.lit/2003/12/13/atom03"/> <id>tag:denmark.lit,2003:entry-32397</id> <published>2003-12-13T18:30:02Z</published> <updated>2003-12-13T18:30:02Z</updated> </entry> </item> </publish> </pubsub> </iq> |
信息/查询节也用于请求一个特定 XMPP 实体的有关信息。例如,在 清单 4 中的节中,boreduser@somewhere 正在查找 friendlyuser@somewhereelse 拥有的公共项目。
清单 4. 用户查找由 friendlyuser@somewhereelse 拥有的公共项目
<iq type="get" from="boreduser@somewhere" to="friendlyuser@somewhereelse" id="publicStuff"> <query xmlns="http://jabber.org/protocol/disco#items"/> </iq> |
反过来,friendlyuser@somewhereelse 使用一列可被订阅到使用 “发布-订阅” 的项目进行响应,如 清单 5 所示:
<iq type="result" from="friendlyuser@somewhereelse" to="boreduser@somewhere" id="publicStuff"> <query xmlns="http://jabber.org/protocol/disco#items"> <item jid="stuff.to.do" name="Things to do"/> <item jid="stuff.to.not.do" name="Things to avoid doing"/> </query> </iq> |
在 清单 5 中的信息/查询节中的每一个返回项目都拥有一个能够订阅到的 JID。信息/查询还容许超出本教程范围的普遍的服务器信息请求。它们中的许多在针对多服务器环境的 web 应用程序上下文中有用,或者做为复杂的分散型协做框架的基础。
联机状态信息包含在一个联机状态(presence)节中。若是 type 属性省略,那么 XMPP 客户端应用程序假定用户在线且可用。不然,type 可设置为 unavailable,或者特定于 pubsub 的值:subscribe、subscribed、unsubscribe 和 unsubscribed。它也能够是针对另外一个用户的联机状态信息的一个错误或探针。
一个联机状态节能够包含如下子元素:
show:一个机器可读的值,表示要显示的在线状态的整体类别。这能够是 away(暂时离开)、chat(可用且有兴趣交流)、dnd(请勿打扰)、或 xa(长时间离开)。
status:一个可读的 show 值。该值为用户可定义的字符串。
priority:一个位于 -128 到 127 之间的值,定义消息路由到用户的优先顺序。若是值为负数,用户的消息将被扣留。
例如,清单 6 中的 boreduser@somewhere 能够用这个节来代表聊天意愿:
<presence xml:lang="en"> <show>chat</show> <status>Bored out of my mind</status> <priority>1</priority> </presence> |
注意 from 属性此处省略。
另外一个用户 friendlyuser@somewhereelse 能够经过发送 清单 7 中的节来探测 boreduser@somewhere 的状态:
<presence type="probe" from="friendlyuser@somewhereelse" to="boreduser@somewhere"/> Boreduser@somewhere's server would then respond with a tailored presence response: <presence xml:lang="en" from="boreduser@somewhere" to="friendlyuser@somewhereelse"> <show>chat</show> <status>Bored out of my mind</status> <priority>1</priority> </presence> |
这些联机状态值源自 “我的-我的” 消息传递软件。show 元素的值 — 一般用于肯定将向其余用户显示的状态图标 — 在聊天应用程序以外如何使用如今还不清楚。状态值可能会在微博工具中找到用武之地;例如,Google Talk(一个 XMPP 聊天服务)中的用户状态字段的更改能够被导入为 Google Buzz 中的微博条目。
另外一种可能性就是将状态值用做每用户应用程序状态数据的携带者。尽管此规范将状态定义为可读,但没有什么可以阻止您在那里存储任意字符串来知足您的要求。对于某些应用程序而言,它能够不是可读的,或者,它能够携带微格式形态的数据负载。
您能够为一个 XMPP 实体拥有的每一个资源独立设置联机状态信息,以便访问和接收链接到一个应用程序中的单个用户的全部工具和上下文的数据只需一个用户账户。每一个资源均可以被分配一个独立的优先级;XMPP 服务器将首先尝试将消息传递给优先级较高的资源。
要经过使用 JavaScript 的 XMPP 进行通讯的 web 应用程序必须符合一些特殊要求。出于安全考虑,不容许 JavaScript 从 web 页面的域与不一样域上的多个服务器通讯。若是您的 web 应用程序界面被托管在 application.mydomain.com,全部 XMPP 通讯也必须发生在 application.mydomain.com。
防火墙是另外一个问题所在。理想状况下,若是您将 XMPP 用做您的 web 界面的实时元素的基础,那么您但愿它对防火墙后面的用户有效。可是,公司防火墙一般只对少数几个协议开放几个端口,以便容许 web 数据、电子邮件和相似的通讯经过。默认状况下,XMPP 使用端口 5222,这极可能是公司防火墙阻止的端口。
假设您知道您的用户前面的防火墙在端口 80 上容许 HTTP(这是用于访问 web 的默认协议和端口)。理想状况是您的 XMPP 通讯可以越过该端口上的 HTTP。可是,HTTP 的设计并不针对持续链接。web 的架构不一样于实时数据所需的通讯架构。
下面咱们看看 Bidirectional-streams Over Synchronous HTTP (BOSH) 的标准,该标准为双向同步数据提供一个模拟层。借助这个标准,能够与一个 XMPP 服务器创建一个较长的 HTTP 链接(时长一分钟或两分钟)。若是新数据在那个期间到达,则 HTTP 请求返回数据并关闭;不然,该请求只是失效。无论是哪一种状况,一旦一个请求关闭,另外一个请求将从新创建。尽管结果是对一个 web 服务器的一系列重复链接,但它是一个比 Ajax 轮询更有效的数量级,特别是由于链接到的是一个专业服务器而不是直接链接到 web 应用程序。
BOSH 上的 XMPP 容许 web 应用程序经过一个原生链接持续与 XMPP 服务器通讯。客户端经过端口 80 上的 HTTP 上的一个标准 URL 链接。而后,web 服务器将这个链接代理到由 XMPP 服务器操做的一个不一样端口 — 一般是 7070 — 上的 HTTP URL。这样,不管什么时候数据被发送到 XMPP 服务器,web 应用程序只需使用一些资源,而 web 客户端可使用一般支持的 web 标准从防火墙后操做。维持 BOSH 的较长 HTTP 轮询的开销主要由 XMPP 服务器而不是 web 服务器或 web 应用程序承担。web 服务器和 XMPP 服务器都不会受到与使用 JavaScript 进行通讯同样的域限制,正是由于这一点,消息才可以被发送到其余 XMPP 服务器和客户端。
如今,您理解了 XMPP 如何适合实时 web,能够下载并设置它,以便开始建立这个 Pingstream 应用程序。
在本小节中,您将安装 Openfire XMPP 服务器并配置它来支持您的实时 web 应用程序。
有两个领先的开源 XMPP 服务器能够免费下载。它们都应用普遍并经过 GNU Public License version 2 许可,每一个服务器都有本身的优点和缺点:
l ejabberd:ejabberd 中的 e 指的是 Erlang,一种软实时编程语言。这一技术基石使 ejabberd 很是快。它还与 XMPP 核心和相关标准高度兼容。ejabberd 能够安装在大多数环境中。
l Openfire:Openfire 用 Java™ 语言编写,用户友好,安装方便。
本教程使用 Openfire。
为您的 Openfire 用户和配置建立一个新的 MySQL 数据库。经过使用 MySQL,您能够以编程方式从您的 PHP web 应用程序添加、编辑、删除和查询您的 XMPP 服务器用户,以及调节您的 XMPP 基础设施以匹配您的 web 基础设施。
若是您安装了 phpMyAdmin,好比做为您的 XAMPP 安装的一部分,那么您能够按照如下步骤建立数据库:
1. 从主界面选择 Privileges。
2. 选择 Add a new user。
3. 添加用户细节(确保主机是 localhost;本教程假定您是在 localhost 上测试),选择 Create database with same name and grant all privileges,如 图 1 所示。不要向您的 Openfire 数据库新用户授予全局数据库特权。
图 1. 在 phpMyAdmin 中添加一个 Openfire 数据库
添加用户和数据库后,就能够安装 Openfire 服务器了。
下载 Openfire 安装程序并运行它,将 Openfire 安装到您选择的位置(参见 参考资料)。(您也能够选择从 Openfire 的 Subversion 源代码知识库检查 Openfire 的最新版本并本地构建它,但这个主题超出了本教程的范围。)收到提示时,告知 Openfire 安装程序在安装完成时启动服务器。
服务器启动后,您应该看到服务器状态窗口,如 图 2 所示:
单击 Launch Admin 打开一个基于 web 的向导,如 图 3 所示,该向导将带您逐步配置您的 Openfire 服务器:
配置向导容许您选择使用标准数据库链接或嵌入式数据库链接。选择标准数据库链接,以便您可以使用您的 MySQL 数据库。
从 Database Driver Presets 列表选择 MySQL。将您的服务器和数据库名称插入 Database URL 字段。例如,对于在 localhost 上设置、名为 openfire 的 MySQL 数据库而言,应输入:
jdbc:mysql://localhost:3306/openfire |
在向导的下一屏幕上,选择将用户账户存储在数据库中。输入此前建立的数据库用户的用户名和密码,而后一直继续到配置向导结束。此时,您应该已经为您的 XMPP 服务器建立了一个服务器管理员并设置了域位置。
每用户通知的插件
根据本教程的演示目的,您将只需使用在这里建立的两个用户。可是,若是您但愿在您的应用程序中支持复杂的每用户通知,那么您须要可以以编程方式从您的应用程序的 PHP 部分添加和移除用户。Openfire 的 User Service 插件经过一个用于 XMPP 用户管理的 REST 界面向您提供这个功能。要安装这个插件,从 Openfire 插件站点(参见 参考资料)下载它。插件自己是单个文件:userservice.jar。您必须将其放置到您的 Openfire 安装的 /plugins 目录中。
使用您创建的管理员凭证登陆到管理屏幕。单击 Edit Properties(位于 Server ports 下方),记录列示的服务器名称。这个名称将造成您的 JIDs 的域部分。这个名称是不可互换的,好比,不能使用 localhost
替代 127.0.0.1
,反之也不行。
单击顶部导航菜单中的 Users/Groups 并建立两个新用户。这些用户将您在开发过程当中的测试用户。
单击 Server settings,而后单击 Offline messages。因为您将 XMPP 用于界面通知,所以应将 Offline Message Policy 设置为 Drop,如 图 4 所示。您不想保存用户没有登陆时收到的消息,不然,当他们返回时,可能会被数千条通知所 “淹没”。
在 Server settings 中,单击 Server to Server。对于本文,您没必要链接到外部服务器,由于您不须要做为更大的 XMPP 网络的一个链接部分操做。所以,将 Service Enabled 设置为 Disabled,Allowed to Connect 设置为 White List。这些设置将阻止未受权的链接形成破坏。
Openfire 在 http://localhost:7070/http-bind 维护了一个 HTTP 绑定 URL,以便经过 BOSH 访问。要在端口 80 上使用这个 URL,您必须配置 Apache HTTP Server 以将一个 URL 转发到这个位置。为此,您须要启动代理模块。
打开您的 http.conf Apache 配置文件并找到 mod_proxy.so
和 mod_proxy_http.so
的 LoadModule
条目,它们默认被注释掉。移除前导的井字符(#
),取消注释。这个配置文件的 Dynamic Shared Object (DSO) Support 部分中的多个适当的行(不必定在一块儿)如今应该相似于 清单 8:
清单 8. 启用 Apache HTTP Server 中的代理支持
LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule proxy_module modules/mod_proxy.so |
在配置文件的末尾,添加 清单 9 中的行(若是您没有将 locahost 做为您的测试服务器环境,则应将 127.0.0.1 替换为您的服务器 IP 地址):
# XMPP proxy rule ProxyRequests Off ProxyPass /xmpp-httpbind http://127.0.0.1:7070/http-bind/ ProxyPassReverse /xmpp-httpbind http://127.0.0.1:7070/http-bind/ |
注意,在 清单 9 中,您在端口 80 上使用了一个稍微不一样的 URL:/xmpp-httpbind
。这个 URL 是 strophe.js(您稍后将用到的客户端 JavaScript 框架)分配给一个用于设置 BOSH 端点的变量的值。
重启服务器。如今,您能够开始编写使用 XMPP 的 web 应用程序了。
在前面的小节中,您设置了服务器和插件。在本小节中,您将建立您的实时应用程序的服务器端部分。
您的应用程序的 PHP 端将执行如下两个主要任务:
1. 一个 PHP 脚本将获取一个 RSS,并将在第一个条目自您上次检查以来发生改变时通知您。
2. 一个前端脚本将初始化您的 JavaScript XMPP 客户端。
首先在您的测试服务器的 web 根中建立一个名为 pingstream 的 PHP 新项目空间。若是您使用针对 Eclipse 开发环境的 PHP Development Tools 项目扩展,那么要注意,要在 web 根、而不是 Eclipse 的默认工做空间中建立这个项目(参见 图 5):
图 5 显示了这个 Pingstream 项目的如下值:
l Project name 字段:Pingstream
l Contents 单选按钮:Create project from existing source
l Directory 字段:c:\xampp\htdocs\pingsstream
l PHP Version 单选按钮:Use default PHP settings
XMPPHP 是应用最普遍的针对 PHP 的 XMPP 库。与 ejabberd 和 Openfire 同样,它也是免费和开源的,所以它是首次使用 XMPP 开发人员的一个不错的起点。
下载 XMPPHP(参见 参考资料)。解包这个归档并将其插入您的新项目的 lib/xmpphp 子文件夹中。
最后,为节约您的 RSS 提要的解析时间,下载并安装 Last RSS PHP 解析器(参见 参考资料)。
您将使用此前建立的两个测试用户之一做为您的通知发送方。在您的 /pingstream 的根中建立一个名为 config.inc.php 的文件,并添加 清单 10 中的内容:
<?php // Pingstream configuration file // Define global $CONFIG array - don't change this! global $CONFIG; $CONFIG = array(); $CONFIG['send'] = new stdClass(); // Set account details for sending party $CONFIG['send']->user = 'testuser'; // User portion of JID $CONFIG['send']->host = '127.0.0.1'; // Host portion of JID $CONFIG['send']->resc = 'pingstream';// Resource portion of JID $CONFIG['send']->pass = 'mypass'; // Password for user |
config.inc.php 文件还必须包含关于您的接收方的细节,如 清单 11 所示:
// Set the receiving account details $CONFIG['receive'] = 'receivinguser@127.0.0.1'; |
您还需将少许数据缓存在一个文件中,所以 config.inc.php 须要包含一个可写入的文件路径,如 清单 12 所示:
// Full path to your cache directory, with trailing slash $CONFIG['cachedir'] = '/tmp/'; |
若是您位于一台 Microsoft® Windows® 机器上,那么您能够将这个位置设置为一个空字符串。不然,确保您指定的目录包含一个完整路径和一个结束斜杠,并被设置为全局可写入(world-writeable)。
您的 Pingstream 应用程序将检查 IBM developerWorks Web development 专区提要获取更新,所以,将 清单 13 中的内容添加到 config.inc.php:
清单 13. 检查一个 developerWorks 提要获取更新
// Set the RSS feed you're going to check $CONFIG['rss'] = 'http://www.ibm.com/developerworks/views/web/rss/libraryview.jsp'; |
如今,建立另外一个新文件:/lib.inc.php。这将是您的库文件,包含在您的应用程序的主控制器页面和用户界面页面中。
lib.inc.php 的顶端必须引用 config.inc.php(主 XMPPHP 库)和 lastRSS,如 清单 14 所示:
<?php // Load libraries require_once('XMPPHP/XMPP.php'); require_once('config.inc.php'); require_once('lastRSS.php'); |
接下来,您将建立一个函数来经过 XMPP 将一条消息发送到客户端。经过 XMPPHP 完成这个任务很简单,只需使用在您的配置文件中保存的凭证创建一个链接,发送消息,而后关闭链接。
在这个函数的第一部分中,您建立了一个新的 XMPPHP_XMPP 对象,如 清单 15 所示:
// Load configuration global $CONFIG; $conn = new XMPPHP_XMPP( $CONFIG['connect']->host, 5222, $CONFIG['connect']->user, $CONFIG['connect']->pass, $CONFIG['connect']->resc); |
注意,XMPPHP 经过 XMPP 通讯的默认端口 5222 链接到您的 XMPP 服务器。尽管您的客户端通讯须要使用 BOSH,但服务器端没有这个要求。
要链接到 XMPP 服务器,须要发送一个初始链接请求,一直等到接收到您的 XMPP 会话已经启动的通知,而后发送一个联机状态节来声明您处于在线状态(参见 清单 16):
$conn->connect(); $conn->processUntil('session_start'); $conn->presence(); |
下一步是发送消息自己,该消息已被预先填充到一个名为 $message 的变量中(参见 清单 17):
$conn->message($CONFIG['receive'], $message); |
而后您使用 $conn->disconnect(); 断开链接。
在此过程当中,您可能会遇到错误。清单 18 将清单 15、16 和 17 中的代码放到一个将插入到 lib.inc.php 中的函数中。在这个过程当中,它将发送一条消息的业务封装到一个 try/catch 语句中,该语句将把任何异常消息写入错误日志中。
清单 18. 完整的 send_notification 函数,包含 try/catch 语句以记录错误
/** * Updates everyone's user interface with a message */ function send_notification($message) { // Load configuration global $CONFIG; $conn = new XMPPHP_XMPP( $CONFIG['connect']->host, 5222, $CONFIG['connect']->user, $CONFIG['connect']->pass, $CONFIG['connect']->resc); try { $conn->connect(); $conn->processUntil('session_start'); $conn->presence(); $conn->message($CONFIG['receive'], $message); $conn->disconnect(); } catch(XMPPHP_Exception $e) { error_log($e->getMessage()); } } |
您能够对任意数量的公共通知应用程序使用这个简单的机制。这里,您向访问站点的全部用户发送相同的通知,但经过如下机制来定制通知是件麻烦事:
每一个用户被赋予一个唯一会话字符串,或者一个特定于某个特殊兴趣或搜索的共享字符串;
这个字符串做为资源片断被添加到接收方的 JID;
消息而后被发送到 user@domain/session-string JID。
接下来,您须要一个函数(参见 清单 19)来从您的指定提要检索最新的 RSS 条目:
function get_last_feed_item() { global $CONFIG; // Load configuration $rss = new lastRSS; // Initialize lastRSS $rss->CDATA = 'content'; if ($rs = $rss->get($CONFIG['rss'])) { if (isset($rs['items'][0])) return $rs['items'][0]; else return false; } } |
这个函数初始化 lastRSS,加载您配置的 RSS 提要(这里是 IBM developerWorks Web development 专区的最新文章提要),将最顶端的条目返回为一个数组。拥有这个最新提要条目后,您须要知道自从上次检查以来,该条目是否被更改。为此,您须要使用一个小型文本文件。为保护应用程序,最好使用数据库或另外一种方法,但对于测试目的,能够放心使用一个小型文件缓存。这个缓存在您每次检查时都存储最新提要条目的 URL;若是这个 URL 更改,那么您就有一个新条目,应该通知用户。另外一个函数 feed_has_changed 将据此返回 true 或 false。无论是哪一种状况,它都会将这个最新 URL 保存到缓存文件中,为下次检查作好准备。清单 20 展现了 feed_has_changed 函数:
function feed_has_changed($url) { global $CONFIG; $changed = false; // Check to see if the file exists, and if it does, if // the URL has changed if (!file_exists($CONFIG['cachedir'] . 'cache.txt')) { $changed = true; } else if (file_get_contents($CONFIG['cachedir'] . 'cache.txt') != $url) { $changed = true; } // If the URL has indeed changed, update the version in the cache // and return true if ($changed) { file_put_contents($CONFIG['cachedir'] . 'cache.txt', $url); return true; } // Otherwise return false return false; } |
您将把最新条目的一个简单的 HTML 编码版本传递给客户端。在更高级的应用程序中,能够以 JavaScript Object Notation (JSON) 或 XML 编码该条目,多包括一些元数据,并容许客户端 JavaScript 根据设备和浏览器适当格式化它。可是,对于如今,清单 21 中的版本就够用了。
function last_item_html($item) { return <<< END <div class="item"> <div class="item_title"> <h2><a href="{$item['link']}" target="_blank">{$item['title']}</a></h2> </div> <div class="item_body"> {$item['description']} </div> </div> END; } |
最后,在您的 /pingstream 目录中建立一个名为 backend.php 的新 PHP 文件。使用 清单 22 中的简单 PHP 脚本做为文件内容。这个脚本负责检索 RSS 提要并经过 XMPP 将最新提要条目的 JSON 编码版本发送到您的接收方。
<?php require_once('lib.inc.php'); if ($lastitem = get_last_feed_item()) { if (feed_has_changed($lastitem['link'])) { send_notification(last_item_html($lastitem)); } } |
这就是将动态通知发送给应用程序的公共用户所需的所有内容。理想状况下,您应该将 backend.php 脚本做为一个按期时间任务运行。可是,对于测试目的,您能够经过一个 web 浏览器手动执行该脚本。
在本小节中,您将编写一些 JavaScript 函数,以便经过 BOSH 上的 XMPP 接收消息,并构建一个 HTML 用户界面来显示接收到的通知。
如今您须要建立用户界面来接收通知。Strophe.js 是用于经过 BOSH 发送和接收 XMPP 数据的经常使用 JavaScript 库。对于 Pingstream 中的目的,您只需接收数据,尽管有一点是显而易见的:双向通讯容许您快速构建丰富的协做环境。
尽管有几个版本,但 Strophe 的 JavaScript 版本做为一个基于浏览器的 XMPP 客户端对您而言是最有用的。下载压缩包(参见 参考资料)并将其解压缩到 pingstream 的 strophejs 文件夹中。
jQuery JavaScript 框架极大地简化了事件处理和 DOM 操做。本文提供的 Strophe.js 示例普遍使用该框架,这二者简直是 “天生一对”。下载 jQuery(参见 参考资料 中的连接)并将这个缩微版放到 pingstream 中的 jquery 文件夹中。
新建一个 index.html 文件。在该文件中包含对刚才下载的 Strophe 和 jQuery 库的引用,以及对稍后即将定义的 pingstream.js 库的引用。在 body
元素中,添加一个 ID 为 notifications 的 div
元素,如 清单 23 所示:
<!DOCTYPE html> <html> <head> <title>Latest content</title> <script type="text/javascript" src="jquery/jquery-1.4.2.min.js"></script> <script type="text/javascript" src="strophejs/strophe.js"></script> <script type="text/javascript" src="pingstream.js"></script> </head> <body> <h1>Latest content:</h1> <div id="notifications"></div> </body> </html> |
建立 JavaScript 文件 — pingstream.js — 您刚才在 清单 23 中引用的。在 pingstream.js 的顶端,定义此前在 Apache 中配置的 BOSH 代理端点,如 清单 24 所示:
var BOSH_SERVICE = '/xmpp-httpbind'; var connection = null; |
当页面彻底加载后,您想自动链接到 XMPP 服务器。您可使用 jQuery 的 $(document).ready
调用实现这个目标;其中,您新建一个 strophe.js Strophe.Connection
对象并用它链接到服务器,如 清单 25 所示:
$(document).ready(function () { connection = new Strophe.Connection(BOSH_SERVICE); connection.connect( "sendinguser@127.0.0.1", "sendingpass", onConnect); }); |
更健壮的选项
对于本教程的目的,您正在使用此前定义的发送方。对于一个更健壮的应用程序,更好的方法多是为每一个注册应用程序用户建立一个新用户,并将每一个用户订阅到一个 “发布-订阅” 界面。或者,若是您将用户名和密码留空并将 XMPP 服务器配置为接受这种类型的链接,那么 Strophe.js 能够匿名登陆。在这些状况下,将针对每一个匿名用户动态建立一个 JID;这些 JID 必须受到管理。最后,您还能够扩展 XMPP 聊天室。
在 清单 25 中,Strophe.Connection.connect
方法包含一个对 onConnect
函数的引用,做为它的一个参数。onConnect
将在链接创建后当即启动。您能够利用这个机会来为入向消息添加一个通知处理程序;您在这里注册了一个名为 notifyUser
函数。随后,您发送了一个简单的联机状态节。
要确保您能够链接并接收新消息,您还需向用户发送一个友好通知。
将 清单 26 中的代码添加到您的 JavaScript 文件中的 $(document)ready
调用上方:
function onConnect(status) { $('#notifications').html('<p class="welcome">Hello! Any new posts will appear below.</p>'); connection.addHandler(notifyUser, null, 'message', null, null, null); connection.send($pres().tree()); } |
最后,因为您注册了通知处理程序,所以,只要 XMPP 客户端接收到消息节,Strophe.js 就会调用 notifyUser(msg)
函数。msg
参数是 XML 节自己的一个表示,能够如 清单 27 所示查询:
var elems = msg.getElementsByTagName('body'); var body = elems[0]; $('#notifications').append(Strophe.getText(body)); |
理想状况下,您但愿对消息进行限制,以便只显示您的服务器端发送用户发送的消息。您能够将它封装到构成 notifyUser
函数主体的一个 if
语句中,如 清单 28 所示:
function notifyUser(msg) { if (msg.getAttribute('from') == "testuser@127.0.0.1/pingstream") { var elems = msg.getElementsByTagName('body'); var body = elems[0]; $('#notifications').append(Strophe.getText(body)); } return true; } |
这个函数应位于在 清单 26 中定义的 onConnect
函数上方。
在一个 web 浏览器中打开您的 index.html 文件。您应该会看到一个简单的标题和一条消息,该消息称更新将在下面显示(这可能会使您回想起您发给本身的测试通知,称 XMPP 链接正在成功运行)。
如今加载 backend.php。就像变戏法同样,来自 IBM developerWorks Web development 专区的最新更新将显示在您的页面上。其余带有 RSS 提要的示例源包括 Twitter 账户、通信社、以及来自服务器监控软甲的更新提要。
这是开发一个强大平台的简单起点。Strophe.js 可以促进应用程序的双向通讯,尽管更简单的方法是使用标准的 jQuery HTTP 回拨来将用户输入送入系统,从而避免为您的应用程序编写一个 XMPP 后台监控进程的麻烦。更使人兴奋的是,当您 web 服务器用做 BOSH 代理时,彻底无需太多来自服务器端 web 应用程序的输入,两个或更多 web 客户端就能经过 XMPP 相互通讯。这种技术将对从办公室协做软件到游戏的不少软件产生深远影响。
本教程讨论了实时 web 应用程序的必要性,以及 XMPP 如何克服现有技术的缺点。为展现这种方法的效果,您使用 XMPPHP、Last RSS、Strophe.js、Openfire 和 PHP 开发了一个简单的 RSS 更新通知应用程序。
尽管须要一个附加服务器层和一些 JavaScript 新技术,但 XMPP 比传统 Ajax 轮询模型更加适合实时 web 应用程序。XMPP 更快,在开发和系统基础设置方面须要的开销更少,而且使用一个强大的新兴 web 开发标准。
描述 |
名字 |
大小 |
下载方法 |
Pingstream 源代码 |
pingstream.zip |
238KB |
l 您能够参阅本文在 developerWorks 全球网站上的 英文原文。
l XMPP Standards Foundation:访问 XMPP 官方站点。
l 实现可扩展消息传递和到场协议(XMPP)(M. Tim Jones,developerWorks,2009 年 9 月):探索 XMPP 的细节,了解如何将它用于简单消息传递。
l 使用 XMPP、SMS、pureXML 和 PHP 建立警报系统(Joe Lennon,developerWorks,2009 年 11 月):尝试使用 XMPP 来将通知发送到 Google Talk。
l Jabber(Gerhard Poul,developerWorks,2002 年 5 月):查看这个早期 XMPP 简介。
l Ajax 和 XML: 将 Ajax 用于聊天(Jack D Herrington,developerWorks,2007 年 12 月):了解如何使用 Ajax 轮询实现一个实时 web 应用程序。
l JavaScript Tutorial:了解如何使用这个 web 脚本语言。
l Ejabberd:进一步了解这个用 Erlang 语言编写的 XMPP 服务器。
l My developerWorks:个性化您的 developerWorks 体验。
l IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
l XML 技术库:访问 developerWorks XML 专区,得到普遍的技术文章和技巧、教程、标准和 IBM 红皮书。
l developerWorks 技术活动 和 网络广播:随时关注这些活动中的技术。
l developerWorks 播客:收听面向软件开发人员的有趣访谈和讨论。
l Openfire:下载 Openfire,这是一个基于 XMPP (Jabber) 协议的跨平台实时协做服务器。
l PHP:访问这个 PHP 站点,获取这个应用普遍的脚本语言,该语言很是适合 Web 开发,能够嵌入到 HTML 中。本教程使用 PHP 5.2 或更高版本。
l Apache HTTP Server:下载这个 Apache web 服务器。
l MySQL:下载这个开源事务型数据库。
l DB2 Express-C:下载这个免费版 IBM DB2 数据库服务器,它是中小型企业应用程序开发的一个坚实基础。
l Openfire 插件:获取用于 Openfire 的 User Service 插件。
l XMPPHP:从这个项目的 Google Code 站点下载 PHP XMPP Library。
l Last RSS:获取 Vojtech Semecky 针对 PHP 的 RSS 解析器(通过 GNU Public License version 2 许可)。
l jQuery:下载 jQuery JavaScript 库,通过 MIT 或 GNU Public License 许可。
l Strophe:下载 Strophe.js,这是一系列用于编写 XMPP 客户端的库。它的许可容许对其进行自由使用、修改和共享。
l phpMyAdmin:获取这个免费软件工具,它支持经过 web 管理 MySQL。
l IBM 产品评估试用版软件:下载或 在线试用 IBM SOA Sandbox,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
l XMPP Discussion:订阅面向开发人员、系统管理员和用户的 XMPP 讨论。
l XML 专区讨论论坛:参与任何一个 XML 相关讨论。
l developerWorks 博客:阅读这些博客并参与讨论。
引用:http://www.cnblogs.com/alex-blog/articles/2665665.html
补充:若是相关的环境都配置成功时,使用xmpp.php库进行相关的消息发送若是出现以下的错误,那么就是您的服务器上面的ssl没有配置成功,具体的一个配置方法以下所示:
意思是不支持ssl,输出phpinfo,发现本身的php环境没有安装ssl扩展,有些默认安装了,有些没有安装
这里写下windows下的安装方法
一、拷贝PHP 目录中的libeay32.dll, ssleay32.dll, php5ts.dll, php ext 目录下 php_curl.dll 文件到
system32 目录。(必定要拷贝全啊,不要丢掉哪一个,否则会报错的)
二、修改php.ini:配置好extension_dir ,去掉extension = php_curl.dll
三、重起apache。
再次输出phpinfo,看到以下内容就安装成功了
这晨须要注意一下的是,您须要确认你所更改的php.ini是不是真正的配置文件,我上回就碰到了一个问题。在php的目录下面,确实是存在一个Php.ini的文件,我改完相磁的配置之后,彻底没有反应,依然报如上的错误。后面我使用phpinfo输出一看才明白,其输出的结果php.ini指向的文件并非这个实际文件,而是另外的一名字,然后,我在那个文件中进行了相应的修改。结果能够正常的运行。在此提醒一下你们。