您的位置:首页 > 运维架构 > Apache

XML-RPC 的 Apache 实现

2013-04-13 01:34 309 查看
简介: 在新技术、新概念甚至新思维层见叠出的 IT 行业 , XML-RPC 绝对不是最新的热门技术,但它自从诞生时起,一直在 IT 行业占有一席之地。XML-RPC 具有简单、高效且易于实现等优点,它一直是中小型应用实现分布式计算的最佳选择之一。本文将会首先介绍一下 XML-RPC 协议规范,然后介绍如何实现 Apache XML-RPC 开发客户端及服务端 Java 代码。最后对
Apache XML-RPC 的高级特性进行了介绍。

XML-RPC 规范及 Java 实现
在新技术、新概念甚至新思维层见叠出的 IT 行业,XML-RPC 绝对不是最新的热门技术,但它自诞生之日起,一直在 IT 行业占有一席之地。XML-RPC 具有简单且易于实现,可以高效跨越不同的软硬件平台上应用系统等优点。相对于庞大、复杂的分布式计算机制,它一直是中小型应用的最佳选择之一。比如,流行于软件研发团队的任务计划管理系统 JIRA、开源的测试用例管理工具 TestLink 等都提供了开放的 XML-RPC 接口,供用户定制开发时进行调用。
XML-RPC(XML-based Remote Procedure Call,基于 XML 的远程过程调用)是一种工作在互联网上的远程过程调用协议。一个 XML-RPC 请求消息就是一个 HTTP-POST 请求消息,其请求消息主体基于 XML 格式。客户端发送 XML-RPC 请求消息到服务端,调用服务端的远程方法并在服务端上运行远程方法。远程方法执行完毕后,返回响应消息给客户端,其响应消息主体同样基于 XML 格式。远程方法的参数支持数字,字符串、日期等;也支持列表数组和其他复杂结构类型。
主流的开发语言都提供了 XML-RPC 协议的客户端程序和服务端程序的实现,这些语言包括 Java、Ruby、Python、C/C++ 、Perl 和 PHP 等。本文选择以 XML-RPC 的 Java 实现 Apache XML-RPC 为例,来研究 XML-RPC 协议规范。Apache XML-RPC 完全兼容 XML-RPC 协议规范,同时也提供了一些扩展特性,使其还具有下述能力:

支持 Java 的全部基本数据类型,如 long、byte、short,以及 double

支持 Calendar 对象

支持支持 DOM 节点、JAXB 对象实例能通过 XML-RPC 进行传输。事实上,任何实现 java.io.Serializable 接口的对象都可以被传输

客户端和服务端都可以通过配置在流模式(stream mode)下进行操作,这样比基于大字节数组的默认模式节省资源

下面就以实例来学习 XML-RPC 的请求和响应消息的格式。本文以当前工作使用的 Window 机器为客户端,IP 为 192.168.1.105。服务端为 Ubuntu 机器,IP 为 192.168.1.126。
步骤 1. 下载本文附带的源代码文件,内含三个 Eclipse Java 项目,请把他们导入到 Eclipse 工作区间:

XML-RPC-Client XML-RPC 客户端实现的例子

XML-RPC-EmbeddedWebServer 嵌入式服务端端实现的例子,可以直接运行

XML-RPC-Server Servlet 服务端端实现的例子 , 需要部署在 Servlet 容器,比如 Tomcat、Jetty 等

