全部博文(1144)
分类: LINUX
2010-07-15 14:38:21
1
还记得注视您的第一个 HTML 表单作品时的兴奋感觉吗?或许您只不过是将表单内容用电子邮件发送给了自己,或者在另一个 HTML 页中显示了用户所输入的信息。无论您做的是什么,您都创建了一个信息体系结构所称为 两层或 客户端/服务器的系统。只要做些其他的工作,从 Web 表单里收集的输入就能够存储在数据库中。按照这种方式,多个客户端只需通过它们的浏览器就可以与单个数据库进行交互。通过 CGI 脚本,存储在数据库中的信息就能够需要时格式化成适当的 HTML 显示。使用这种类型体系结构的典型的 Web 应用程序是 Weblog,例如 SlashDot(请参阅 参考资料)。生成 HTML 页的代码称为 前端(front end),而包含数据库以及业务逻辑的部分称为 后端(back end)。
这个系统运行得非常好,直到您的数据库或 Web 服务器不再能够处理流量为止。如果瓶颈在于您的 Web 服务器,您可以简单地决定在您的网络中添加更多的 Web 机器。如果您是使用前端的本地应用程序编程接口(Application Programming Interface,API)调用与数据库相连接,那么改变后端实现就比较困难。转换数据库厂商或设法集群数据库服务器将意味着完全改变您的前端代码。解决的办法是将前端的表示逻辑与后端的业务逻辑相分离,但是它们仍旧需要相互连接。在前端与后端之间提供管道的软件称为 中间件(middleware)。一个非常简单的、开放的体系结构中间件协议是 XML-RPC,它在 Web 应用程序中工作良好。
2
远程过程调用(Remote Procedure Call,RPC)并不是一个新的概念。在一个客户端/服务器系统中,传统上 RPC 就是在一台机器上的应用程序调用的过程,它通过网络到达某个 RPC 服务器,在那里真正地实现被调用的过程。RPC 服务器将过程的结果打包并将那些结果发送回调用者。然后,调用应用程序继续执行。虽然这个系统需要许多的开销和延迟,但它同样也使性能较低的机器能够访问高性能的资源。它也使应用程序可以利用机器网络的计算能力。这种类型的分布式计算的一个非常熟悉的示例是 SETI@Home 项目 (请参阅 参考资料)。
Dave Winer,因 Frontier 和 Userland 而闻名(请参阅 参考资料),帮助扩展了使用 XML 和 HTTP 的 RPC 的概念。XML-RPC 的工作原理是将 RPC 请求编码成 XML 并通过标准的 HTTP 连接将其发送到服务器或 监听器(listener)部件。监听器对 XML 解码,执行被请求的过程,然后用 XML 打包结果,并通过有线线路将它们发送回客户端。客户端对 XML 进行解码,将结果转换成标准的语言数据类型,并且继续执行。 图 1 展示了客户端(请求 get_account_info
RPC)和监听器(它正返回那个过程的结果)之间一个实际的 XML-RPC 会话。
XML-RPC 令人兴奋的地方在于它可以跨编程语言和操作系统平台,允许使用不同语言编写的客户端和服务器协同工作。Perl 客户端可以和 Java 服务器对话;Python 监听器可以响应 PHP 请求;您甚至可以使用蹩脚的、陈旧的 C 编写 XML-RPC 程序。和 XML-RPC 协同工作非常容易,因为 XML 转换的细节被隐藏起来不让用户看到,当然,除非您正在实现您自己的 XML-RPC 库。
在构建您的中间件时,您应该谨记这个协议的两个重要的方面。XML-RPC 是构建在 HTTP 之上的,并且,像普通的 Web 通信量一样,它的无状态会话是各种各样的请求和响应。对于事务或加密的内置支持是不存在的。另一个需要记住的重要细节是 XML-RPC 具有一个数据类型的有限集。客户端过程参数和监听器返回值被映射在不可扩展的 XML 子集中。然而,实际上,XML-RPC 的数据类型通常足够灵活,能够完成复杂的任务。
表 1 是所有 XML-RPC 数据类型的列表。其中有四个比其他的数据类型常用得多。因为单值数据类型 < sting
> 和 < int
>(分别表示字符串和整数数据)在大部分 XML-RPC 程序中广为使用。
同样还有两种集合数据类型。任意数据类型的简单序列用 < array
> 标签表示。记录、结构和联合数组用 < struct
> 标签表示。XML-RPC 结构是使用关键字-值对形成的,对于这一点, Perl、Python 和 PHP 编码人员应该觉得很自然。
|
3
让我们考虑一个运行中的 XML-RPC 的具体示例。很多计算机语言入门图书都创建“hello world”程序,它们都毫无二致地编译和打印一个字符串。XML-RPC 的入门程序需要一个更丰富的框架。因为您大量的 XML-RPC 工作都将花在客户端,让我们构建一个客户端来与 XML-RPC 监听器对话,监听器定义了一个 sum()
过程,毫无疑问地,它将返回传递给它的两个整数之和。不管您使用哪种语言编写 XML-RPC 客户端,您总需要知道:
这是定义 XML-RPC 监听器的 API 的分类信息。不幸地是,XML-RPC 规范没有为监听器提供任何标准的发现方法来用于传输这个信息。我推荐使用简单的 Web 页,它看起来像 表 2。
|
根据这个信息,我们可以构建客户端。因为稍后在本文中有一个 PHP 客户端的扩展示例,让我们看看运行的 Perl 的 Frontier::RPC
模块。不要被它的名称所蒙蔽;这实际上是 Perl 的 XML-RPC 库,由于历史原因才有了这个意外的名称。这个库依赖于 XML::Parser
,反过来它又依赖于 expat XML 解析器。两个 Perl 模块都可以在离您最近的 CPAN (请参阅 参考资料)镜像上找到,但是您将需要访问 SourceForge 获得 expat 源(请参阅 参考资料)。一个好的消息是,在大部分 Unix 系统上 expat 可以干净利落地编译,所以安装不应该太困难。请查阅 SourceForge expat 页,看看能够多大程度地支持您的操作系统。
4
一旦您安装完 Frontier::RPC
,您可能会惊讶它只用那么少的代码调用 XML-RPC。 清单 1是一个脚本,它使用带有两个硬编码整数参数的 RPC 并打印出结果。
1 #!/usr/bin/perl 2 # Testing sum() 3 4 use strict; 5 use warnings; 6 use Frontier::Client; 7 8 my $url = ""; 9 my @args = (2,5); 10 11 my $client = Frontier::Client->new( url => $url, 12 debug => 0, 13 ); 14 15 print "$args[0] + $args[1] = ", $client->call('sum', @args), "\\n"; |
这个脚本以始终存在的“shbang”行开始,它指向我安装的 Perl 解释器。第 4 行和第 5 行开启警告。大部分读者将认识 use strict
,但是可能对 use warnings
有些陌生,因为它是最近才加进核心 Perl 分发版中。它和 -w
标记检查相同类型的错误,但是它允许更好地控制错误的报告。第 6 行引进我们的 XML-RPC 库。第 8 行创建一个变量保存监听器 URL,同样也包括端口号。第 9 行是一个简单的参数列表,它将被传送给我们的远程过程。
真正有价值的工作出现在第 11 行,在这里创建了一个新的 XML-RPC 客户端对象。这个对象使用 URL 初始化,尽管还没有建立起网络连接。这个类也提供了一个非常便利的调试工具特征,一旦开启这个特征,它将打印发生在 call()
方法内部的 XML 请求和响应。
谈到 call()
方法,第 15 行展示了 RPC 是如何无缝地适合正常的 Perl 代码的。 call()
的第一个参数是远程过程的名称,后面跟随的是它的参数列表。稍后我们将看到如何传送更复杂的变量,如数组和结构。远程过程的返回值通过 call
转换成标准的 Perl 标量,并打印出来。
您可能已经注意到在 Frontier::RPC
库中 Perl 的按我的意思去做(Do What I Mean,DWIM)态度接口,它知道 5
和 2
是整数。您可以在 XML-RPC 对象中开启调试来校验它。当您强制转换数据类型时, Frontier::RPC
库提供一个面向这个类型强制转换的接口的对象。 清单 2 给出了一段我们如何强制把值 2
作为字符串发送的代码片断。
1 use Frontier::RPC2; 2 3 my $coder = Frontier::RPC2->new; 4 my @args; 5 6 push @args, $coder->string("2"); |
正如您可以看到的,我们需要引入 Frontier::RPC2
类,它是 Frontier::Client
的基类。这个类的实例化的对象能够通过创建数据的对象容器来将值强制转换成所需的数据类型。 call()
方法能够处理这些对象,同时不需要用户进行额外的编程工作。
5
在很多方面,创建 Perl XML-RPC 监听器几乎都会比创建客户端容易一些。 清单 3 可以说明这一点。
1 #!/usr/bin/perl 2 # sum() server 3 4 use strict; 5 use warnings; 6 use Frontier::Daemon; 7 8 my $d = Frontier::Daemon->new( 9 methods => { 10 sum => \\&sum, 11 }, 12 LocalAddr => 'marian.daisypark.net', 13 LocalPort => 1080, 14 ); 15 16 sub sum { 17 my ($arg1, $arg2) = @_; 18 19 return $arg1 + $arg2; 20 } |
我们在前面已经见到了第 1-5 行。这个类是 Frontier::Daemon
,它是 HTTP::Daemon
的子类。当这个类的对象实例化时,方法不返回。相反,程序进入一个 while 循环,等待新的连接。在独具匠心的子类中, HTTP::Daemon
本身派生于 IO::Socket::INET
。这使 Frontier::Daemon
对象可以配置为通过设置 LocalPort
属性来监听任何 TCP 端口。执行这个脚本的 Linux Box 响应很多的 IP 地址。此外,通过使用 IO::Socket::INET
的一个属性,我们能够使用 LocalAddr
属性将监听器限制为只监听特定的 IP。
XML-RPC 监听器的中心是 methods
属性,它指向一个匿名散列,这个匿名散列将 API 过程名映射到将执行必要的工作的实际 Perl 子例程的引用。如前所述, 表 2 中的 API 指出监听器有一个称为 sum()
的过程。在 Perl XML-RPC 库中不需要有一个称为 sum()
的 Perl 函数。 sum()
子例程是以直接的方式实现的。我们可能想要对产品代码执行错误校验。注意到 sum()
处理本地的 Perl 标量,并返回一个 Perl 标量。这个库再次对我们隐藏了转换到 XML 的细节。
6
Eric Raymond 曾经称 XML-RPC “深具 Unix 的精神”(请参阅 参考资料)。它的简单创建了一个比它的老大哥 SOAP 更低的门槛。利用 XML-RPC,您不但创建了一个用于远程用户访问您的应用程序的网关,而且您还给了他们使用任何想用的语言这样做的自由。
在下一篇文章中,我们将研究一个更复杂、更实用的 XML-RPC 示例。很多 Web 站点需要用户使用账号名和密码登录。通常,签发给用户一个会话 ID,以后,用户就能够使用这个 ID 恢复特定应用程序的状态(考虑购物的情形,购物车应用程序需要记住车内都有哪些物品)。下次我们将创建一个 Web 服务,它对前端 PHP 代码隐藏了存储账号信息的后端 MySQL 系统。