CSDN博客

img bjbs_270

用 XML-RPC 开发 Web 服务:XML-RPC 中间件

发表于2004/10/18 13:56:00  1374人阅读

分类: XML

 
用 XML-RPC 开发 Web 服务:XML-RPC 中间件 英文原文
内容:
中间件:应用程序组件的松耦合
集中 Web 账号:一个实际的示例
PHP 客户端
没有先例
参考资料
关于作者
相关内容:
用 XML-RPC 开发 Web 服务:针对 Perl 的 XML-RPC 入门
Using SOAP::Lite with Perl
在 Web 服务专区还有:
教学
工具与产品
所有的文章
在 Web 服务中使用 XML-RPC 的第 2 部分

Joe Johnstonjjohn@cs.umb.edu
资深软件工程师,O'Reilly & Associates
2004 年 4 月

流行的 Web 应用程序常常使服务它们的硬件资源不堪重负。通过使用 Web 服务中间件,开发人员可以创建一个应用程序体系结构,它分成由中间件连接起来的逻辑组件,从而可以更容易地消除性能瓶颈。这一切都是通过简单地在问题区域添加更好的处理来完成的。XML-RPC 是简单的 Web 服务协议,它用于构建中间件。

中间件:应用程序组件的松耦合

在我的前一篇文章中(请参阅参考资料),我介绍了 XML-RPC (请参阅参考资料)作为一种在远程机器上执行函数的容易的方法。然而,单单这个函数并不足以使这个协议值得去学习。我们将看到一个与 XML-RPC:中间件(middleware) 结合的有趣的 Web 服务应用程序。

在它最基本的层次,中间件只是通过使用应用程序编程接口(Application Programming Interface,API)访问资源的抽象的方法。对了给程序员提供方便,面向功能的和面向对象的编程都使用 API 来简化处理软件资源的细节,例如用户验证数据库或文件系统。明显地,调用 API 的用户会从这种抽象中受益,因为底层实现的细节对于他们是隐藏的。可能不那么确切的是,API 的实现者同样也能够从这个抽象中受益,因为他们可以从根本上改变底层库代码,而又不破坏使用旧的实现的程序。

加强实现和调用代码之间的这种独立性的软件库被认为是松耦合的(loosely-coupled)。如果调用代码忽略 API 并处理库代码中的数据结构,那么将丧失这种独立性,因为库实现中的改变可能破坏行为失常的客户端。这样的系统被称为是紧耦合的(tightly-coupled)。良好的软件工程实践要求在所有可能的地方使用松耦合的系统。

API 这个想法可以超越编程库扩展成更大的概念。当然,一个允许用户图形化修改数据库内容的应用程序它本身必须能够处理原始数据。像 MySQL 和 PostgreSQL 这样的数据库提供了 C 接口就为了达到这个目的。如果这个假设的应用程序直接使用特定的关系数据库管理(relational database management,RDBM)系统的本地接口,那么转换到一个新的 RDBM 系统将包括重写所有与旧系统进行对话的代码。通过引入中间件层作为显示逻辑和需要处理特定的底层数据库的例程之间的所有通信的代理(如表 1 所示),程序员可以在 API fence 的任何一端作根本的改变,而不需要在另一端作相应的改变。

图 1:典型的中间件的设置
典型的中间件的设置

集中 Web 账号:一个实际的示例

根据这个简短的理论背景,让我们使用 XML-RPC 中间件来解决现实世界中的一个问题。

设想一个需要用户账号登录的 Web 站点。当然,Apache 内置的 HTTP 验证能够用来存储用户的名称和密码。在通常情况下,一旦用户通过验证,其他特定于账号的数据都必须是可用的,它们也必须被存储起来。这就是在像 MySQL 这样的 RDBM 系统中存储账号信息的原因所在(请参阅参考资料)。如果前端是用 Perl 或 PHP 编写的,那么使用 CGI 脚本硬编码访问 MySQL 是很有诱惑力的。然而,这样做就使显示逻辑和特定的 RDBM 系统紧耦合。这正是中间件的用武之地。中间件创建一个灵活的系统,在这个系统中,底层的 RDBM 可以无缝地改变,而且允许用集群替换单一的数据库服务器。因而,使用中间件创建的松耦合是创建可伸缩的 Web 应用程序体系结构的关键。

