摘要

在 PHPRPC 1.1 协议描述中,我们定义了一种轻型的 RPC 机制 PHPRPC 来代替 XML-RPC。该协议可以使纯 PHP 应用程序之间的通讯更方便快速,并且可以与其它实现了该协议的语言的应用程序之间进行的通讯。本文所要描述的是,在 PHPRPC 1.1 的基础上增加了数据加密传输机制的 PHPRPC 2.0 协议。

版本

下面描述的是 2.0 版本的 PHPRPC 协议。

概述

PHPRPC 是一个工作于 Internet 之上的远程过程调用协议。

PHPRPC 使用 HTTP 协议作为传输协议。PHPRPC 可以使用 HTTP-GET 和 HTTP-POST 两种请求方式。客户端提交的请求和服务器端执行后返回的应答信息在适当的时候通过 PHP serialize() 和 base64_encode() 进行封装,通过 PHP 的 unserialize() 和 base64_decode() 解封装。

远程过程调用中的参数和返回值可以是 PHP 中除了资源类型以外的任意类型(例如整数类型、浮点数类型、字符串类型、数组、对象等),但对于自定义对象,通讯双方都包含该对象的类定义时方才有效。

PHPRPC 2.0 中增加了数据加密传输的安全机制。

请求和应答

请求使用的是标准的 application/x-www-form-urlencoded 格式。请求中一共包含五个参数,请求的参数名称一律小写,但前后顺序任意。

应答头部的 Content-Type 为 text/plain,X-Powered-By 为 PHPRPC Server/版本号。客户端可通过这两项判断所请求的服务器是否正确。

应答内容为 JavaScript 语法格式。各语句之间用“rn”分割。应答内容的各项有固定的前后顺序。

我们将请求和应答大致分为初始化和远程过程调用两个阶段来描述。

初始化阶段的请求和应答

初始化阶段主要完成交换密钥和获取远程调用列表的工作。

只有当需要启用加密机制时,才需要进行密钥交换。远程调用列表的获取对于客户端的具体实现也是可选的。

初始化阶段的请求可以包含以下三个参数:

  • phprpc_encrypt(可选):该参数在不同阶段有不同的取值。在初始化阶段,该参数取值为 true 或者 false,如果取值为 true,表示启用加密传输机制,接下来将会进行密钥交换。如果取值为 false,表示不启用加密传输机制。如果没有设置该参数,则默认为 false。 
  • phprpc_callback(可选):该参数只用于 JavaScript 客户端,表示服务器端调用执行后,客户端需要回调的JavaScript 程序。其值为经过 base64_encode() 编码的 JavaScript 程序。 
  • phprpc_encode(可选):该参数一般用于 JavaScript 客户端,表示服务器端调用执行后,所返回的各项是否进行 base64_encode() 编码,默认为 true,JavaScript 客户端如果设为 false,可以大大的提高处理速度。 

初始化阶段的应答信息跟请求紧密相关。

如果请求中 phprpc_encrypt 参数为 true,则应答中第一项为:

phprpc_encrypt="value";

其中 value 为包含了 Diffie-Hellman 密钥交换算法中的 p、g、Ya 的三个元素的数组(详见安全登录系统的设计与实现方案一文),它们的下标分别为 p、g、y,其值为由十进制数字组成的字符串。该数组经过 serialize() 序列化后再进行 base64_encode() 编码(如果请求中包含了 phprpc_encode 参数,则不进行 base64 编码,而是用 addcslashes() 生成 JavaScript 可识别的字符串,详见用 PHP 生成 JavaScript 字符串)。

第二项为远程调用列表:

phprpc_functions="value";

其中 value 为经过 serialize() 序列化后再进行 base64_encode() 编码的远程调用列表数组。

如果客户端的请求中包含了回调参数 phprpc_callback,则将其内容进行解码(调用 base64_decode()),然后直接输出解码后的内容。

当客户端收到应答后,根据 p、g、y 及客户端生成的随机数 Xb 来生成 Yb 和密钥 k。Yb 的值(由十进制数字组成的字符串)作为第二次请求的 phprpc_encrypt 参数值进行提交。

当服务器接收到第二次请求时,根据请求中 phprpc_encrypt 所包含的 Yb 和服务器端保存的 Xa (保存在 session 中)来生成密钥 k。之后返回应答信息。

应答信息格式同第一次应答的格式。不同的是 phprpc_encrypt 的值为 true。此时,启用加密的初始化请求与应答完成。

如果第一次请求时,phprpc_encrypt 的取值为 false,或者没有设置 phprpc_encrypt。则应答直接返回上面应答的后两项,而不包含 phprpc_encrypt 这一项。这与 PHPRPC 1.1 是兼容的。