步骤 2. 右键选择 XML-RPC-Server 项目,导出为 WAR 文件,命名为 xmlrpc.war。把它部署在服务端的 Servlet 容器上,本文以部署在 Ubuntu 12.04 上的 Tomcat 7.0 为例,复制 xmlrpc.war 到 Tomcat 安装根目录下的 webapps 文件夹下。启动 Tomcat 服务器,使用浏览器访问 http://192.168.1.126:8080/xmlrpc/xmlrpc, 如能正常显示,则表明在服务端上部署成功。
步骤 3,下载安装 SmartSniff(下载地址见参考资源部分)。SmartSniff 用于捕获客户端与服务端的 XML-RPC 消息交互数据包。运行该软件,在菜单 Options-Capture Filter 与 Display Filter 里设置捕获过滤、展示过滤:include:remote:tcpudp: 192.168.1.126:8080,这样只显示我们感兴趣的协议数据。点击 Start Capture 按钮,启动捕获监听。注意:SmartSniff 只能运行在 Window
机器上,并且只能捕获不同的机器之间的 TCP/IP 网络数据交互。如果 XML-RPC 客户端与服务端运行在相同的机器上,无法使用该工具捕获协议交互数据。
步骤 4,运行客户端的单元测试类,研究 XML-RPC 消息格式。在 Eclipse 项目 XML-RPC-Client 中,打开单元测试类 junit.TestClientServletWebServer.java。选择
testXMLRPC()方法
,以 JUnit Test 方式运行。可以发现 JUnit 单元测试可以运行成功,SmartSniff
捕获的协议见图 1。使用相同的方法,可以运行更多的测试类,研究更多的 XML-RPC 消息的格式。

图 1. 捕获 XML-RPC 协议消息



XML-RPC 请求消息格式
清单 1 是捕获的请求消息,可以看出和普通的 HTTP 请求一样,包含如下消息头:POST、Content-Type、User-Agent、Authorization、Host 与 Content-Length 等。XML 格式的消息体包含一个 methodCall 元素来指定远程方法的信息。MethodName 指定远程方法名称,params/param/value 等标签元素指定远程方法需要的参数列表。xmlns:ex 名称空间是 Apache XML-RPC 对 XML-RPC
协议的扩展,后续会详细介绍。

清单 1. 请求消息例子

POST /xmlrpc/xmlrpc HTTP/1.1
Content-Type: text/xml
User-Agent: Apache XML RPC 3.1.3 (Jakarta Commons httpclient Transport)
Authorization: Basic Zm9vOmJhcg==
Host: 192.168.1.126:8080
Content-Length: 260
<?xml version="1.0" encoding="UTF-8"?>
<methodCall xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
<methodName>Calculator.add</methodName>
<params>
<param>
<value>
<i4>2</i4>
</value>
</param>
<param>
<value>
<i4>3</i4>
</value>
</param>
</params>
</methodCall>

XML-RPC 响应消息格式
清单 2 是捕获的响应消息。消息体同样是 XML 格式的,它包含一个 methodResponse 元素来指定远程方法的响应内容。methodResponse 元素下面可以包含一个 params 元素,用于包装返回的数据内容。也可以包含 <fault> 元素,用于返回遇到的异常消息。XML-RPC 响应消息必须有返回值,因此 <fault> 和 <params> 必须出现一个且只能出现一个。

清单 2. 响应消息例子

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/xml
Content-Length: 189
Date: Thu, 18 Oct 2012 02:23:04 GMT
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse
xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
<params>
<param>
<value>
<i4>5</i4>
</value>
</param>
</params>
</methodResponse>

在本节,我们研究了 XML-RPC 协议规范及其消息内容格式。下面继续介绍的是,如何使用 Apache XML-RPC 开发客户端和服务端 Java 代码。

回页首
如何开发客户端代码
在 Eclipse 项目 XML-RPC-Client 中,打开单元测试类 junit.TestClientServletWebServer.java。我们研究一下客户端的代码实现,见清单 3:

清单 3. 客户端代码实现

import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
……
// 创建客户端实例
XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
config.setServerURL(new URL("http://192.168.1.126:8080/xmlrpc/xmlrpc"));
XmlRpcClient client = new XmlRpcClient();
client.setConfig(config);
// 设置传输工厂类
client.setTransportFactory(new XmlRpcSunHttpTransportFactory(client));
// 创建远程方法的参数数组,通过指定远程方法名称进行调用
Object[] params = new Object[]{new Integer(33), new Integer(9)};
Integer result = (Integer) client.execute("Calculator.add", params);

上面的代码首先实例化一个客户端配置对象 XmlRpcClientConfigImpl config,为该实例指定服务端 URL 属性。接着创建 XmlRpcClient 对象,为其指定配置对象,然后为其设置一个 Sun HTTP 传输工厂对象,这个属于默认的传输工厂对象,可以忽略该行语句。最后创建参数数组,并调用服务端的方法 Calculator.add(后文会介绍如何查询服务端可以提供的方法列表)。
我们可以选择不同的传输工厂对象,比如可以不使用默认的 Sun HTTP 传输工厂对象,而是使用基于 Jakarta Commons HttpClient 的传输工厂。使用不同的传输工厂, XML-RPC 请求消息头 User-Agent 部分可以看出差异。使用 SmartSniff 捕获到的请求消息头,如下图所示。

