有时候我们需要在两个 php 服务器之间执行远程过程调用,虽然用 xml-rpc 是一种解决方案,但是目前的 xml-rpc 的 php 实现用起来都非常麻烦,原来我也写过一个能够方便使用的 php 的 xml-rpc 类库,但是那个需要安装 xmlrpc-epi 扩展,这对于不支持这个扩展的服务器就不方便了。所以我写了下面这个 phprpc 的类库,它没有使用 xml-rpc 协议,而是我自己定义的 phprpc 协议,这个虽然只能用于 php 程序之间的远程过程调用,但是使用起来比 xml-rpc 更方便。
其他语言也不是完全不可能实现,只是实现起来比用 php 麻烦点而已,因为这里对参数和返回值的序列化与反序列化直接使用的 php 的 serialize 和 unserialize,其他语言只要能够实现这两个函数,要实现这个协议也很简单。
1. <?php
2. /**
3. * @author Ma Bingyao(andot@ujn.edu.cn)
4. * @copyright 2005 CoolCode.CN
5. * @package PHPRPC
6. * @version 0.1
7. * @link http://www.coolcode.cn/?p=101
8. *
9. * Example usage:
10. *
11. * server.php
12. * <?php
13. * include('phprpc.php');
14. * function add($a, $b) {
15. * return $a + $b;
16. * }
17. * function sub($a, $b) {
18. * return $a - $b;
19. * }
20. * new phprpc_server(array('add', 'sub'));
21. * ?>
22. *
23. * client.php
24. * <?php
25. * include('phprpc.php');
26. * $rpc_client = new phprpc_client('http://test.coolcode.cn/phprpc/server.php');
27. * echo $rpc_client->add(1, 2);
28. * echo "<br />";
29. * echo $rpc_client->Sub(1, 2); // the function name is case-insensitive
30. * echo "<br />";
31. * // error handle
32. * echo "<pre>";
33. * $result = $rpc_client->mul(1, 2); // no mul function
34. * if (get_class($result) == "phprpc_error") {
35. * print_r($result);
36. * }
37. * $result = $rpc_client->add(1); // wrong arguments
38. * if (get_class($result) == "phprpc_error") {
39. * print_r($result);
40. * }
41. * $rpc_client->use_service('wrong url'); // wrong url
42. * $result = $rpc_client->add(1, 2);
43. * if (get_class($result) == "phprpc_error") {
44. * print_r($result);
45. * }
46. * echo "</pre>";
47. * ?>
48. */
49.
50. class phprpc_error {
51. var $errno;
52. var $errstr;
53. function phprpc_error($errno, $errstr) {
54. $this->errno = $errno;
55. $this->errstr = $errstr;
56. }
57. }
58.
59. class phprpc_server {
60. function tolower(&$func, $keys) {
61. $func = strtolower($func);
62. }
63. function error_handler($errno, $errstr) {
64. echo $errno;
65. echo "\r\n\r\n";
66. echo $errstr;
67. exit;
68. }
69. function phprpc_server($functions) {
70. header("HTTP/1.1 200 OK");
71. header("Connection: close");
72. header("Content-Type: text/plain");
73. header("X-Powered-By: PHPRPC Server");
74. header("Date: " . gmdate("D, d M Y H:i:s") . " GMT");
75. error_reporting(0);
76. set_error_handler(array(&$this, 'error_handler'));
77. if (isset($_POST['phprpc_func'])) {
78. array_walk($functions, array(&$this, 'tolower'));
79. $function = strtolower(get_magic_quotes_gpc() ? stripslashes($_POST['phprpc_func']) : $_POST['phprpc_func']);
80. if (in_array($function, $functions)) {
81. if (isset($_POST['phprpc_args'])) {
82. $arguments = unserialize(base64_decode(get_magic_quotes_gpc() ? stripslashes($_POST['phprpc_args']) : $_POST['phprpc_args']));
83. }
84. else {
85. $arguments = array();
86. }
87. $result = base64_encode(serialize(call_user_func_array($function, $arguments)));
88. }
89. else {
90. $result = "1\r\n\r\nCan't find this function $function()";
91. }
92. }
93. else {
94. $result = "1\r\n\r\nCalled no function";
95. }
96. print $result;
97. restore_error_handler();
98. }
99. }
100.
101. class phprpc_client {
102. var $scheme;
103. var $host;
104. var $port;
105. var $path;
106. var $user;
107. var $pass;
108. var $timeout;
109.
110. function phprpc_client($url, $user = '', $pass = '', $timeout = 10) {
111. $this->use_service($url);
112. $this->user = $user;
113. $this->pass = $pass;
114. $this->timeout = $timeout;
115. }
116.
117. function use_service($url) {
118. $urlparts = parse_url($url);
119.
120. if (isset($urlparts['scheme']) && ($urlparts['scheme'] == "https")) {
121. $urlparts['scheme'] = "ssl";
122. }
123. else {
124. $urlparts['scheme'] = "";
125. }
126.
127. if (!isset($urlparts['host'])) {
128. if (isset($_SERVER["HTTP_HOST"])) {
129. $urlparts['host'] = $_SERVER["HTTP_HOST"];
130. }
131. else if (isset($_SERVER["SERVER_NAME"])) {
132. $urlparts['host'] = $_SERVER["SERVER_NAME"];
133. }
134. else {
135. $urlparts['host'] = "localhost";
136. }
137. }
138.
139. if (!isset($urlparts['port'])) {
140. if ($urlparts['scheme'] == "ssl") {
141. $urlparts['port'] = 443;
142. }
143. else {
144. $urlparts['port'] = 80;
145. }
146. }
147.
148. if (!isset($urlparts['path'])) {
149. $urlparts['path'] = "/";
150. }
151. else if (($urlparts['path']{0} != '/') && ($_SERVER["PHP_SELF"]{0} == '/')) {
152. $urlparts['path'] = substr($_SERVER["PHP_SELF"], 0, strrpos($_SERVER["PHP_SELF"], '/') + 1) . $urlparts['path'];
153. }
154.
155. $this->scheme = $urlparts['scheme'];
156. $this->host = $urlparts['host'];
157. $this->port = $urlparts['port'];
158. $this->path = $urlparts['path'];
159. }
160.
161. function __invoke($function, $arguments) {
162. $request = "phprpc_func=$function";
163. if (count($arguments) > 0) {
164. $request .= "&phprpc_args=" . base64_encode(serialize($arguments));
165. }
166. $content_len = strlen($request);
167. $errno = 0;
168. $errstr = '';
169. $host = ($this->scheme) ? $this->scheme . "://" . $this->host : $this->host;
170. $handle = @fsockopen($host, $this->port, $errno, $errstr, $this->timeout);
171. $buf = '';
172. if ($handle) {
173. if ($this->user) {
174. $auth = "Authorization: Basic " . base64_encode($this->user . ":" . $this->pass) . "\r\n";
175. }
176. $http_request =
177. "POST $this->path HTTP/1.0\r\n" .
178. "User-Agent: PHPRPC Client\r\n" .
179. "Host: $this->host:$this->port\r\n" .
180. $auth .
181. "Content-Type: application/x-www-form-urlencoded\r\n" .
182. "Content-Length: $content_len\r\n" .
183. "\r\n" .
184. $request;
185. fputs($handle, $http_request, strlen($http_request));
186. while (!feof($handle)) {
187. $buf .= fgets($handle, 128);
188. }
189. fclose($handle);
190. if (strlen($buf)) {
191. $buf = explode("\r\n\r\n", $buf);
192. $header = $buf[0];
193. if (strpos($header, 'X-Powered-By: PHPRPC Server') !== FALSE) {
194. if (count($buf) == 2) {
195. $result = unserialize(base64_decode($buf[1]));
196. }
197. else if (count($buf) == 3){
198. $result = new phprpc_error((int)$buf[1], $buf[2]);
199. }
200. else {
201. $result = new phprpc_error(E_ERROR, "Unknown error");
202. }
203. }
204. else {
205. $result = new phprpc_error(E_ERROR, "Wrong PHPRPC Server");
206. }
207. }
208. else {
209. $result = new phprpc_error(E_ERROR, "No data received from server");
210. }
211. }
212. else {
213. $result = new phprpc_error($errno, $errstr);
214. }
215. return $result;
216. }
217.
218. function __call($function, $arguments, &$return) {
219. $return = $this->__invoke($function, $arguments);
220. if (phpversion() < 5) return true;
221. }
222.
223. function call($function, $args) {
224. $arguments = func_get_args();
225. array_shift($arguments);
226. return $this->__invoke($function, $arguments);
227. }
228. }
229.
230. if (function_exists("overload") && version_compare(phpversion(), "5", "<")) {
231. overload('phprpc_client');
232. }
233. ?>
