在跨process或是跨機器溝通上的IPC有許多方式,以http為基底的也有如SOAP、REST相關的實現,並且也發展的滿成熟的。相較之下,xml-rpc比較像是整個過程中的一個開端或是過渡的解決方案, 這篇整理會著重在背景介紹、spec的一些想法
xml-rpc發展的年代大約在1998年,這個時間點是Web開始蓬勃發展的年代,網路的通信普及也使得跨機器的溝通越來越重要,在這之前或同一個時期,其實已經有不少跨機器通信的標準或實現,例如COBRA、Sun RPC等等,在TCP/IP的環境中,加上資料的傳輸格式的定義(marshaling),其實就可以實現了,因為不難,所以有很多其他像是COBRA、Java RMI、DCOM,都是不同團體或陣營提出的解決方案。xml-rpc就是在當時環境下的一個產物,一方面XML剛出現沒多久,作為一個資料交換的格式,XML的設計在當時跟其他的資料交換格式,更強調human readable以及machine readable,在當時常見的資料交換作法如ASN.1 + DER,常見於其他protocol (如SNMP、LDAP)。將網路+XML作為rpc的想法很自然就出現了。使用XML的代價就是 not compact,資料的大小會膨脹許多,這個對於網路頻寬資源有限的時代或是環境而言,就不是那麼受歡迎。
跨機器通信,其中有一個重點是heterogeneous platforms,因為在1998年時,很多rpc類的protocol是特定陣營提出或使用的,例如DCOM本身基本上只在Windows實現,其他的平台要實作可能會遇到很多瓶頸,例如poor implementation,或是可能架構上已經跟系統的某些元件coupling,所以完整實現很困難。COBRA則是因為本身設計的太完整、太複雜,導致無論是使用或者實現完整都有一定的難度,這個現象在很多系統發展或是標準制定都會看到,當一個設計越想要comprehensive, 想要一個解決方案涵蓋所有的問題時,常常最後會因為過度複雜,導致開發者沒辦法完全掌握。
xml-rpc的spec可以參考: http://xmlrpc.com/spec.md
我們先從實際程式範例來看xml-rpc可以做的事情
server side example: adapted from https://docs.python.org/3/library/xmlrpc.client.html
# from https://docs.python.org/3/library/xmlrpc.client.html from xmlrpc.server import SimpleXMLRPCServer def is_even(n): return n % 2 == 0 server = SimpleXMLRPCServer(("localhost", 8090)) print("Listening on port 8090...") server.register_function(is_even, "is_even") server.serve_forever()
python文件中有client side example
import xmlrpc.client with xmlrpc.client.ServerProxy("http://localhost:8000/") as proxy: print("3 is even: %s" % str(proxy.is_even(3))) print("100 is even: %s" % str(proxy.is_even(100)))
不過為了展示interop,我另外用nodejs寫了一個範例
var xmlrpc = require('xmlrpc') function TestClient(ep){ let client = xmlrpc.createClient(ep); this.isEven = function(val){ return new Promise(function(resolve, reject){ client.methodCall('is_even', [val], function (err, data) { if(err) return reject(err); resolve(data); }) }); } } async function main(){ let testClient = new TestClient('http://127.0.0.1:8090/'); console.log('3 is even?', await testClient.isEven(3)); console.log('100 is even?', await testClient.isEven(100)); } main().catch(console.log);
看起來js的實現很冗長,主要的原因再methodCall要傳入 method name和argument,javascript ES6有Proxy物件,可以動態攔截function call
var xmlrpc = require('xmlrpc') function TestClient(ep){ let client = xmlrpc.createClient(ep); let smartClient = new Proxy({}, { get: function( target, prop, receiver) { if(prop in target) return target[prop]; return function() { let args = [].slice.call(arguments); return new Promise(function(resolve, reject){ client.methodCall(prop, args, function (err, data){ if(err) return reject(err); resolve(data); }) }); } } }); Object.setPrototypeOf(this, smartClient); } //上面與rpc method name無關 async function main(){ let testClient = new TestClient('http://127.0.0.1:8090/'); console.log('3 is even?', await testClient.is_even(3)); console.log('100 is even?', await testClient.is_even(100)); } main().catch(console.log);
利用Javascript的Proxy可以讓整個api call變得非常簡潔,很可惜在C++這樣static typing的語言中沒有這樣的機制,因此在C++比較常見的作法是定義interface,再用code generator產生proxy stub