远程过程调用阶段的请求和应答

请求中可以包含以下五个参数,请求参数一律小写,前后顺序任意:

  • phprpc_func(必选):该参数表示所调用的服务器端的函数,其值为要调用的函数名,函数名采用明文,无大小写区分,函数名后不跟括号。 
  • phprpc_encrypt(可选):该参数在这一阶段取值为 0,1,2。默认取值为 0,表示不加密传输。如果该值为 1,表示单向加密,即请求和应答中的 phprpc_args 参数值经过了加密处理。如果该值为 2,表示双向加密,即除了 phprpc_args 参数加密以外,应答中的 phprpc_result 也要经过加密处理。 
  • phprpc_args(可选):该参数表示所调用函数所需的参数,参数以数组格式保存,第一个参数的数组下标为 0,第二个参数的数组下标为 1,以此类推。该数组在赋给 phprpc_args 前需要通过 serialize() 序列化(当使用加密传输时,需要再对序列化后的值进行加密),然后通过 base64_encode() 对序列化后的值进行编码。 
  • phprpc_callback(可选):该参数只用于 JavaScript 客户端,表示服务器端调用执行后,客户端需要回调的JavaScript 程序。其值为经过 base64_encode() 编码的 JavaScript 程序。 
  • phprpc_encode(可选):该参数一般用于 JavaScript 客户端,表示服务器端调用执行后,所返回的各项是否进行 base64_encode() 编码,默认为 true,JavaScript 客户端如果设为 false,可以大大的提高处理速度。 

如果服务器端正确执行了所请求的调用。则返回内容的第一项为过程调用的返回值,第二项为过程调用的参数。

第一项格式如下:

phprpc_result="value";

其中 value 为经过编码后的返回值。编码过程为:先对过程调用的返回值进行序列化(调用 PHP serialize() ),然后根据请求参数中 phprpc_encrypt 的取值来决定是否对序列化后的值进行加密。接下来根据请求参数中的 phprpc_encode 来设置编码格式,如果 phprpc_encode 为 false,则对上一步处理后的值进行 addcslashes 操作,否则进行 base64 编码(调用 base64_encode() )。

第二项格式如下:

phprpc_args="value";

其中 value 为经过编码后的过程调用后的参数值。因为所调用的过程在参数传递时可能是引用传递,因此在调用执行后,参数值可能会改变。该项返回的就是改变后的参数值。该项的编码过程与第一项返回值的编码过程相同。

如果服务器端执行所请求的调用时发生了错误,则没有前面两项。接下来的两项是跟错误处理有关的。第一个是出错编号,第二个是出错信息。这两项格式为:

phprpc_errno="errno";
phprpc_errstr="errstr";

上面 errno 为错误编号,为整数明文,如果没有错误,则该数字为 0。errstr 是错误信息,为 base64 编码的字符串或经过了 addcslashes 操作的字符串(取决于 phprpc_encode 的取值)。

接下来是服务器端的过程调用中本来应该输出的内容,在这里它们将被重定向为 phprpc_output 项的值。格式为:

phprpc_output="output";

这里的 output 也是经过 base64 编码的字符串或经过了 addcslashes 操作的字符串(取决于 phprpc_encode 的取值)。

如果客户端的请求中包含了回调参数 phprpc_callback,则将其内容进行解码(调用 base64_decode()),然后直接输出解码后的内容。

实现

PHPRPC 2.0 中只定义了密钥交换所使用的算法,但没有定义具体加密所使用的算法,在具体实现中可根据具体需求来选择加密算法,但只有采用了相同加密算法的 PHPRPC 2.0 实现才能够进行数据加密通讯,在这里我们推荐采用 XXTEA 加密算法。

在使用该协议的实现时,无需了解该协议。该协议描述旨在为其他开发语言开发者(例如 Perl、Python、Java 等)在实现该协议时提供参考。其他语言开发者在实现该协议时也可以参考已有的实现。

备注

本文所描述的 PHPRPC 协议不同于 Description of PHP-RPC protocol 中所描述的 PHP-RPC 协议。尽管这两个协议都是用来替代 XML-RPC 的专用于 PHP 应用程序间的轻量级 RPC 协议,而且都用到了 php 的序列化(serialize() 和 unserialize())和 BASE64 编码(base64_encode() 和 base64_decode())。但是本文中所描述的 PHPRPC 协议比该文中的 PHP-RPC 协议多了错误处理,服务器端输出重定向,JavaScript 回调支持等,而且请求和应答的内容及格式也不相同。希望读者不要混淆。