图 2. 使用不同传输工厂的 XML-RPC 请求消息



XmlRpcClient 是一个无状态的线程安全的对象,实例化 client 对象时,需要设置下面的对象:

表 1. 配置 XmlRpcClient

名称描述
ClientConfig该对象是 XmlRpcClientConfig 的实例,包括如下原子属性,比如服务端 URL,认证信息,编码字符集等
TransportFactory该工厂类用于创建负责使用客户端配置与服务端通讯的对象。下文会介绍常用的传输工厂类。
XmlWriterFactoryXmlWriter 用于创建 XML
传输工厂类 TransportFactory 决定客户端与服务端通讯的方式,Apache XML-RPC 支持如下几种工程类:

表 2. 传输工厂类

工厂类名描述
XmlRpcSunHttpTransportFactory默认的工程类 , 使用 java.net.HttpURLConnection 与服务端连接
XmlRpcCommonsTransportFactory该工厂类使用 Jakarta Commons HttpClient 与 HTTP 服务端通讯 . 该工厂允许直接访问结果文档,可以降低内存资源的使用。
XmlRpcLiteHttpTransportFactory该工厂类基于内置的 HTTP 客户端,速度很快,但不支持

HTTP/1.1, 不能使用 keepalive 连接。
XmlRpcLocalTransportFactory该工厂类拥有内置的 XML-RPC 服务器,可以直接使用 Java 调用,主要用于调试与开发环境。
根据传输工厂的不同,客户端配置分为如下两种类型:

org.apache.xmlrpc.client.XmlRpcHttpClientConfig

用于 HTTP 传输工厂,即上文提到的 XmlRpcSunHttpTransportFactory、XmlRpcCommonsTransportFactory 和 XmlRpcLiteHttpTransportFactory。HTTP 传输工厂对应的客户端配置实例支持以下属性设置 basicUserName、basicPassword、basicEncoding、contentLengthOptional、enabledForExceptions、enabledForExtensions、Encoding、gzipCompressing
和 gzipRequesting。关于这些属性的详细描述,请参考 Apache XMl-RPC 官方站点。

org.apache.xmlrpc.client.XmlRpcLocalClientConfig

用于本地传输工厂,即上文提到的 XmlRpcLocalTransportFactory。本地传输工厂对应的客户端配置实例支持配置属性 xmlRpcServer。作为内置的 XML-RPC 客户端,模拟来自客户端的请求,用于调试与开发。

org.apache.xmlrpc.client.XmlRpcClientConfigImpl 实现了上述两个接口,为方便计,不管使用什么传输工厂类,客户端配置都可以使用实现类 XmlRpcClientConfigImpl。

回页首
如何开发服务端代码
相对于 XmlRpcClient,服务端的 XmlRpcServer 对象用于接收并执行来自客户端的 XML-RPC 调用,该对象可以嵌入 Servlet 容器(Tomcat,Jetty 等),或其他的 HTTP 服务器。在使用本地传输工厂类的情况下,XML-RPC 被集成到客户端应用内。和 XmlRpcClient 类似,XmlRpcServer 也需要制定一个服务端配置对象 XmlRpcServerConfigImpl。该配置对象支持的属性如下:

表 3. 服务端配置属性

属性名称描述
enabledForExceptions在启用该属性时,如果服务端捕获异常,服务端会把异常转换成字节数组,

并返回客户端。
enabledForExtensions指定是否启用 Apache 对 XML-RPC 的扩展。
下面,我们就来研究一下如果使用 XML-RPC Servlet 来开发服务端的代码。
首先,创建一个业务逻辑类,供客户端调用。见清单 4。需要注意:能被客户端调用的方法必须是 public 的,返回值不是 void 的、实例方法。像构造方法、存取方法的 setter 方法和静态方法都不能被远程调用。任何抽象类、接口类,不能被实例化,它们的方法也不能被远程调用。

