XML-RPC
XML-RPC(XML Remote Procedure Call)是一种使用 XML 格式通过 HTTP 进行远程过程调用的协议。它比 SOAP 更轻量,适合简单的服务间通信。本节将讲解 PHP 中 XML-RPC 的使用方法。
基础概念
什么是 XML-RPC
XML-RPC 是一种通过 HTTP 传输 XML 编码数据的远程过程调用协议。它于 1998 年发布,是 Web 服务的早期标准之一。相比 SOAP,XML-RPC 更加简洁,只支持有限的数据类型。
支持的数据类型
| XML-RPC 类型 | PHP 类型 | 说明 |
|---|---|---|
<int> / <i4> | int | 32 位整数 |
<boolean> | bool | 布尔值(0 或 1) |
<string> | string | 字符串 |
<double> | float | 双精度浮点数 |
<dateTime.iso8601> | string | ISO 8601 日期时间 |
<base64> | string | Base64 编码的二进制数据 |
<struct> | array | 关联数组/对象 |
<array> | array | 索引数组 |
<nil> (扩展) | null | 空值 |
安装
bash
# PHP 8.4+ 内置 xmlrpc 扩展(或通过 PECL 安装)
pecl install xmlrpc
# 编译安装
./configure --with-xmlrpcXML-RPC 消息格式
请求示例
xml
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>user.getInfo</methodName>
<params>
<param>
<value><int>123</int></value>
</param>
<param>
<value><string>active</string></value>
</param>
</params>
</methodCall>响应示例
xml
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<params>
<param>
<value>
<struct>
<member>
<name>id</name>
<value><int>123</int></value>
</member>
<member>
<name>name</name>
<value><string>张三</string></value>
</member>
</struct>
</value>
</param>
</params>
</methodResponse>错误响应
xml
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>404</int></value>
</member>
<member>
<name>faultString</name>
<value><string>User not found</string></value>
</member>
</struct>
</value>
</fault>
</methodResponse>XML-RPC 客户端
基础用法
php
<?php
declare(strict_types=1);
/**
* 使用 xmlrpc 扩展的客户端
*/
function xmlrpcCall(
string $serverUrl,
string $methodName,
array $params = []
): mixed {
$request = xmlrpc_encode_request($methodName, $params, [
'encoding' => 'UTF-8',
]);
$headers = [
'Content-Type: text/xml; charset=UTF-8',
'Content-Length: ' . strlen($request),
];
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => implode("\r\n", $headers),
'content' => $request,
'timeout' => 30,
],
]);
$response = file_get_contents($serverUrl, false, $context);
if ($response === false) {
throw new RuntimeException("XML-RPC 请求失败");
}
$result = xmlrpc_decode($response, 'UTF-8');
if (xmlrpc_is_fault($result)) {
throw new RuntimeException(
"XML-RPC 错误 [{$result['faultCode']}]: {$result['faultString']}"
);
}
return $result;
}
// 使用示例
try {
$result = xmlrpcCall(
'https://api.example.com/xmlrpc',
'user.getInfo',
[123]
);
print_r($result);
} catch (RuntimeException $e) {
echo "错误: " . $e->getMessage() . PHP_EOL;
}使用 cURL 的 XML-RPC 客户端
php
<?php
declare(strict_types=1);
/**
* 基于 cURL 的 XML-RPC 客户端
*/
class XmlRpcClient
{
public function __construct(
private readonly string $serverUrl,
private readonly int $timeout = 30
) {}
public function call(string $method, array $params = []): mixed
{
$requestXml = xmlrpc_encode_request($method, $params, [
'encoding' => 'UTF-8',
'escaping' => ['markup'],
'version' => 'xmlrpc',
]);
$ch = curl_init($this->serverUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $requestXml,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: text/xml; charset=UTF-8',
],
CURLOPT_TIMEOUT => $this->timeout,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($response === false) {
throw new RuntimeException("请求失败: {$error}");
}
if ($httpCode !== 200) {
throw new RuntimeException("HTTP 错误: {$httpCode}");
}
$result = xmlrpc_decode($response, 'UTF-8');
if (xmlrpc_is_fault($result)) {
throw new RuntimeException(
"XML-RPC 错误 [{$result['faultCode']}]: {$result['faultString']}"
);
}
return $result;
}
/**
* 多次调用
*/
public function multicall(array $calls): array
{
$methodCalls = [];
foreach ($calls as $call) {
$methodCalls[] = [
'methodName' => $call['method'],
'params' => $call['params'] ?? [],
];
}
return $this->call('system.multicall', [$methodCalls]);
}
}
// 使用示例
// $client = new XmlRpcClient('https://api.example.com/xmlrpc');
//
// // 单次调用
// $user = $client->call('user.getInfo', [123]);
//
// // 批量调用
// $results = $client->multicall([
// ['method' => 'user.getInfo', 'params' => [123]],
// ['method' => 'user.getInfo', 'params' => [456]],
// ['method' => 'user.getInfo', 'params' => [789]],
// ]);XML-RPC 服务器
创建 XML-RPC 服务
php
<?php
declare(strict_types=1);
/**
* XML-RPC 服务端
*/
// 注册请求处理函数
$requestFunctions = [
// 基本运算
'math.add' => 'mathAdd',
'math.subtract' => 'mathSubtract',
'math.multiply' => 'mathMultiply',
'math.divide' => 'mathDivide',
// 用户操作
'user.get' => 'userGet',
'user.create' => 'userCreate',
'user.list' => 'userList',
// 系统方法
'system.listMethods' => 'systemListMethods',
'system.methodHelp' => 'systemMethodHelp',
];
function mathAdd(int $a, int $b): int
{
return $a + $b;
}
function mathSubtract(int $a, int $b): int
{
return $a - $b;
}
function mathMultiply(int $a, int $b): int
{
return $a * $b;
}
function mathDivide(int $a, int $b): int
{
if ($b === 0) {
// 返回 XML-RPC fault
return xmlrpc_encode_request(null, null, [
'fault' => true,
]);
}
return (int) ($a / $b);
}
function userGet(int $userId): array
{
// 模拟数据库查询
$users = [
1 => ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
2 => ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com'],
];
if (isset($users[$userId])) {
return $users[$userId];
}
// 返回 fault
return [
'faultCode' => 404,
'faultString' => "用户不存在: {$userId}",
];
}
function userList(int $offset = 0, int $limit = 10): array
{
return [
'total' => 100,
'offset' => $offset,
'limit' => $limit,
'users' => [
['id' => 1, 'name' => '张三'],
['id' => 2, 'name' => '李四'],
],
];
}
function systemListMethods(): array
{
global $requestFunctions;
return array_keys($requestFunctions);
}
function systemMethodHelp(string $method): string
{
$help = [
'math.add' => '两个数相加,参数:a(int), b(int)',
'math.subtract' => '两个数相减,参数:a(int), b(int)',
'math.multiply' => '两个数相乘,参数:a(int), b(int)',
'math.divide' => '两个数相除,参数:a(int), b(int)',
'user.get' => '获取用户信息,参数:userId(int)',
'user.list' => '获取用户列表,参数:offset(int), limit(int)',
];
return $help[$method] ?? '无帮助信息';
}
// 处理 XML-RPC 请求
$requestXml = file_get_contents('php://input');
$response = xmlrpc_server_call(
xmlrpc_server_create(),
$requestXml
);
// 或者使用 xmlrpc_server_register_methods
$server = xmlrpc_server_create();
xmlrpc_server_register_method($server, 'math.add', 'mathAdd');
xmlrpc_server_register_method($server, 'user.get', 'userGet');
// ... 注册所有方法 ...
// 处理请求
$response = xmlrpc_server_call_method($server, $requestXml, null, [
'encoding' => 'UTF-8',
]);
header('Content-Type: text/xml; charset=UTF-8');
echo $response;内置系统方法
XML-RPC 规范定义了几个标准的系统方法:
php
<?php
declare(strict_types=1);
// system.listMethods - 列出所有可用方法
// system.methodSignature - 获取方法签名
// system.methodHelp - 获取方法帮助文档
// system.multicall - 批量调用多个方法
function handleMulticall(array $methodCalls): array
{
$results = [];
foreach ($methodCalls as $call) {
$methodName = $call['methodName'];
$params = $call['params'] ?? [];
try {
$result = call_user_func_array($methodName, $params);
$results[] = ['faultCode' => 0, 'response' => $result];
} catch (\Throwable $e) {
$results[] = [
'faultCode' => 500,
'faultString' => $e->getMessage(),
];
}
}
return $results;
}详细说明
xmlrpc_encode_request
php
<?php
declare(strict_types=1);
// 编码请求
$request = xmlrpc_encode_request(
'user.getInfo', // 方法名
[123], // 参数数组
[
'encoding' => 'UTF-8', // 编码
'escaping' => ['markup'], // XML 转义
'version' => 'xmlrpc', // 协议版本
]
);
// 编码值为 XML-RPC 格式
$encoded = xmlrpc_encode(
['name' => '张三', 'age' => 30, 'active' => true]
);
echo $encoded;
// <struct><member><name>name</name><value><string>张三</string></value></member>...xmlrpc_decode
php
<?php
declare(strict_types=1);
// 解码 XML-RPC 响应
$xml = '<?xml version="1.0"?><methodResponse><params><param><value><int>42</int></value></param></params></methodResponse>';
$result = xmlrpc_decode($xml);
// int(42)
// 判断是否为 fault 响应
if (xmlrpc_is_fault($result)) {
echo "Fault Code: " . $result['faultCode'] . PHP_EOL;
echo "Fault String: " . $result['faultString'] . PHP_EOL;
}
// xmlrpc_decode_request 同时获取方法名
$requestXml = file_get_contents('php://input');
$method = '';
$params = xmlrpc_decode_request($requestXml, $method, 'UTF-8');
echo "Method: {$method}" . PHP_EOL;
echo "Params: " . print_r($params, true);类型转换
php
<?php
declare(strict_types=1);
// xmlrpc_set_type 用于强制指定编码类型
$xmlrpcValue = xmlrpc_encode('Hello World');
xmlrpc_set_type($xmlrpcValue, 'base64');
// 创建 xmlrpcval 对象进行精确类型控制
$intVal = new xmlrpcval(42, 'int');
$boolVal = new xmlrpcval(true, 'boolean');
$stringVal = new xmlrpcval('Hello', 'string');
$doubleVal = new xmlrpcval(3.14, 'double');
$dateVal = new xmlrpcval('20240101T12:00:00', 'dateTime.iso8601');
$base64Val = new xmlrpcval(base64_encode('binary data'), 'base64');
// 创建结构体
$struct = new xmlrpcval([
'name' => new xmlrpcval('张三', 'string'),
'age' => new xmlrpcval(30, 'int'),
'email' => new xmlrpcval('zhangsan@example.com', 'string'),
], 'struct');
// 创建数组
$array = new xmlrpcval([
new xmlrpcval('apple', 'string'),
new xmlrpcval('banana', 'string'),
], 'array');
// 创建消息
$msg = new xmlrpcmsg('user.create', [$struct]);
$request = $msg->serialize();实战示例
完整的 XML-RPC 服务封装
php
<?php
declare(strict_types=1);
/**
* XML-RPC 服务端框架
*/
class XmlRpcServer
{
private array $methods = [];
public function register(string $name, callable $handler): void
{
$this->methods[$name] = $handler;
}
public function handle(): void
{
$input = file_get_contents('php://input');
// 解码请求
$method = '';
$params = xmlrpc_decode_request($input, $method);
if (!isset($this->methods[$method])) {
$this->sendFault(1, "方法不存在: {$method}");
return;
}
try {
$result = call_user_func_array(
$this->methods[$method],
is_array($params) ? $params : []
);
$this->sendResponse($result);
} catch (\Throwable $e) {
$this->sendFault(500, $e->getMessage());
}
}
private function sendResponse(mixed $data): void
{
$response = xmlrpc_encode_request(null, $data, [
'encoding' => 'UTF-8',
'version' => 'xmlrpc',
]);
header('Content-Type: text/xml; charset=UTF-8');
echo $response;
}
private function sendFault(int $code, string $message): void
{
$fault = [
'faultCode' => $code,
'faultString' => $message,
];
$response = xmlrpc_encode_request(null, $fault, [
'encoding' => 'UTF-8',
'version' => 'xmlrpc',
]);
header('Content-Type: text/xml; charset=UTF-8');
header('HTTP/1.1 500 Internal Server Error');
echo $response;
}
}
// 使用示例
$server = new XmlRpcServer();
$server->register('add', fn(int $a, int $b): int => $a + $b);
$server->register('subtract', fn(int $a, int $b): int => $a - $b);
$server->handle();注意事项
XML-RPC vs SOAP vs REST
| 特性 | XML-RPC | SOAP | REST |
|---|---|---|---|
| 协议 | HTTP + XML | HTTP + XML (Envelope) | HTTP (JSON/XML) |
| 复杂度 | 低 | 高 | 低 |
| 数据类型 | 有限 | 扩展(XSD) | 自定义 |
| WSDL | 无 | 有 | OpenAPI/Swagger |
| 适用场景 | 简单 RPC | 企业级 WS | 现代Web API |
提示
在现代 PHP 开发中,XML-RPC 已较少使用。新项目推荐使用 RESTful API + JSON。但了解 XML-RPC 有助于维护遗留系统,例如 WordPress 的 XML-RPC 接口。
最佳实践
- 使用 UTF-8 编码:确保请求和响应都使用 UTF-8
- 参数验证:服务端对所有输入进行严格验证
- 错误处理:使用标准 fault 格式返回错误
- 超时设置:客户端设置合理的请求超时
- 日志记录:记录所有 XML-RPC 调用便于排查问题
下一节
继续学习:Yar RPC 框架