Skip to content

XML-RPC

XML-RPC(XML Remote Procedure Call)是一种使用 XML 格式通过 HTTP 进行远程过程调用的协议。它比 SOAP 更轻量,适合简单的服务间通信。本节将讲解 PHP 中 XML-RPC 的使用方法。

前置知识

阅读本节前,建议先了解:cURL 详解SOAP

基础概念

什么是 XML-RPC

XML-RPC 是一种通过 HTTP 传输 XML 编码数据的远程过程调用协议。它于 1998 年发布,是 Web 服务的早期标准之一。相比 SOAP,XML-RPC 更加简洁,只支持有限的数据类型。

支持的数据类型

XML-RPC 类型PHP 类型说明
<int> / <i4>int32 位整数
<boolean>bool布尔值(0 或 1)
<string>string字符串
<double>float双精度浮点数
<dateTime.iso8601>stringISO 8601 日期时间
<base64>stringBase64 编码的二进制数据
<struct>array关联数组/对象
<array>array索引数组
<nil> (扩展)null空值

安装

bash
# PHP 8.4+ 内置 xmlrpc 扩展(或通过 PECL 安装)
pecl install xmlrpc

# 编译安装
./configure --with-xmlrpc

XML-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-RPCSOAPREST
协议HTTP + XMLHTTP + XML (Envelope)HTTP (JSON/XML)
复杂度
数据类型有限扩展(XSD)自定义
WSDLOpenAPI/Swagger
适用场景简单 RPC企业级 WS现代Web API

提示

在现代 PHP 开发中,XML-RPC 已较少使用。新项目推荐使用 RESTful API + JSON。但了解 XML-RPC 有助于维护遗留系统,例如 WordPress 的 XML-RPC 接口。

最佳实践

  1. 使用 UTF-8 编码:确保请求和响应都使用 UTF-8
  2. 参数验证:服务端对所有输入进行严格验证
  3. 错误处理:使用标准 fault 格式返回错误
  4. 超时设置:客户端设置合理的请求超时
  5. 日志记录:记录所有 XML-RPC 调用便于排查问题

下一节

继续学习:Yar RPC 框架

参考链接