清单 4. Calculator 业务逻辑类

package org.apache.xmlrpc.demo;
public class Calculator {
public int add(int i1, int i2) {
return i1 + i2;
}
public int subtract(int i1, int i2) {
return i1 - i2;
}
}

接着,在源文件目录 org/apache/xmlrpc/webserver/ 下创建属性文件 XmlRpcServlet.properties。XmlRpcServlet 默认在上述包路径下寻找属性文件。在文件中添加如下条目(清单 5)。左侧为一标识符,右侧为业务逻辑类的全限定类名。

清单 5. 句柄属性文件

Calculator=org.apache.xmlrpc.demo.Calculator

然后,在 Web 应用的部署描述文件 web.xml 添加如下内容(清单 6):

清单 6. 部署描述符配置 XmlRpcServlet

<servlet>
<servlet-name>XmlRpcServlet</servlet-name>
<servlet-class>org.apache.xmlrpc.webserver.XmlRpcServlet</servlet-class>
<init-param>
<param-name>enabledForExtensions</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>XmlRpcServlet</servlet-name>
<url-pattern>/xmlrpc</url-pattern>
</servlet-mapping>

现在已经完成了服务端的代码开发,可以把它打成 war 包,部署在 servlet 容器内。启动服务端后,就可以运行客户端代码对服务端上提供的远程方法进行调用。关于如何运行,请详细参考本文附带的源代码项目。
另外,为了给服务端上提供的 XML-RPC 接口提供安全保障,可以为服务端启用认证机制。Apache XML-RPC 使用 XmlRpcHandler 处理认证,它以 XmlRpcRequest 作为参数。XmlRpcRequest 的 getConfig() 方法可以返回 XmlRpcRequestConfig 对象。使用 XmlRpcRequestConfig 的方法 getBasicUserName() 和 getBasicPassword() 可以获得客户端的认证信息。获取客户端的认证信息后,就可以采取适当的逻辑去验证。
首先,实现 AuthenticationHandler 接口,编写处理认证信息的逻辑代码,见清单 7。

清单 7. 认证处理器程序

public class myAuthenticationHandler implements AuthenticationHandler {

@Override
public boolean isAuthorized(XmlRpcRequest req) throws XmlRpcException {
XmlRpcHttpRequestConfig config = (XmlRpcHttpRequestConfig) req.getConfig();
return isAuthenticated(config.getBasicUserName(),
config.getBasicPassword());
}
private boolean isAuthenticated(String pUserName, String pPassword) {
return "foo".equals(pUserName) && "bar".equals(pPassword);
}
}

在自己定义的 XmlRpcServlet 类中使用 AuthenticationHandler,见清单 8。并在应用的部署描述文件中,修改 servlet-class,使用定制的 Servlet 类 MyServlet。

清单 8. 自定义的 XmlRpcServlet