为了在前端 CGI 显示代码和后端数据库及业务逻辑代码之间构建一个中间件桥,必须定义一个允许所有必需数据访问的 API。表 1 给出了这个应用程序的用户账号数据存储的 XML-RPC 中间件 API。前端代码只有通过调用这些 XML-RPC 才能够接触到底层数据。

表 1:账号信息的 XML-RPC API

主机: http://marian.daisypark.net/RPC2 端口: 1080
过程描述
过程名称 输入 输出 描述
authenticate <string>, <string> <int> 给定一个用户名和密码,如果凭证匹配,将返回一个新的会话 ID
get_account_info <int> <struct> 给定一个有效的会话 ID,将返回与这个 ID 相关联的用户的账号信息;它的结构将有下列字段:
username
fullname
points
set_account_info <struct> <int> 给定一个结构,它包含下列字段:
username
fullname
password
points
如果用户名(username)是新的,将创建一个新的账号,否则更新现有的账号;操作成功返回 1,失败返回 0

当用户第一次登录到这个虚构的站点时,他们会碰到一个 HTML 表格要求输入他们的用户名称和密码(请参阅图 2)。当用户点击提交(submit)按钮,处理这个表格的 CGI 脚本将向 Perl 监听器调用 authenticate() XML-RPC。如果用户的凭证有效,前端将收到一个会话 ID,可以用它在站点中跟踪用户或检索其他的账号信息。本示例中,在用户登录以后,它们就被传送到允许用户更新账号信息的页面。

图 2:用于 ID 和密码的 HTML 表单
用于 ID 和密码的 HTML 表格

PHP 客户端

很少有比 PHP 更适于开发前端 Web 代码的语言了。清单 1 给出了生成图 3 中所示的登录界面的 PHP 代码。

图 3:使用 PHP 登录
使用 PHP 登录

那些对 PHP 不熟悉的读者应该看一看 PHP 主页(请参阅参考资料)。如同其他的客户端,包括像活动服务器页面(Active Server Page)和 ColdFusion 这样的技术,PHP 模糊了静态页面和动态内容之间的界线。登录页面代码(参阅清单 1)首先拉出 XML-RPC PHP 库。如果用户正在提交他的用户名称和密码,那么使用 URL 和 TCP 端口号初始化一个新的 xmlrpc_client 对象。就 PHP 来说,这个对象初始化器的参数是 pathhostport

同样与 Perl 库类似,PHP 使程序员能够查看底层的 XML-RPC 会话,以便用于调试。第 6 行和第 7 行创建了用于 RPC 参数的对象。每个参数都需要用一个特殊的对象来包装,它使 PHP 库能够正确地使用 XML 对数值进行编码。

第 8 行创建了一个新的 xmlrpcmsg 对象,它对 RPC 调用的内容进行编码。这个对象应该被给与远程过程的名称,后面跟随着由所有参数列表包装成的一个 PHP 数组对象。最后,在第 9 行,返回 XML-RPC 监听器和响应对象之间的连接。您可以通过先获得存储在响应对象里的 xmlrpcval 对象,然后请求 xmlrpcval 对象的标量值,来检索监听器返回的值。为什么是标量值呢?回忆一下 API。它规定 authenticate() 返回某种类型的整数。如果这个整数非 0,那么它就是一个会话 ID,并且使用标记有 URL 的会话 ID,用户被重定向到账号维护界面。如果 authenticate() 失败,则发送一条失败消息,用户被给与再次登录的机会。

清单 2 不是来自于 XML-RPC,而是来自于 SQL。读者应该已经比较熟悉 Perl 的 DBI 模块了。如果不太熟悉,请阅读 MySQL 主页(参阅参考资料),那里提供了使用这个模块的完整介绍。

在第 4-7 行拉出所有的库之后,创建了一个全局的 DBI 句柄,$Dbh,所有的子例程都将使用它访问数据库。当脚本终止并且确保没有出现关于数据库未显式分离的 DBI 错误消息时,END() 子例程就会执行。在第 15-22 行,发布的 API 程序的名称与实现它们的 Perl 函数相关联。

第 36 行展示了验证是如何完成的。这个函数需要两个标量;检查它们以确保至少它们的大小和形式是合乎要求的。查询 SQL 表 users 来查看凭证是否匹配。清单 3 给出了创建这个表的 SQL 代码。

