xml-rpc整理 (XML remote procedure call) – introduction

在跨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

This entry was posted in Network. Bookmark the permalink.

Leave a Reply