public class MyServlet extends XmlRpcServlet {
private static final long serialVersionUID = 4499604583805027974L;
protected XmlRpcHandlerMapping newXmlRpcHandlerMapping()
throws XmlRpcException {
PropertyHandlerMapping mapping = (PropertyHandlerMapping) super
.newXmlRpcHandlerMapping();
AuthenticationHandler handler = new myAuthenticationHandler();
mapping.setAuthenticationHandler(handler);
//XmlRpcSystemImpl.addSystemHandler(mapping);
return mapping;
}

重启 Servlet 容器,刚才的单元测试类必须添加如下用户认证信息(清单 9)才能执行成功。

清单 9. XML-RPC 客户端提供认证信息

config.setBasicUserName("foo");
config.setBasicPassword("bar");

至此,我们完成了使用 Apache XML-RPC 开发客户端和服务端代码,部署到了 Servlet 容器 Tomcat 上,并掌握了如何运行客户端代码调用服务端的远程方法。下面对 Apache XML-RPC 的高级特性进行介绍。除了部署在 Servlet 容器里,Apache XML-RPC 还提供了嵌入式 WebServer。XML-RPC 动态代理技术带来了良好的编程体验。通过启用扩展,Apache XML-RPC 支持了更多的 Java 数据类型。还对内省机制进行了简单介绍。

回页首
嵌入式 WebServer 实现
嵌入式 WebServer,更加便携,适用于为非 Web 应用提供远程方法。WebServer 实现代码见如下清单 10。

清单 10. 嵌入式 WebServer 代码

WebServer webServer = new WebServer(port);
XmlRpcServer xmlRpcServer = webServer.getXmlRpcServer();
PropertyHandlerMapping phm = new PropertyHandlerMapping();
/* 从属性文件加载
*/
phm.load(Thread.currentThread().getContextClassLoader(),
"MyHandlers.properties");
/* 直接使用 PropertyHandlerMapping 的 addHandler() 方法进行加载。
* 推荐使用属性文件,避免硬编码
*/
phm.addHandler("Calculator",dw.xmlrpc.Calculator.class);
XmlRpcServerConfigImpl serverConfig =
(XmlRpcServerConfigImpl) xmlRpcServer.getConfig();

serverConfig.setEnabledForExtensions(true);
serverConfig.setEnabledForExceptions(true);
serverConfig.setContentLengthOptional(false);
webServer.start();

Apache XML-RPC 推荐使用 WebServer 的子类 ServletWebServer。ServletWebServer 支持 Servlet API,方便日后迁移到 Servlet 容器上。使用该子类需要把 servlet-api.jar 加入到构建路径中,该 jar 文件可以在 Servlet 容器中得到。本文演示的 ServletServer.java 代码片段如清单 11 所示。

清单 11. 嵌入式 ServletServer 实现

XmlRpcServlet servlet = new XmlRpcServlet();
ServletWebServer webServer = new ServletWebServer(servlet, port);
XmlRpcServer xmlRpcServer=  servlet.getXmlRpcServletServer();

webServer.start();
XmlRpcServerConfigImpl serverConfig =
(XmlRpcServerConfigImpl) xmlRpcServer.getConfig();
serverConfig.setEnabledForExtensions(true);
serverConfig.setEnabledForExceptions(true);
serverConfig.setContentLengthOptional(false);
PropertyHandlerMapping phm=(PropertyHandlerMapping)
xmlRpcServer.getHandlerMapping();
String[] ss=phm.getListMethods();
System.out.println("打印服务端支持的远程调用方法:");
for(String s:ss){
System.err.println(s);
}

可以在 Eclipse 选中包 dw.webserver 中 Server.java 或 ServletServer.java,右键弹出上下文菜单,选择 Export 导出菜单,在接下来的向导中选择 Runnable Jar file,并将依赖的 Jar 文件导出到单独的文件夹中,导出的可运行的 Jar 命名为 XMLRPC.jar。通过下面的命令,启动嵌入式 WebServer:java -cp XMLRPC_lib -jar XMLRPC.jar。
在客户端调用嵌入式服务端的远程方法和调用部署在 Servlet 容器上的服务端的远程方法一样,只是前者为 config 对象指定 ServerURL 的时候只需要指定 IP 和端口即可,服务的名称不是必须的,如 config.setServerURL(new RL("http://127.0.0.1:7071"))。

回页首
XML-RPC 动态代理技术
在客户端编写代码调用服务端远程方法时,需要为 client.execute() 提供两个参数。第一个参数为字符串 String 类型,即远程方法的名称;第二个参数是数组类型,即远程方法的参数列表。这种编程体验不够友好,很容易因为记忆、编写错误而导致问题。动态代理技术可以提供一种舒服的客户端编程方式。工作方式如下:把服务端的远程方法都抽象提炼到相应的接口文件中,接口文件在客户端、服务端都存在。在客户端以 XmlRpcClient 为参数创建 ClientFactory 的一个实例。ClientFactory
以抽象接口为参数,返回服务端对应的实现类的实例,然后使用该实例调用服务端的远程方法。
首先,编写接口文件 Adder.java,该接口在服务端和客户端都存在。接着,在服务端编写实现类 AdderImpl.java,实现上述接口类。见清单 12。

清单 12. 动态代理接口及其实现类

package dw.xmlrpc;
public interface Adder {
public int add(int pNum1, int pNum2);
}

package dw.xmlrpc;
public class AdderImpl implements Adder {
public int add(int pNum1, int pNum2) {
return pNum1 + pNum2;
}
}

然后,修改句柄属性配置文件,注意左侧必须为接口类的全限定名称(清单 13)。

清单 13. 属性配置文件

xmlrpc.Adder=dw.xmlrpc.AdderImpl

最后,重启服务端,使修改生效。使用动态代理技术编写客户端调用服务端的方法(清单 14)。可以看出使用这种方式,客户端代码可读性增强,编写更加容易。

清单 14. 动态代理客户端测试类

@Test
public void testDynamicProxy() {
ClientFactory factory = new ClientFactory(client);
Adder adder = (Adder) factory.newInstance(Adder.class);
int sum = adder.add(2, 4);
org.junit.Assert.assertEquals("期望返回整数 6", 6, sum);
System.out.println("2 + 4 = " + sum);
}

回页首
XML-RPC 支持的数据类型
XML-RPC 请求消息体和响应消息体中 Value 元素的下级子元素用于指定数据类型。数据类型元素包含实际的文本数据。XML-RPC 协议规范支持八种数据类型,Apache XML-RPC 都有相应的 Java 数据类型与其对应。如果 XML-RPC 的客户端和服务端都基于 Apache XML-RPC 实现,并且启用了扩展(enabledForExtensions 属性设置为 true),还可以额外支持更多的 Java 数据类型。通过启用扩展,XML-RPC 支持 Java 语言的全部基本数据类型,日历对象,序列化对象等。
下面将要演示 Apache XML-RPC 如何支持扩展的序列化对象。首先,创建一个序列化 Java Bean,其中包含一个可供远程调用的方法 setAndGetBean(), 其参数与返回值均为一个可序列化的对象 SerializableBean。见清单 15。

清单 15. Java 序列化类

public class SerializableBean implements Serializable {
private static final long serialVersionUID = 3787840155837070188L;
private String str;

public SerializableBean setAndGetBean(SerializableBean bean) {
this.str = bean.getStr()+"--> Server!";
bean.setStr(str);
return bean;
}
String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "SerializableBean [str=" + str + "]";
}
}

