19. drupal XML-RPC

   Drupal 可与外部系统进行良好的集成。也就是说,如果存在一个开放的标准,drupal可以通过核心模块或者第3方模块这两种方式来支持这一标准。XML-RPC也不例外:Drupal内置了对它的支持。在本章中,你将学习到如何在drupal中,发送和接受XML-RPC调用。

Drupal版本:

1.什么是XML-RPC

                    译者:老葛, Think in Drupal

     一个远程过程调用(remote procedure call)是指一个程序要求另一个程序执行一个函数。XML-RPC就是远程过程调用的标准,它采用XML格式编码并使用HTTP传送。XML-RPC协议是由UserLand软件公司的Dave Winner与微软合作创造出来的。它是专门用于分布式网络系统之间的相互对话的,比如当一个Drupal网站需要另一个Drupal网站的一些信息时,就可以使用XML-RPC。

 

     当一个XML-RPC发生时,存在着两个参与者。一个是产生请求的网站,作为客户端。另一个是接受请求的,作为服务器端。

Drupal版本:

2.drupal XML-RPC的前置条件

译者:老葛, Think in Drupal

如果你的网站仅仅用作服务器, 这就没有任何担心了,因为传入的XML-RPC请求使用标准的web端口(通常为端口80).在你的Drupal安装目录中的文件xmlrpc.php里面,包含了处理XML-RPC请求的代码。它也被称为XML-RPC终端。

 

注意:部分用户可能会悄悄的对文件xmlrpc.php进行重命名,来修改他们的终端,从而增加安全性。这会阻止恶意的网络爬虫,探测到服务器的XML-RPC接口。如果你的网站不接受XML-RPC请求的话,那么完全可以将其删除。

 

为了让你的Drupal网站能够作为客户端,那么它就必须具有向外发送HTTP请求的能力。一些主机服务提供商出于安全原因禁止了这一能力,这样你的drupal网站发送的HTTP就穿过不了他们的防火墙。

Drupal版本:

XML-RPC 客户端

 

客户端是用来发送请求的的计算机。它向服务器端发送一个标准的HTTP POST请求。这一请求的主体是由XML组成的,并且包含一个简单的名为<methodCall>的标签。在<methodCall>标签内部,嵌套了两个子标签,<methodName>和<params>。让我们通过一个实例来看一下它是如何工作的。
 

 

注意:远程过程被调用时是作为一个方法被引用的。这就是为什么XML编码将远程过程的名字包装在<methodName>标签里的原因。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

XML-RPC 客户端例子:获取时间

在网站http://www.xmlrpc.com上可以看到XML-RPC说明,它同时也带有了一些可用于测试的例子。在我们的第一个例子中,让我们通过XML-RPC来向该站点请求当前时间:

 
 
    在这里你调用了Drupal的xmlrpc()函数,告诉它链接到服务器time.xmlrpc.com,并且路径为RPC2,请求服务器端执行一个名为currentTime.getCurrentTime()的方法。在这一调用中,你没有使用任何参数。Drupal将其转化为一个如下所示的HTTP请求:
 
POST /RPC2 HTTP/1.0
Host: time.xmlrpc.com
User-Agent: Drupal (+http://drupal.org/)
Content-Length: 118
Content-Type: text/xml
 
<?xml version="1.0"?>
<methodCall>
<methodName>currentTime.getCurrentTime</methodName>
<params></params>
</methodCall>
 
服务器端time.xmlrpc.com非常高兴的执行该函数,并为你返回如下所示的响应:
 
HTTP/1.1 200 OK
Connection: close
Content-Length: 183
Content-Type: text/xml
Date: Wed, 23 Apr 2008 16:14:30 GMT
Server: UserLand Frontier/9.0.1-WinNT
 
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value>
<dateTime.iso8601>20080423T09:14:30</dateTime.iso8601>
</value>
</param>
</params>
</methodResponse>
 
当响应返回后,Drupal解析它,将其识别为一个简单的采用ISO8601国际日期格式的值,并将此值分配给变量$time. Drupa不仅返回了ISO8601格式的时间,并且还包括时间的组成部分比如年,月,日,小时,分钟,和秒。具有这些属性的对象被赋值给$time变量,如图19-1所示。
 
19-1. XML-RPC调用的结果,获取了当前时间
 
 在这里有几个要点需要注意:
    你调用一个远程服务器,接着它响应你。
    请求和响应都是通过XML来描述的。
    你使用了xmlrpc()函数,里面包含一个URL和要调用的远程过程的名字。
    返回给你的值将作为一个特定数据类型来标记。
    Drupal自动识别该数据类型并解析响应。
    你仅用一行代码就搞定了一切。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

XML-RPC 客户端例子:获取州名

  让我们尝试一个稍微复杂的例子。它仅仅复杂了一点点,因为你不但发送了你所调用的远程方法的名称,而且还包括了一个参数。UserLand软件在站点betty.userland.com运行了一个web服务:它将50个美国的州以字母顺序排列。所以如果你请求第1个州,它返回Alabama。第50个州为Wyoming。方法的名称为examples.getStateName。让我们向它请求列表中的第3个州:

 
$state_name = xmlrpc('http://betty.userland.com/RPC2', 'examples.getStateName', 3);
 
它将$state_name设置为Arizona.下面是Drupal发送的XML(为了简洁,从这里起我们省略了HTTP头部)
 
<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value>
<int>3</int>
</value>
</param>
</params>
</methodCall>
 
下面是你从betty.userland.com获得的相应:
 
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value>Arizona</value>
</param>
</params>
</methodResponse>
 
注意,Drupal能够自动的识别你传递的参数是一个整数,并在你的请求中以此来对它编码。但是在响应中发生了什么呢?在返回值的周围没有使用任何类型标签。难道不是这种形式么<value><string>Arizona</string></value>?是的,这个也能工作。不过在XML-RPC中,一个没有类型的值将被默认为字符串类型,这样更简洁。
Drupal中,进行一个XML-RPC客户端调用是非常简单的。仅用一行代码:
 
$result = xmlrpc($url, $method, $param_1, $param_2, $param_3...)
 老葛的Drupal培训班  Think in Drupal

Drupal版本:

处理XML-RPC客户端错误

当与远程的服务器打交道时,经常会出错.例如,你可能会遇到语法错误;服务器可能会挂掉;或者网络连接不通.让我们看看Drupal是如何处理这些情况的.

 
网络错误
Drupal使用includes/common.inc中的drupal_http_request()函数来发送HTTP请求,包括XML-RPC请求.在该函数内部,使用PHP函数fsockopen来打开一个套接字,用于连接远程服务器.如果套接字打不开,Drupal将会根据运行的PHP平台,以及在打开套接字时错误的发生点,来设置一个负值的错误代码或者0值错误代码.当获取州名时,假定我们拼错了服务器的名字:
 
$state_name = xmlrpc('http://betty.userland.comm/RPC2', 'examples.getStateName', 3);
if ($error = xmlrpc_error()) {
if ($error->code <= 0) {
$error->message = t('Outgoing HTTP request failed because the socket could
not be opened.');
}
drupal_set_message(t('Could not get state name because the remote site gave
an error: %message (@code).', array(
'%message' => $error->message,
'@code' => $error->code
)
)
);
 
这将生成如下所示的消息:
 
无法获取州名,因为远程网站给出了一个错误: 因为无法打开套接字,所以发出的HTTP请求失败了.( -19891355)。
 

老葛的Drupal培训班  Think in Drupal

Drupal版本:

HTTP错误

老葛的Drupal培训班 Think in Drupal

前面的代码也适用与HTTP错误,比如当服务器不存在,但是该路径上的web服务不存在时.现在,我们向drupal.org请求该服务, drupal.org指出 http://drupal.org/RPC2不存在:

 
$state = xmlrpc('http://drupal.org/RPC2', 'examples.getStateName');
if ($error = xmlrpc_error()) {
if ($error->code <= 0) {
$error->message = t('Outgoing HTTP request failed because the socket could
not be opened.');
}
drupal_set_message(t('Could not get state name because the remote site gave
an error: %message (@code).', array(
'%message' => $error->message,
'@code' => $error->code
)
)
);
 
这将生成如下所示的消息:
 
无法获取州名,因为远程网站给出了一个错误: 未找到 (404).
 

Drupal版本:

调用语法错误

如果你成功的连接到了服务器,尝试从betty.userland.com获取州名,但是却忘记提供州号了,而这个又是必须的参数:

 
$state_name = xmlrpc('http://betty.userland.com/RPC2', 'examples.getStateName');
 
远程服务器返回以下结果:
 
<?xml version="1.0"?>
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value>
<int>4</int>
</value>
</member>
<member>
<name>faultString</name>
<value>
<string>Can't call "getStateName" because there aren't enough
parameters.</string>
</value>
</member>
</struct>
</value>
</fault>
</methodResponse>
 
    与服务器的连接是好的;前面的代码返回的HTTP响应为200 OK.在XML响应中, faultCode用来标记错误,并使用一个字符串来描述错误.你的错误处理代码应该与前面一样:
 
$state_name = xmlrpc('http://betty.userland.com/RPC2', 'examples.getStateName');
if ($error = xmlrpc_error()) {
if ($error->code <= 0) {
$error->message = t('Outgoing HTTP request failed because the socket could
not be opened.');
}
drupal_set_message(t('Could not get state name because the remote site gave
an error: %message (@code).', array(
'%message' => $error->message,
'@code' => $error->code
)
)
);
 
这将为用户生成如下所示的消息
 
无法获取州名,因为远程网站给出了一个错误:由于参数不够,导致无法调用"getStateName". (4)
 
注意当你报告错误的时候,你应该指出3件事情:你想要做什么,为什么你不能完成它,和其它一些你可以获取到的额外信息。通常一个友好的错误信息将通过drupal_set_message()来显示给用户,同时一个更加详细的错误信息将会写到watchdog中去并可通过“管理➤报告➤最近日志条目”来查看。
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

参数类型转换

老葛的Drupal培训班 Think in Drupal

通常你所调用的远程过程要求参数必须是特定的XML-RPC类型,比如整形(integers)或者数组。一种保证这样的方式是使用PHP的类型转换来发送你的参数:
 
$state_name = xmlrpc('http://betty.userland.com/RPC2', 'examples.getStateName',
(int) $state_num);
 
一种更好的方式是保证在你代码的其他地方当给变量赋值时,这一变量已经被设置为相应的类型了。
 

Drupal版本:

一个简单的XML-RPC服务器

正如在XML-RPC客户端例子中所看到的,Drupal为你做了大部分工作。现在让我们看一个简单的服务器端的例子。你需要做3件事来建立你的服务器:

 
1, 定义一个当客户请求到来时你想执行的函数。
2, 将函数映射为一个公共的方法名。
3, 可选的定义一个方法签名。
 
    按照Drupal的惯例,你想将你的代码与系统核心分开并将其作为一个模块插入。下面是一个简单的模块,用来通过XML-RPC说”你好”.创建文件sites/all/modules/custom/remotehello/remotehello.info:
 
; $Id$
name = Remote Hello
description = Greets XML-RPC clients by name.
package = Pro Drupal Development
core = 6.x
 
    下面是 remotehello.module:
 
<?php
// $Id$
 
/**
 * Implementation of hook_xmlrpc().
 * Map external names of XML-RPC methods to PHP callback functions.
 */
function remotehello_xmlrpc() {
$methods['remoteHello.hello'] = 'xmls_remotehello_hello';
return $methods;
}
 
/**
 * Greet a user.
 */
function xmls_remotehello_hello($name) {
if (!$name) {
return xmlrpc_error(1, t('I cannot greet you by name if you do not
provide one.'));
}
return t('Hello, @name!', array('@name' => $name));
}
 老葛的Drupal培训班  Think in Drupal

Drupal版本:

使用hook_xmlrpc()映射你的方法

xmlrpc钩子描述了由模块所提供的外部XML-RPC方法。在我们的例子中,我们仅提供了一个方法。,所以这里,方法名字为:remoteHello.hello。这是请求者使用的名字,它是任意的。一个好的实践是使用“.“分割的字符串,使用你的模块名作为前半部分,使用一个描述性的动词作为后半部分。

 
 
注意:尽管通常在Drupal里面避免使用骆驼形式的字符串,但在XML-RPC方法名中这是个例外。
 
  数组的第2部分,是对remoteHello.hello的请求到来时,所要调用的函数的名称。在我们的例子中,我们将这一函数叫做xmls_remotehello_hello ()。当你开发模块时,你将写很多的函数。通过在函数名字中包含”xmls“(XML-RPC Server的简写), 这样你就可以一眼看出这个函数是与外界交互的。类似的,你可以在函数中使用”xmlc“,用以调用其他网站上的方法。当你写一个主要调用自身的模块时,这是个很好的实践,尽管在另一个网站上,否则的话,调试时你会非常困惑。
  当你的模块认定一个错误发生时,使用xmlrpc_error()来定义一个错误代码和一个用以描述哪里出错的帮助字符串,以显示给客户。数字错误代码是任意的,并且是应用相关的。
  假定带有这一个模块的站点位于example.com上,现在你可以在一个单独的Drupal安装上(比如说,在example2.com)使用以下代码来来发送你的名字:
 
$method_name = 'remoteHello.hello';
$name = t('Joe');
$result = xmlrpc($url, $method_name, $name);
 
$result现在是 "Hello, Joe."
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

在hook_xmlrpc()中进行自动的参数类型验证

    xmlrpc钩子有两种形式。简单的形式,如例子remotehello.module中所展示的,它简单的将一个外部的方法名映射到一个函数上。在一个更高级的形式中,它描述了方法的方法签名;这里指的是,它返回的是什么XML-RPC类型,以及每一个参数的类型(参看http://www.xmlrpc.com/spec来查看类型列表).下面是remotehello.module的升级版,xmlrpc钩子的形式更复杂一些:

 
function xmlrpclucky_xmlrpc() {
return array(
array(
'xmlrpclucky.guessLuckyNumber', // External method name.
'xmlrpclucky_lucky_number', // Drupal function to run.
array('string', 'int'), // Return value's type, then any parameter types
t('Returns a lucky number.') // Description.
)
);
}
 
19-2显示了当一个请求从XML-RPC客户端到达我们的模块时XML-RPC的请求生命周期。如果你在你的模块中使用更复杂的形式来实现xmlrpc钩子,你将得到多个好处。首先,Drupal将根据方法签名自动验证发送过来的类型并返回 -32602:Server error。如果验证失败将标示出无效的方法参数。(这还意味着你的函数具有挑选能力.而不再进行类型转换了,如果是整数3,那么就不能当作字符串“3”使用了).如果你使用xmlrpc钩子的复杂形式, Drupal内置的XML-RPC方法system.methodSignature和system.methodHelp将返回你方法的相关信息.注意,你在你的xmlrpc钩子实现中提供的描述,将会在system.methodHelp方法中作为帮助信息返回,所以你需要写一个有用的描述.
19-2 一个XML-RPC请求的处理流程图
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal内置的XML-RPC方法

Drupal自带了多个XML-RPC方法.在下面的部分中,将讲解这些内置方法.

 
system.listMethods
system.listMethods方法列出了有哪些XML-RPC方法可用.当查询一个Drupal站点提供了哪些方法时,该站点所给出的响应.
 
// Get an array of all the XML-RPC methods available on this server.
$methods = xmlrpc($url, 'system.listMethods');
 
服务器的响应如下:
 
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value>
<array>
<data>
<value>
<string>system.multicall</string>
</value>
<value>
<string>system.methodSignature</string>
</value>
<value>
<string>system.getCapabilities</string>
</value>
<value>
<string>system.listMethods</string>
</value>
<value>
<string>system.methodHelp</string>
</value>
<value>
<string>remoteHello.hello</string>
</value>
</data>
</array>
</value>
</param>
</params>
</methodResponse>
 
$methods的内容现在是一个数组,里面包含了服务器上可用的方法名字:
('system.multicall', 'system.methodSignature', 'system.getCapabilities',
'system.listMethods', 'system.methodHelp', 'remoteHello.hello').
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

system.methodSignature

老葛的Drupal培训班 Think in Drupal

system.methodSignature

这个内置的Drupal XML-RPC方法返回一个数据类型的数组.列表中的第一个是函数返回值的数据类型;接着是给定方法所需的任意参数.例如, remoteHello.hello方法返回了一个字符串,并期望一个参数:一个包含了客户端名称的字符串.让我们调用system.methodSignature来看看Drupal是不是这样的.
 
// Get the method signature for our example method.
$signature = xmlrpc($url, 'system.methodSignature', 'remoteHello.hello');
 
果然, $signature的值变成了一个数组:('string', 'string').
 
system.methodHelp
这个内置的Drupal XML-RPC方法,返回xmlrpc钩子中定义的方法的描述.
 
// Get the help string for our example method.
$help = xmlrpc($url, 'system.methodHelp', 'remoteHello.hello');
 
$help的值现在是一个字符串: Greets XML-RPC clients by name.
 
system.getCapabilities
这个内置的Drupal XML-RPC方法描述了Drupal的XML-RPC服务器能力,这里根据实现的规格进行描述. Drupal实现了以下规格:
 
xmlrpc:
specVersion 1
 
faults_interop:
specVersion 20010516
 
system.multicall
specVerson 1
 
introspection
specVersion 1
 
system.multiCall
其它值得一提的内置方法就是system.multiCall了,它允许在一个HTTP请求中调用多个XML-RPC方法。关于这一个规定的更多信息(它没有包含在XML-RPC说明中),参看以下URL(注意这是一个字符串):http://web.archive.org/web/20060502175739/http:// www.xmlrpc.com/discuss/msgReader$1208

Drupal版本:

总结

当读完这一章后,你应该可以:

    能够从一个Drupal站点上发送XML-RPC请求到一个不同的服务器上
    能够实现一个基本的XML-RPC服务器
    理解Drupal是如何将XML-RPC方法映射到php函数上的
    能够实现简单的和复杂的xmlrpc钩子
    了解Drupal内置的XML-RPC方法
老葛的Drupal培训班 Think in Drupal
 

Drupal版本: