在PHP中利用XML技术构造远程服务
未来的Web将是以服务为中心的Web,XML_RPC标准使得编写和应用服务变得非常简单。本文介绍XML_RPC标准及其PHP实现,并通过实例示范了如何在PHP中开发XML_RPC服务和客户程序。
一、服务式Web 从内容提供商所采用的简单方法到UDDI(Universal Description,Discovery and Integration)的未来构想,业界已经有大量关于“服务式Web”的说明和评论。就Web的初创阶段来说,它只是一个文档的集散地,提供的只是一些可浏览的信息。随着Web的发展,在Web上运行服务越来越具有吸引力。未来,Web将成为企业为客户和其他企业提供便捷服务的载体。B2B和B2C模式间的协同就可以看成是一种服务式Web。
一个很重要的问题是,Web上究竟可以提供哪些服务?Web能够提供的服务非常多,其中有些服务现在已经在使用,有些服务在不久的将来就会出现。为了说明问题,下面列出了一小部分可以通过Web提供的服务:
面向主题的垂直搜索引擎。 供用户查找信息的知识库。 用户可以请教问题的专家系统。 银行服务。 新闻和信息出版服务。 数字化支付相关的服务。 图形处理服务。 卫生和健康服务。
那么,企业和组织通过Web提供服务的正确途径是什么呢?这是一个很重要的问题。今天,有些服务提供HTML界面,它们通过文档的形式提供服务,但在服务界面的背后隐藏着什么?在占领Web的竞赛中,Web浏览器并不孤单,移动电话、手持设备以及微波炉之类的设备都想要访问Web、查询数据库、转换数据、提取信息,等等。要实现真正的服务式Web,在表现层(HTML)之下应该还有另外一层。
二、XML_RPC标准 XML或许是近10年来最为重要的标准,XML词汇表(Vocabulary)为企业构造服务环境提供了基石。要构建服务式Web就有必要学习XML_RPC标准,这不仅是因为XML_RPC对于把服务放到Web上很有用,而且因为XML_RPC是一种已经成形的、很容易采用的标准。对于B2B服务来说,提供服务的标准是极其重要的,共同遵循标准的公司可以利用其它公司提供的服务获得快速的增长。无法想象在各种私有的服务标准之上可以建立起真正的服务式Web,服务必须有一种可以遵循的标准。
XML_RPC是一种面向Internet分布式处理的标准。RPC即为Remote Procedure Call(远程过程调用)的缩写,它是一种远程调用机制,用于调用可能驻留在其他机器之上以及可能用其他语言编写的过程。远程过程调用是分布式计算的重要支柱。例如,在一个分布式计算环境中,我们可以寻找和利用在其他机器上运行的执行加法和减法操作的过程,执行加法操作的过程可能用APL编写、在RS6000机器上运行,执行减法操作的过程可能用C编写、在Unix上运行。其他要使用这种分布式计算器的开发者同样可以利用它们,或者他也可以选用另外更好的计算器。
在RPC中,过程(Procedure)是最主要的构件,服务器提供的就是供客户端调用的过程。过程可以接收参数并返回结果。XML_RPC以HTTP作为协议载体,通过发送和接收数据的XML词汇表实现RPC机制。XML_RPC服务器接收XML_RPC请求并返回XML_RPC应答,XML_RPC客户程序发送XML_RPC请求并接收XML_RPC应答。服务器和客户必须按照XML_RPC标准的要求处理应答和请求。
三、XML_RPC协议 完整的XML_RPC规范可以在http://www.xmlrpc.com/spec找到。下面是其要点说明。
3.1 XML_RPC请求
XML_RPC请求应该是HTTP POST请求,它的正文是XML格式。请求的XML部分格式如下:
<?xml version="1.0" ?> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall>
指定数据发送到哪里的URL并未在这里指定。如果服务器专门用来进行RPC处理,它可能是“/”。上述XML文档中的有效载荷是一个“methodCall”结构。methodCall必须包含一个“methodName”子元素,“methodName”子元素包含一个描述待调用方法的字符串。如何解释“methodName”的内容完全由服务器决定,例如它可以是一个执行文件的名字,可以是数据库中记录的名字,或者任何其他东西。如果过程接收参数,“methodCall”可以包含一个“params”元素以及若干个“param”子元素。每一个“param”元素包含一个带有类型描述符的值,类型描述符如下表所示:
标记 说明 <i4>或<int> 四字节的带符号整数,如12 <boolean> 0(false),或1(true) <string> 字符串,如“Hello World” <double> 双精度带符号浮点数,如-12.214 <dateTime.iso8601> 日期/时间,如19980717T14:08:55 <base64> base64编码的二进制数据,如eW91IGbid0IHJlQgdGhpcyE
3.1.1 结构
值可以是一个结构,结构用<struct>元素描述。每个<struct>包含多个<member>,每个<member>包含一个<name>和一个<value>。下面是一个由两个元素构成的结构:
<struct> <member> <name>name</name> <value><string>member1</string></value> </member> <member> <name>member2</name> <value><i4>19</i4></value> </member> </struct>
<struct>可以嵌套,任意<value>可以包含<struct>或者任意其它类型,包括<array>。
3.1.2 数组
值可以是数组类型,数组用<array>元素描述。每个<array>元素包含一个<data>元素,<data>元素里面可以包含任意多个<value>元素。下面是数组元素的一个例子:
<array> <data> <value><boolean>0</boolean></value> <value><i4>9</i4></value> <value><string>Hello</string></value> </data> </array>
<array>元素没有名字。如前例所示,<array>元素的值可以是各种类型。<array>元素可以嵌套,任何<value>都可以包含<array>或者其他类型,如上面介绍的<struct>。
3.2 XML_RPC应答
XML_RPC应答是一个HTTP应答,内容类型是text/xml。应答正文的格式如下:
<?xml version="1.0"?> <methodResponse> <params> <param> <value><string>ABCDEFG</string></value> </param> </params> </methodResponse>
<methodResponse>可能包含一个<params>结构,或者可能包含一个<fault>结构,具体由过程调用是否成功决定。<params>结构与XML请求中的一样,<fault>元素的语法如下:
<fault> <value> <struct> <member> <name>faultCode</name> <value><int>4</int></value> </member> <member> <name>faultString</name> <value><string>Error!</string></value> </member> </struct> </value> </fault>
四、基于XML_RPC的Web服务 利用XML_RPC构造和使用服务是很方便的。企业为自己提供的各种服务部署XML_RPC服务器,用户、客户软件和客户企业就可以使用这种服务构造出高端服务或者面向最终用户的应用。这种提供更有效、廉价和优质服务的竞争将极大地提高应用服务的质量。
但这里还存在一些问题有待解决,例如怎样编目、索引、搜索Web上的服务?UDDI试图解决这个问题,不过这个标准并不简单,而且业界对它的反应也尚未明了。然而,在企业内部应用XML_RPC不仅能够改善代码的可重用性,而且还会带来一种全新的分布式计算模式,在此后的数年中它必将成为一种重要的知识财富。XML_RPC的发展从解决分布式计算问题以及成为服务式Web的基本层面开始,从而获得了一个非常好的开端,其后必将紧随着人们对该标准的热衷。既然如此,现在就让我们来看看XML_RPC的实际应用吧!
4.1 在PHP中应用XML_RPC
对于提供Web服务来说,PHP是一种非常理想的语言。我们只需编写好PHP代码然而把它放到一个合适的位置,就立即有了一个可通过URL“调用”的服务。PHP中的XML_RPC实现可能复杂也可能简单,但我们拥有许多种选择。这里我们选用的是来自Useful Information Company的XML_RPC实现,它的代码和文档可以从http://xmlrpc.usefulinc.com/下载。
这个XML_RPC实现的基本类涉及两个文件:
xmlrpc.inc:包含XML_RPC的php客户端所需要的类
xmlrpcs.inc:包含XML_RPC的php服务器所需要的类
4.2 客户端
编写XML_RPC客户端意味着:
1.创建一个XML_RPC请求消息
2.设置XML_RPC参数
3.创建一个XML_RPC消息
4.发送消息
5.获得应答
6.解释应答
请看下面这个例子:
<?php $f=new xmlrpcmsg('examples.getStateName',array(new xmlrpcval(14, "int"))); $c=new xmlrpc_client("/RPC2", "betty.userland.com", 80); $r=$c->send($f); $v=$r->value(); if (!$r->faultCode()) { print "状态代码". $HTTP_POST_VARS["stateno"] . "是" . $v->scalarval() . "<BR>"; print "<HR>这是服务器的应答<BR><PRE>" . htmlentities($r->serialize()). "</PRE><HR>\n"; } else { print "错误: "; print "代码: " . $r->faultCode() . " 原因: '" .$r->faultString()."'<BR>"; } ?>
在这个例子中,我们先创建了一个调用“examples.getStateName”方法的XML_RPC消息,并传递了一个类型为“int”值为14的整数参数。然后,我们创建了一个描述待调用URL(路径、域和端口)的客户。接着,我们发送了消息,接收应答对象并检查错误。如果不存在错误,我们就显示结果。
编写RPC客户程序时要用到的主要函数如下:
创建客户用:
$client=new xmlrpc_client($server_path, $server_hostname, $server_port);
发送消息的方法是:
$response=$client->send($xmlrpc_message);
它返回的是xmlrpcresp的一个实例。我们所传递的消息是xmlrpcmsg的实例,它用如下方法创建:
$msg=new xmlrpcmsg($methodName, $parameterArray);
methodName是待调用的方法(过程)的名字,parameterArray是xmlrpcval对象的php数组。例如:
$msg=new xmlrpcmsg("examples.getStateName", array(new xmlrpcval(23, "int")));
xmlrpcval对象可以用如下形式创建:
<?php $myVal=new xmlrpcval($stringVal); $myVal=new xmlrpcval($scalarVal, "int" | "boolean" | "string" | "double" | "dateTime.iso8601" | "base64"); $myVal=new xmlrpcval($arrayVal, "array" | "struct"); ?>
第一种形式创建的是xmlrpc字符串值。第二种形式创建的是描述值和类型的值。第三种形式通过在数组之类的结构中组合其他xmlrpc值创建复杂的对象,例如:
<?php $myArray=new xmlrpcval(array(new xmlrpcval("Tom"), new xmlrpcval("Dick"),new xmlrpcval("Harry")), "array"); $myStruct=new xmlrpcval(array( "name" => new xmlrpcval("Tom"), "age" => new xmlrpcval(34, "int"), "geek" => new xmlrpcval(1, "boolean")),"struct"); ?>
应答对象是xmlrpcresp类型,通过调用客户对象的send方法获得。在服务器端,我们可以通过如下方式创建xmlrpcresp类型的对象:
$resp=new xmlrpcresp($xmlrpcval);
而在客户端,则使用如下方法从应答获取xmlrpcval:
$xmlrpcVal=$resp->value();
接下来我们就可以用下面这种方式获取描述应答结果的PHP变量:
$scalarVal=$val->scalarval();
对于复杂的数据类型,有两个函数非常有用,这两个函数都在xmlrpc.inc内:
$arr=xmlrpc_decode($xmlrpc_val);
该函数返回一个PHP数组,其中包含了xmlrpcval变量$xmlrpc_val之内的数据,这些数据已经被转换成PHP本身具有的变量类型。
$xmlrpc_val=xmlrpc_encode($phpval);
该函数返回一个xmlrpcval类型的值,其中包含了$phpval描述的PHP数据。对于数组和结构,此方法能够进行递归分析。注意,这里不存在对非基本数据类型(如base-64数据,或者日期-时间数据)的支持。
4.3 服务器端
利用xmlrpcs.inc提供的类编写服务非常简单。要创建一个服务,我们按照如下方式创建xmlrpc_server的实例:
<?php $s=new xmlrpc_server( array("examples.myFunc" => array("function" => "foo"))); ?>
传递给xmlrpc_server构造函数的是一个联合数组的联合数组。过程“examples.myFunc”调用“foo”函数,由于这个原因foo被称为方法句柄。
编写方法句柄很简单。下面是一个方法句柄的骨架:
<?php function foo ($params) { global $xmlrpcerruser; // 引入用户错误代码值 // $params是一个xmlrpcval对象的数组 if ($err) { // 错误条件 return new xmlrpcresp(0, $xmlrpcerruser+1, // 用户错误1 "Error!"); } else { // 成功 return new xmlrpcresp(new xmlrpcval("Fine!", "string")); } } ?>
可以看到,程序检查了错误,如存在错误则返回错误(从$xmlrpcerruser+1开始);否则如果一切正常,则返回描述操作成功信息的xmlrpcresp。
五、应用实例 在下面这个例子中我们将构造一个服务。对于给定的数值n,服务返回n*2。客户端利用该服务计算5*2的值。
服务器端的代码如下:
<?php include("xmlrpc.inc"); include("xmlrpcs.inc"); function foo ($params) { global $xmlrpcerruser; // 引入用户错误代码值 // $params是xmlrpcval对象的一个数组 $vala=$params->params[0]; $sval=$vala->scalarval(); $ret=$sval*2; return new xmlrpcresp(new xmlrpcval($ret, "int")); } $s=new xmlrpc_server( array("product" => array("function" => "foo"))); ?>
客户端代码如下:
<?php include("xmlrpc.inc"); if ($HTTP_POST_VARS["number"]!="") { $f=new xmlrpcmsg('product',array(new xmlrpcval($HTTP_POST_VARS["number"], "int"))); $c=new xmlrpc_client("/xmlrpc/servfoo.php", "luigi.melpomenia.com.ar", 80); $c->setDebug(0); $r=$c->send($f); $v=$r->value(); if (!$r->faultCode()) { print "Number ". $HTTP_POST_VARS["number"] . " is " . $v->scalarval() . "<BR>"; print "<HR>来自服务器的结果!<BR><PRE>" . htmlentities($r->serialize()). "</PRE><HR>\n"; } else { print "操作失败: "; print "代码: " . $r->faultCode() . " 原因: '" .$r->faultString()."'<BR>"; } } print "<FORM METHOD=\"POST\"> <INPUT NAME=\"number\" VALUE=\"${number}\"> <input type=\"submit\" value=\"go\" name=\"submit\"></FORM><P> 输入一个数值"; ?>
结束语:XML_RPC服务的运作还涉及其他许多基础设施和基础工作,如分布式过程的编目和索引机制,又如在编程语言中处理XML_RPC的更好接口等。有关XML_RPC和服务式Web的报道非常多,让我们密切关注它们的发展吧!
|