接着,在句柄属性配置文件添加一行 serializable=dw.xmlrpc.SerializableBean,并重启服务端(Servlet 容器或者 WebServer 进程)。
最后,编写客户端调用远程方法,代码清单 16 如下:

清单 16. 序列化测试类

@Test
public void testSerializable() {
SerializableBean bean = new SerializableBean();
bean.setStr("Client!");
params = new Object[] { bean };
try {
result = (SerializableBean) client.execute(
"serializable.setAndGetBean", params);
System.err.println(result.toString());
} catch (XmlRpcException e) {
e.printStackTrace();
org.junit.Assert.fail(e.getMessage());
}
org.junit.Assert.assertNotNull("返回结果不为 Null", result);
}

运行客户端的时候,使用 SmartSniff 进行监控协议数据。观察捕获的协议数据,可以看到扩展的标签使用了 ex 前缀,其名称空间 URL 为 http://ws.apache.org/xmlrpc/namespaces/extensions。请求消息、响应消息数据见清单
17。

清单 17. 请求消息数据与响应消息数据

请求消息:
POST /xmlrpc/xmlrpc HTTP/1.1
Content-Type: text/xml
User-Agent: Apache XML RPC 3.1.3 (Jakarta Commons httpclient Transport)
Authorization: Basic Zm9vOmJhcg==
Host: 192.168.1.126:8080
Content-Length: 369
<?xml version="1.0" encoding="UTF-8"?>
<methodCall xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
<methodName>serializable.setAndGetBean</methodName>
<params>
<param>
<value><ex:serializable>rO0ABXNyABpkdy54bWxycGMuU2VyaWFsaXphYmxlQmVhb
jSRHI2KoPtsAgABTAADc3RydAASTGphdmEvbGFuZy9TdHJpbmc7eHB0AAdDbGllbnQh
</ex:serializable>
</value>
</param>
</params>
</methodCall>
响应消息:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/xml
Content-Length: 342
Date: Thu, 18 Oct 2012 02:45:26 GMT
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
<params>
<param>
<value><ex:serializable>rO0ABXNyABpkdy54bWxycGMuU2VyaWFsaXphYmxlQmVhbj
SRHI2KoPtsAgABTAADc3RydAASTGphdmEvbGFuZy9TdHJpbmc7eHB0ABJDbGllbnQhLS0+IFNlcnZlciE=
</ex:serializable>
</value>
</param>
</params>
</methodResponse>

