在dojo中使用JSON-RPC

 
在dojo中使用JSON-RPC
1        JSON-RPC规范
JSON-RPC 是一种轻量级远程过程调用协议,类似JAVA的RMI和.NET中的Remoting。在此协议中,通讯双方的请求对象和响应对象使用JSON编码方式,通过前面的“JSON编码简介”可以简单了解其编码规则。
 
1.1      请求,响应和通知对象
首先客户端向远程服务器发出远程调用请求,该请求对象具有三个属性的:
 
method – 远程调用的方法名称的字符串。
params – 方法参数数组。
id –   请求 ID 。可以为任何类型,用于将响应与其应答的请求相匹配。
 
服务端的响应对象也具有三个属性:
 
result - 被调用方法返回的结果对象。在调用该方法时发生错误时必须为 null
error - error 对象。如果在调用方法时设置,没有发生错误时必须为 null
id - 它必须是与响应的请求相同的 ID
 
通知是不需要响应的特殊请求类型。通知对象与请求对象基本相同,唯一的一点区别通知的id必须为 null。
 
1.2      通讯协议
JSON-RPC没有规定具体的传输协议,可以在普通的TCP/IP socket通讯中使用,也可以使用HTTP协议。
 
在socket通讯中,序列化的请求和响应对象通过字节流的方式在通讯双方传递。请求和响应可以在任意时间发起,除了通知外,通讯双方必须对所有请求对象进行响应。一个响应只能发送到一个请求方。在关闭连接时,所有通讯双方未响应的请求必须抛出异常。无效的请求或响应必须引起连接关闭。
 
在带有某些限制的情况下,可以使用HTTP协议进行通讯。通讯双方的会话,包括一个HTTP客户端和一个HTTP服务器,可能跨越多个HTTP请求。客户端可以使用一个HTTP POST请求向对方发送一个或多个序列化的请求、通知或响应对象。服务器端必须对请求对象进行响应,也可以发送自己的通知或请求对象。客户端必须使用另一个HTTP POST对接收到的请求对象进行响应。为了实现服务器端向客户端的主动通讯,客户端可以发送空的HTTP POST以重新建立连接。无效请求将导致连接关闭。在客户端,一个无效的响应将对所有没有应答的请求抛出异常,关闭连接也将对所有没有应答的请求抛出异常。
 
1.3      会话举例
简单的会话举例如下:
调用服务器echo方法,参数Hello JSON-RPC ,id为1
--> { "method": "echo", "params": ["Hello JSON-RPC"], "id": 1}
服务器响应结果为Hello JSON-RPC,对应id为1,没有错误
<-- { "result": "Hello JSON-RPC", "error": null, "id": 1}
 
带有通知的通讯会话
...
--> {"method": "postMessage", "params": ["Hello all!"], "id": 99}
<-- {"result": 1, "error": null, "id": 99}
<-- {"method": "handleMessage", "params": ["user1", "we were just talking"], "id": null}
<-- {"method": "handleMessage", "params": ["user3", "sorry, gotta go now, ttyl"], "id": null}
--> {"method": "postMessage", "params": ["I have a question:"], "id": 101}
<-- {"method": "userLeft", "params": ["user3"], "id": null}
<-- {"result": 1, "error": null, "id": 101}
...
 
2        Dojo对JSON-RPC的支持
2.1      dojo.io.blind 介绍
dojo.io包中提供了对XMLHTTP和一些其他更复杂的传输结构的支持。在dojo.io 包中一般最常使用的是dojo.io.bind()方法。dojo.io.blind()是一个标准的异步的请求API,它包含了各种传输层(transport layers),包括Iframe请求、XMLHTTP、mod_pubsub、LivePage等等。Dojo会根据当前的请求选择最合适的传输方法。下面的代码创建了一个请求(request),这个请求会从指定的URL返回字符串,并且指定了一个处理函数。
dojo.io.bind({
    url:  " http://foo.bar.com/sampleData.txt " ,
    load:  function (type, data, evt){  /* do something with the data  */  },
    mimetype:  " text/plain "
});
 
如果在请求过程中出错了怎么办呢?我们可以再指定一个错误处理函数:
dojo.io.bind({
    url: "http://foo.bar.com/sampleData.txt",
    load: function(type, data, evt){ /*do something with the data */ },
    error: function(type, error){ /*do something with the error*/ },
    mimetype: "text/plain"
});

同样也可以只创建一个单独的函数,然后在内部根据返回类型来进行不同的处理:
dojo.io.bind({
    url: "http://foo.bar.com/sampleData.txt",
    handle: function(type, data, evt){
        if(type == "load"){
            // do something with the data object
        }else if(type == "error"){
            // here, "data" is our error object
            // respond to the error here
        }else{
            // other types of events might get passed, handle them here
        }
    },
    mimetype: "text/plain"
});
 
下面的代码提交一段javascript程序段,然后让服务器运行它,一般我们这么做是为了加速程序运行,注意mimetype:
dojo.io.bind({
    url: "http://foo.bar.com/sampleData.js",
    load: function(type, evaldObj){ /* do something */ },
    mimetype: "text/javascript"
});
 
如果想确保程序使用XMLHTTP,可以指定传输协议类型:
dojo.io.bind({
    url: "http://foo.bar.com/sampleData.js",
    load: function(type, evaldObj){ /* do something */ },
    mimetype: "text/plain", // get plain text, don't eval()
    transport: "XMLHTTPTransport"
});
 
dojo.io.bind()同样支持来自于表单提交的数据。
dojo.io.bind({
    url: "http://foo.bar.com/processForm.cgi",
    load: function(type, evaldObj){ /* do something */ },
    formNode: document.getElementById("formToSubmit")
});
 
2.2      RPC
Dojo通过dojo.io.bind提供了简单,强大的方法使用多种多样的I/O functions。但是在开发过程中,程序员会调用很多很多I/O,这同时会给服务器和客户端加重负担。Dojo的RPC功能就是为了减少开发负担,使代码更加精简易用而产生的。Dojo不仅提供了基本的RPC client包,而且还扩展了它,使它支持JSON-RPC服务和YAHOO服务。假定有一个需要调用服务器端程序的小程序,假设要调用add(x,y)和subtract(x,y)。在没有特殊情况的条件下,客户端会这样写:
add = function(x,y) {
    request = {x: x, y: y};
    dojo.io.bind({
            url: "add.php",
            load: onAddResults,
            mimetype: "text/plain",
        content: request
    });
}

subtract = function(x,y) {
    request = {x: x, y: y};
        dojo.io.bind({
            url: "subract",
            load: onSubtractResults,
            mimetype: "text/plain"
        content: request
    });
}
 
这不是很难。但是这只是一个非常简单的程序。如果要调用在服务器上30个不同method会怎么样呢?开发人员可能要重复的写几乎一样的代码一遍又一遍,每次都要创建一个请求类(request object),设定URL,设定变量等等。这不仅容易出错,而且还很枯燥。Dojo的RPC客户端简化了这个过程,首先创建一个服务定义文件testService.smd,SMD(Simple Method Description )是远程调用接口的描述文件,可以视为简化的WSDL文件
{
    "serviceType": "JSON-RPC", 
    "serviceURL": "rpcProcessor.php", 
    "methods":[ 
        {
            "name": "add", 
            "parameters":[
                {"name": "x"},
                {"name": "y"}    
            ]
        },
        {
            "name": "subtract", 
            "parameters":[
                {"name": "x"},
                {"name": "y"}    
            ]
        }

    ]
}
 
以上就是对于远程对象接口定义。一旦定义创建完毕,要使用服务器的方法则可以首先根据服务接口定义创建远程对象,然后调用相应的方法:
var myObject = new dojo.rpc.JsonService(testService.smd);
myObject.add(3,5);
 
服务器端的myObject.add()会返回一个延缓类(deferred object)。延缓类允许开发者根据返回数据的类型附加一个或更多的回叫(callbacks)和错误处理(errbacks)。这里有一个简单的例子:
var myDeferred = myObject.add(3,5);
myDeferred.addCallback(contentCallBack);
 
或者象最后的例子中,在一条语句中完成:
testClass.contentC().addCallbacks(contentCallBack,contentErrBack);
 
在dojo中,可以随意添加回叫方法(callback),它们会按照定义的顺序被调用。
以上的例子都是基于dojo.rpc.JsonService的。Dojo中还可以使用dojo.rpc.YahooService,规范和结构都是一样的。这两个类都是继承了dojo.rpc.RpcService。
 
3        具体的例子
该例子从dojo提供的rpc例子中改进而来,修改了一些错误,增加了返回JSON对象的函数。其服务端包括两个PHP脚本,以及JSON的PHP实现。客户端包括两个SMD定义和一个HTML页面。当然还应当有dojo的脚本以及JSON的javascript实现。
3.1      服务端
实际的服务类,其中myecho为简单的回显函数,contentB返回固定的contentB字符串,contentC返回一段JSON编码的对象,在客户端可以使用JSON进行解码以得到一个响应对象。这就演示了如何通过JSON-RPC传送复杂的对象。add实现了简单的加法。
<?php
class testClass {
 
       function myecho ($somestring) {
              return $somestring;
       }
 
       function contentB () {
              return "Content B";
       }
 
       function contentC () {
              return '{"user":"firefight", "password":"sorry, I can not tell you"}';
       }
 
       function add($x,$y) {
              return $x + $y;
       }
}
?>
服务中介
负责实例化服务类,接收客户端请求,使用JSON反序列化,然后根据请求内容调用正确的服务类方法。在程序中,如果客户端调用的是triggerRpcError,则返回一个带有错误的响应对象,模拟了服务器处理失败的情况。
<?php
       require_once("./JSON.php");
      
       // ensure that we don't try to send "html" down to the client
       header("Content-Type: text/plain");
       $json = new Services_JSON;
      
       //Get request object
       $req = $json->decode($HTTP_RAW_POST_DATA);
 
       include("./testClass.php");
      
       $testObject = new testClass();
      
       //Prepare results
       $results = array();
       $results['error'] = null;
      
       $method = $req->method;
       if ($method != "triggerRpcError") {
              $ret = call_user_func_array(array($testObject,$method),$req->params);
              $results['result'] = $ret;
       } else {
              $results['error'] = "Triggered RPC Error test";
       }
      
       //Set result id
       $results['id'] = $req->id;
 
       $encoded = $json->encode($results);
 
       print $encoded;
?>
3.2      客户端
 
带有一个testClass接口描述的SMD定义。
{
       "SMDVersion":".1",
       "objectName":"testClass",
       "serviceType":"JSON-RPC",
       "serviceURL":"test_JsonRPC.php",
       "methods":[
              {
                     "name":"myecho",
                     "parameters":[
                            {
                                   "name":"somestring",
                                   "type":"STRING"
                            }
                     ]
              },
              {
                     "name":"contentB"
              },
              {
                     "name":"contentC"
              },
              {
                     "name":"add",
                     "parameters":[
                            {
                                   "name":"x",
                                   "type":"STRING"
                            },
                            {
                                   "name":"y",
                                   "type":"STRING"
                            }
                     ]
              },
              {
                     "name":"triggerRpcError"
              },
 
       ]
}
 
为了测试连接错误的情况,创建一个带有无效URL地址的SMD文件,其中包括对erroringClass的接口描述,当然对erroringClass的远程调用会在连接时就失败。
{
       "SMDVersion":".1",
       "objectName":"erroringClass",
       "serviceType":"JSON-RPC",
       "serviceURL":"badUrlForTesting",
       "methods":[
              {
                     "name":"content"
              },
       ]
}
 
客户页面,提供按钮和结果显示。
<script type="text/javascript">
        var djConfig = {isDebug: true,debugContainerId: "dojoDebug" };
        //djConfig.debugAtAllCosts = true;
</script>
 
<script type="text/javascript" src="../dojo/dojo.js"></script>
<script type="text/javascript" src="../common/json.js"></script>
 
<script type="text/javascript">
       dojo.require("dojo.debug.Firebug");
       dojo.require("dojo.widget.*");
       dojo.require("dojo.widget.Button");
       dojo.require("dojo.rpc.JsonService");
       dojo.require("dojo.rpc.Deferred");
 
       function contentCallBack(result) {
              var handlerNode = document.getElementById("ReturnedContent");
              handlerNode.innerHTML = "<p>" + result + "</p>" ;
       }
 
       function contentErrBack(obj) {
              dojo.debug(obj);
              var handlerNode = document.getElementById("ReturnedContent");
              handlerNode.innerHTML = "<p> Error! Please refer to debug below! </p>" ;
       }
 
       function callBackForContentC(result)
       {
              var r = JSON.parse(result);
              var str = "User: " + r.user + "; Password: " + r.password;
              var handlerNode = document.getElementById("ReturnedContent");
              handlerNode.innerHTML = "<p>" + str + "</p>" ;
       }
      
       var testClass = new dojo.rpc.JsonService("testClass.smd");
       var erroringClass=new dojo.rpc.JsonService("erroringClass.smd");
</script>
 
</head>
 
<body>
<div id="RPC">
       <h2>Push these buttons to execute code in the button.</h2>
       <h3>Results will be returned and show in "Returned Content"</h3>
</div>
 
<br>
 
<div id="ReturnedContent">
<p>None.</p>
</div>
 
<br>
 
<div class="box">
       <button dojoType="Button" οnclick='testClass.myecho("blah").addCallbacks(contentCallBack,contentErrBack);'>
              Echo blah
       </button>
       <button dojoType="Button" οnclick='testClass.contentB().addCallbacks(contentCallBack,contentErrBack);'>
              ContentB()
       </button>
       <button dojoType="Button" οnclick='testClass.contentC().addCallbacks(callBackForContentC,contentErrBack);'>
              Get Object
       </button>
       <button dojoType="Button" οnclick='testClass.add(5,6).addCallbacks(contentCallBack,contentErrBack);'>
              5+6=?
       </button>
       <button dojoType="Button" οnclick='testClass.triggerRpcError().addCallbacks(contentCallBack,contentErrBack);'>
              Error from server
       </button>
       <button dojoType="Button" οnclick='erroringClass.content().addCallbacks(contentCallBack,contentErrBack);'>
              Error from client
       </button>
</div>
 
<br clear=both>
 
<div id="dojoDebug">
<h2>Debug Log:</h2>      
</div>
 
</head>
</html>
 
参考资料:
JSON-RPC Specifications
http://json-rpc.org/wiki/specification
Dojo book 以及burnet的中文翻译
http://www.blogjava.net/burnet/
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值