清单 3:创建用户表的 SQL

create table users (
         username char(12) not null default '',
         password char(13) not null default '',
         fullname char(50) not null default '',
         points     int default 0,
         Primary Key (username),
         index (fullname)
);

准备好一条 SQL 语句来查找与用户名称相匹配的行。因为用户名(username)是主键,最多将有一行匹配。返回的一列称作“密码(password)”,并且它是 DES 加密的字符串。使用 Perl 的 crypt() 函数 DES 加密传递来的 $password,然后将这个标量与表中找到的进行比较。如果这些字符串相匹配,通过 logger() 函数把这个事件记录在另一个 MySQL 表中作为日志,并且生成一个新的会话 ID。第 210 行的 new_session_id() 函数仅仅将用户名称(username)插入到 sessions 表。清单 4 给出了创建会话表的 SQL 代码。

清单 4:创建会话表的 SQL

create table sessions (
        id int auto_increment not null primary key,
        username char(13),
        issued timestamp
);

会话 ID 只是一个 auto_increment 字段。每次执行插入时,创建一个新的、惟一的 ID 以及一个插入何时发生的时间戳。MySQL 巧妙的地方在于,使用 DBI 句柄的 mysql_insertid 属性可以容易地确定最近使用的插入 ID。然后这个整数返回给 XML-RPC 客户端。

既然如何生成会话 ID 的秘密已经被解开,那么让我们看一看使用它的函数。在第 66 行,get_account_info() 开始。期望传递给它一个有效的会话 ID。回忆一下,会话 ID 属于表 sessions,但是想要的信息存储在 users 表中。需要能够通过使用 SQL JOIN 操作符来获得这些信息。 sessions users 都有一个称为“用户名(username)”的字段。通过在 sessions 里查找具有会话 ID 的行,同样可以找到那个会话的用户名(username)。然后,使用这个用户名(username)在 users 里查找包含那个用户名(username)的行。如果查找成功,就检索可以作为散列引用回给调用者的那一行。如果这个 SQL 有点难懂,简单地使用两个小的 SELECT 调用,并手工地使用 Perl 散列来连接行。

最后的函数是 set_account_info(),并且它需要一个结构。在 Perl 中,这个 XML-RPC 结构表示成散列引用。因为这个函数将要 INSERTUPDATE,所以锁定 users 表以防止这两个 SQL 操作之间发生竞态条件(race condition)。理想地,我们本可以使用 MySQL 最新的版本,因为它支持事务,但是周围还有很多以前安装的 MySQL,并且理解历史上在这个 RDBM 中如何处理事务是有用的。

DBI 的 do() 方法返回的行数受具体执行哪些 SQL 语句的影响。这个返回值可以被用来推测是否需要更新或创建帐户。请记住,只有现有的用户可以改变他们的全名或分数。“用户名(username)”字段是一个主键,应该以一种更受控的方式改变它。可以证明,密码能够在这里改变,但是将它隔离到另外一个函数中似乎更好。一旦更新完成,解锁这个表并使用 logger() 记录这个事务。如果需要创建一个新的账号,表就会被解锁,并且在加密密码之后这个信息是插入的(INSERTed)。此外,这个操作的成功或失败都返回给 XML-RPC 客户端。

没有先例

如果把 SQL、XML-RPC 监听器和客户端的代码行数都计算在内,估计这个中间件系统的代码会低于 600 行。XML-RPC 是一个强大而又简单的消息传递系统,超过了本文中所描述那种应用程序。XML-RPC 的后继者是简单对象访问协议(Simple Object Access Protocol,SOAP)(请参阅参考资料),它扩展了这里所见到的某些功能。我写的下一个系列的文章将集中在 SOAP。要获得更多关于 XML-RPC 的信息,可以查看它的主页(请参阅参考资料)。

参考资料

关于作者

在白天,Joe Johnston (jjohn@cs.umb.edu)是 O'Reilly Labs (O'Reilly and Associates 的一个新的部门)的一名程序员。只要他的猫没有坐在他的键盘上,他就为 The Perl Journal、use.perl.org、www.perl.com 和 O'Reilly Network 写文章。他和 Michael Lord 一起创建了异想天开的 UFO 民间传说的站点,Aliens、Aliens、Aliens




    

阅读全文
0 0

相关文章推荐

img
取 消
img