回页首
XML-RPC 内省机制
正如上文所示,客户端通过调用像 Calculator.add 这样的远程方法来实现分布式计算。但是客户端如何知道服务端可以提供什么样的方法?客户端怎么样调用远程方法,方法的签名是什么?如何获取关于远程方法的帮助信息? XML-RPC 内省机制(introspection)可以回答这些问题,客户端只需要调用服务端的"system.listMethods", "system.methodSignature" 和 "system.methodHelp"就能得到答案。Apache XMl-RPC
支持这种内省特性,只需要在服务端进行简单的配置即可。对于部署在 Servlet 容器上的应用,需要继承 XmlRpcServlet,获取 PropertyHandlerMapping 对象,并将其添加到系统句柄对象 XmlRpcSystemImpl,详见代码清单 18。

清单 18. 定制 XmlRpcServlet

public class MyXmlRpcServlet extends XmlRpcServlet {
protected XmlRpcHandlerMapping newXmlRpcHandlerMapping()
throws XmlRpcException {
PropertyHandlerMapping mapping =
(PropertyHandlerMapping) newXmlRpcHandlerMapping();
XmlRpcSystemImpl.addSystemHandler(mapping);
}
}

如果使用的是嵌入式 WebServer,修改方法与之相似。完整代码见附带的 Eclipse 项目 XML-RPC-EmbeddedWebServer\src\ dw.webserver, 代码片段见清单 19。为了演示启用内省机制前后服务端提供的远程方法的差异,笔者特意在代码中调用了 PropertyHandlerMapping 的 getListMethods() 方法,并在服务端控制台把远程方法打印出来。可以通过此种方式,对启用内省配置前后服务端提供的方法进行比较。

清单 19. 嵌入式 WebServer 内省配置

WebServer webServer = new WebServer(port);
XmlRpcServer xmlRpcServer = webServer.getXmlRpcServer();
PropertyHandlerMapping phm = new PropertyHandlerMapping();
phm.load(Thread.currentThread().getContextClassLoader(),
"MyHandlers.properties");
String[] ss=phm.getListMethods();
System.out.println("可以通过 XML-RPC 调用的方法(添加 system 句柄前)");
for(String s:ss){
System.err.println(s);
}
xmlRpcServer.setHandlerMapping(phm);
XmlRpcSystemImpl.addSystemHandler(phm);
Thread.sleep(2000);
System.out.println("可以通过 XML-RPC 调用的方法(添加 system 句柄后)");
ss=phm.getListMethods();
for(String s:ss){
System.err.println(s);
}

下面的单元测试类代码清单 20 演示如何在客户端调用内省方法 system.listMethods,运行该单元测试类可以打印服务端提供的远程方法列表。更多的例子在附带的源代码项目中可查。

清单 20. 客户端测试内省配置

@Test
public void testSystem_listMethods() {
Object[] params = new Object[0];
try {
Object[] resultArray = (Object[]) client.execute(
"system.listMethods",params);
} catch (XmlRpcException e) {
e.printStackTrace();
org.junit.Assert.fail(e.getMessage());
}
org.junit.Assert.assertTrue(resultArray.length > 0);
for (int i = 0; i < resultArray.length; i++) {
System.err.println(i + "#.   " + resultArray[i]);
}
}

回页首
结束语
通过上面的介绍,我们了解了 XML-RPC 协议规范。通过使用网络数据包捕获工具 SmartSniff, 捕获 XML-RPC 的请求与响应消息并进行了细致的研究。然后以 XML-RPC 的 Apache 实现为例,演示了如何开发 XML-RPC 客户端及服务端 Java 代码。最后对 Apache XML-RPC 的高级特性进行了介绍。相信读者们,如果想实现简单的分布式计算,现在应该很清楚如何操作。对于使用 JIRA、TestLink 等提供 XML-RPC 接口的开源工具的读者,如果想调用其开放
XML-RPC 接口,也是很容易的事情。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: