您的位置:首页 > Web前端 > JavaScript

在dojo中使用JSON-RPC

2006-12-15 10:47 453 查看
 
在dojo中使用JSON-RPC
1        JSON-RPC规范
JSON-RPC 是一种轻量级远程过程调用协议,类似JAVA的RMI和.NET中的Remoting。在此协议中,通讯双方的请求对象和响应对象使用JSON编码方式,通过前面的“JSON编码简介”可以简单了解其编码规则。
 
1.1      请求,响应和通知对象
首先客户端向远程服务器发出远程调用请求,该请求对象具有三个属性的:
 
[align=left]method – 远程调用的方法名称的字符串。[/align]
[align=left]params – 方法参数数组。[/align]
[align=left]id –  请求 ID。可以为任何类型,用于将响应与其应答的请求相匹配。[/align]
 
服务端的响应对象也具有三个属性:
 
[align=left]result - 被调用方法返回的结果对象。在调用该方法时发生错误时必须为null。[/align]
[align=left]error - error 对象。如果在调用方法时设置,没有发生错误时必须为 null。[/align]
[align=left]id - 它必须是与响应的请求相同的 ID。[/align]
 
通知是不需要响应的特殊请求类型。通知对象与请求对象基本相同,唯一的一点区别通知的id必须为 null。
 
1.2      通讯协议
JSON-RPC没有规定具体的传输协议,可以在普通的TCP/IP socket通讯中使用,也可以使用HTTP协议。
 
在socket通讯中,序列化的请求和响应对象通过字节流的方式在通讯双方传递。请求和响应可以在任意时间发起,除了通知外,通讯双方必须对所有请求对象进行响应。一个响应只能发送到一个请求方。在关闭连接时,所有通讯双方未响应的请求必须抛出异常。无效的请求或响应必须引起连接关闭。
 
在带有某些限制的情况下,可以使用HTTP协议进行通讯。通讯双方的会话,包括一个HTTP客户端和一个HTTP服务器,可能跨越多个HTTP请求。客户端可以使用一个HTTP POST请求向对方发送一个或多个序列化的请求、通知或响应对象。服务器端必须对请求对象进行响应,也可以发送自己的通知或请求对象。客户端必须使用另一个HTTP POST对接收到的请求对象进行响应。为了实现服务器端向客户端的主动通讯,客户端可以发送空的HTTP POST以重新建立连接。无效请求将导致连接关闭。在客户端,一个无效的响应将对所有没有应答的请求抛出异常,关闭连接也将对所有没有应答的请求抛出异常。
 
1.3      会话举例
简单的会话举例如下:
[align=left]调用服务器echo方法,参数Hello JSON-RPC ,id为1[/align]
[align=left]--> { "method": "echo", "params": ["Hello JSON-RPC"], "id": 1}[/align]
[align=left]服务器响应结果为Hello JSON-RPC,对应id为1,没有错误[/align]
[align=left]<-- { "result": "Hello JSON-RPC", "error": null, "id": 1}[/align]
 
带有通知的通讯会话
[align=left]...
--> {"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}
...[/align]
 
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返回字符串,并且指定了一个处理函数。
[align=left]dojo.io.bind({
    url:  " http://foo.bar.com/sampleData.txt " ,
    load:  function (type, data, evt){  /* do something with the data  */  },
    mimetype:  " text/plain "
}); [/align]
 
如果在请求过程中出错了怎么办呢?我们可以再指定一个错误处理函数:
[align=left]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"
});[/align]

同样也可以只创建一个单独的函数,然后在内部根据返回类型来进行不同的处理:
[align=left]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"
});[/align]
 
下面的代码提交一段javascript程序段,然后让服务器运行它,一般我们这么做是为了加速程序运行,注意mimetype:
[align=left]dojo.io.bind({
    url: "http://foo.bar.com/sampleData.js",
    load: function(type, evaldObj){ /* do something */ },
    mimetype: "text/javascript"
});[/align]
 
如果想确保程序使用XMLHTTP,可以指定传输协议类型:
[align=left]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"
});[/align]
 
dojo.io.bind()同样支持来自于表单提交的数据。
[align=left]dojo.io.bind({
    url: "http://foo.bar.com/processForm.cgi",
    load: function(type, evaldObj){ /* do something */ },
    formNode: document.getElementById("formToSubmit")
});[/align]
 
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)。在没有特殊情况的条件下,客户端会这样写:
[align=left]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
    });
}[/align]
 
这不是很难。但是这只是一个非常简单的程序。如果要调用在服务器上30个不同method会怎么样呢?开发人员可能要重复的写几乎一样的代码一遍又一遍,每次都要创建一个请求类(request object),设定URL,设定变量等等。这不仅容易出错,而且还很枯燥。Dojo的RPC客户端简化了这个过程,首先创建一个服务定义文件testService.smd,SMD(Simple Method Description )是远程调用接口的描述文件,可以视为简化的WSDL文件
[align=left]{
    "serviceType": "JSON-RPC", 
    "serviceURL": "rpcProcessor.php", 
    "methods":[ 
        {
            "name": "add", 
            "parameters":[
                {"name": "x"},
                {"name": "y"}    
            ]
        },
        {
            "name": "subtract", 
            "parameters":[
                {"name": "x"},
                {"name": "y"}    
            ]
        }

    ]
}[/align]
 
以上就是对于远程对象接口定义。一旦定义创建完毕,要使用服务器的方法则可以首先根据服务接口定义创建远程对象,然后调用相应的方法:
[align=left]var myObject = new dojo.rpc.JsonService(testService.smd);[/align]
[align=left]myObject.add(3,5);[/align]
 
服务器端的myObject.add()会返回一个延缓类(deferred object)。延缓类允许开发者根据返回数据的类型附加一个或更多的回叫(callbacks)和错误处理(errbacks)。这里有一个简单的例子:
[align=left]var myDeferred = myObject.add(3,5);
myDeferred.addCallback(contentCallBack);[/align]
 
或者象最后的例子中,在一条语句中完成:
[align=left]testClass.contentC().addCallbacks(contentCallBack,contentErrBack);[/align]
 
在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实现了简单的加法。
[align=left]<?php[/align]
[align=left]class testClass {[/align]
[align=left] [/align]
[align=left]       function myecho ($somestring) {[/align]
[align=left]              return $somestring;[/align]
[align=left]       }[/align]
[align=left] [/align]
[align=left]       function contentB () {[/align]
[align=left]              return "Content B";[/align]
[align=left]       }[/align]
[align=left] [/align]
[align=left]       function contentC () {[/align]
[align=left]              return '{"user":"firefight", "password":"sorry, I can not tell you"}';[/align]
[align=left]       }[/align]
[align=left] [/align]
[align=left]       function add($x,$y) {[/align]
[align=left]              return $x + $y;[/align]
[align=left]       }[/align]
[align=left]}[/align]
[align=left]?>[/align]
服务中介
负责实例化服务类,接收客户端请求,使用JSON反序列化,然后根据请求内容调用正确的服务类方法。在程序中,如果客户端调用的是triggerRpcError,则返回一个带有错误的响应对象,模拟了服务器处理失败的情况。
[align=left]<?php[/align]
[align=left]       require_once("./JSON.php");[/align]
[align=left]       [/align]
[align=left]       // ensure that we don't try to send "html" down to the client[/align]
[align=left]       header("Content-Type: text/plain");[/align]
[align=left]       $json = new Services_JSON;[/align]
[align=left]       [/align]
[align=left]       //Get request object[/align]
[align=left]       $req = $json->decode($HTTP_RAW_POST_DATA);[/align]
[align=left] [/align]
[align=left]       include("./testClass.php");[/align]
[align=left]       [/align]
[align=left]       $testObject = new testClass();[/align]
[align=left]       [/align]
[align=left]       //Prepare results[/align]
[align=left]       $results = array();[/align]
[align=left]       $results['error'] = null;[/align]
[align=left]       [/align]
[align=left]       $method = $req->method;[/align]
[align=left]       if ($method != "triggerRpcError") {[/align]
[align=left]              $ret = call_user_func_array(array($testObject,$method),$req->params);[/align]
[align=left]              $results['result'] = $ret;[/align]
[align=left]       } else {[/align]
[align=left]              $results['error'] = "Triggered RPC Error test";[/align]
[align=left]       }[/align]
[align=left]       [/align]
[align=left]       //Set result id[/align]
[align=left]       $results['id'] = $req->id;[/align]
[align=left] [/align]
[align=left]       $encoded = $json->encode($results);[/align]
[align=left] [/align]
[align=left]       print $encoded;[/align]
[align=left]?>[/align]
3.2      客户端
 
带有一个testClass接口描述的SMD定义。
[align=left]{[/align]
[align=left]       "SMDVersion":".1",[/align]
[align=left]       "objectName":"testClass",[/align]
[align=left]       "serviceType":"JSON-RPC",[/align]
[align=left]       "serviceURL":"test_JsonRPC.php",[/align]
[align=left]       "methods":[[/align]
[align=left]              {[/align]
[align=left]                     "name":"myecho",[/align]
[align=left]                     "parameters":[[/align]
[align=left]                            {[/align]
[align=left]                                   "name":"somestring",[/align]
[align=left]                                   "type":"STRING"[/align]
[align=left]                            }[/align]
[align=left]                     ][/align]
[align=left]              },[/align]
[align=left]              {[/align]
[align=left]                     "name":"contentB"[/align]
[align=left]              },[/align]
[align=left]              {[/align]
[align=left]                     "name":"contentC"[/align]
[align=left]              },[/align]
[align=left]              {[/align]
[align=left]                     "name":"add",[/align]
[align=left]                     "parameters":[[/align]
[align=left]                            {[/align]
[align=left]                                   "name":"x",[/align]
[align=left]                                   "type":"STRING"[/align]
[align=left]                            },[/align]
[align=left]                            {[/align]
[align=left]                                   "name":"y",[/align]
[align=left]                                   "type":"STRING"[/align]
[align=left]                            }[/align]
[align=left]                     ][/align]
[align=left]              },[/align]
[align=left]              {[/align]
[align=left]                     "name":"triggerRpcError"[/align]
[align=left]              },[/align]
[align=left] [/align]
[align=left]       ][/align]
[align=left]}[/align]
 
为了测试连接错误的情况,创建一个带有无效URL地址的SMD文件,其中包括对erroringClass的接口描述,当然对erroringClass的远程调用会在连接时就失败。
[align=left]{[/align]
[align=left]       "SMDVersion":".1",[/align]
[align=left]       "objectName":"erroringClass",[/align]
[align=left]       "serviceType":"JSON-RPC",[/align]
[align=left]       "serviceURL":"badUrlForTesting",[/align]
[align=left]       "methods":[[/align]
[align=left]              {[/align]
[align=left]                     "name":"content"[/align]
[align=left]              },[/align]
[align=left]       ][/align]
}
 
客户页面,提供按钮和结果显示。
[align=left]<script type="text/javascript">[/align]
[align=left]        var djConfig = {isDebug: true,debugContainerId: "dojoDebug" };[/align]
[align=left]        //djConfig.debugAtAllCosts = true;[/align]
[align=left]</script>[/align]
[align=left] [/align]
[align=left]<script type="text/javascript" src="../dojo/dojo.js"></script>[/align]
[align=left]<script type="text/javascript" src="../common/json.js"></script>[/align]
[align=left] [/align]
[align=left]<script type="text/javascript">[/align]
[align=left]       dojo.require("dojo.debug.Firebug");[/align]
[align=left]       dojo.require("dojo.widget.*");[/align]
[align=left]       dojo.require("dojo.widget.Button");[/align]
[align=left]       dojo.require("dojo.rpc.JsonService");[/align]
[align=left]       dojo.require("dojo.rpc.Deferred");[/align]
[align=left] [/align]
[align=left]       function contentCallBack(result) {[/align]
[align=left]              var handlerNode = document.getElementById("ReturnedContent");[/align]
[align=left]              handlerNode.innerHTML = "<p>" + result + "</p>" ;[/align]
[align=left]       }[/align]
[align=left] [/align]
[align=left]       function contentErrBack(obj) {[/align]
[align=left]              dojo.debug(obj);[/align]
[align=left]              var handlerNode = document.getElementById("ReturnedContent");[/align]
[align=left]              handlerNode.innerHTML = "<p> Error! Please refer to debug below! </p>" ;[/align]
[align=left]       }[/align]
[align=left] [/align]
[align=left]       function callBackForContentC(result)[/align]
[align=left]       {[/align]
[align=left]              var r = JSON.parse(result);[/align]
[align=left]              var str = "User: " + r.user + "; Password: " + r.password;[/align]
[align=left]              var handlerNode = document.getElementById("ReturnedContent");[/align]
[align=left]              handlerNode.innerHTML = "<p>" + str + "</p>" ;[/align]
[align=left]       }[/align]
[align=left]       [/align]
[align=left]       var testClass = new dojo.rpc.JsonService("testClass.smd");[/align]
[align=left]       var erroringClass=new dojo.rpc.JsonService("erroringClass.smd");[/align]
[align=left]</script>[/align]
[align=left] [/align]
[align=left]</head>[/align]
[align=left] [/align]
[align=left]<body>[/align]
[align=left]<div id="RPC">[/align]
[align=left]       <h2>Push these buttons to execute code in the button.</h2>[/align]
[align=left]       <h3>Results will be returned and show in "Returned Content"</h3>[/align]
[align=left]</div>[/align]
[align=left] [/align]
[align=left]<br>[/align]
[align=left] [/align]
[align=left]<div id="ReturnedContent">[/align]
[align=left]<p>None.</p>[/align]
[align=left]</div>[/align]
[align=left] [/align]
[align=left]<br>[/align]
[align=left] [/align]
[align=left]<div class="box">[/align]
[align=left]       <button dojoType="Button" onclick='testClass.myecho("blah").addCallbacks(contentCallBack,contentErrBack);'>[/align]
[align=left]              Echo blah[/align]
[align=left]       </button>[/align]
[align=left]       <button dojoType="Button" onclick='testClass.contentB().addCallbacks(contentCallBack,contentErrBack);'>[/align]
[align=left]              ContentB()[/align]
[align=left]       </button>[/align]
[align=left]       <button dojoType="Button" onclick='testClass.contentC().addCallbacks(callBackForContentC,contentErrBack);'>[/align]
[align=left]              Get Object[/align]
[align=left]       </button>[/align]
[align=left]       <button dojoType="Button" onclick='testClass.add(5,6).addCallbacks(contentCallBack,contentErrBack);'>[/align]
[align=left]              5+6=?[/align]
[align=left]       </button>[/align]
[align=left]       <button dojoType="Button" onclick='testClass.triggerRpcError().addCallbacks(contentCallBack,contentErrBack);'>[/align]
[align=left]              Error from server[/align]
[align=left]       </button>[/align]
[align=left]       <button dojoType="Button" onclick='erroringClass.content().addCallbacks(contentCallBack,contentErrBack);'>[/align]
[align=left]              Error from client[/align]
[align=left]       </button>[/align]
[align=left]</div>[/align]
[align=left] [/align]
[align=left]<br clear=both>[/align]
[align=left] [/align]
[align=left]<div id="dojoDebug">[/align]
[align=left]<h2>Debug Log:</h2>      [/align]
[align=left]</div>[/align]
[align=left] [/align]
[align=left]</head>[/align]
[align=left]</html>[/align]
 
参考资料:
JSON-RPC Specifications http://json-rpc.org/wiki/specification Dojo book 以及burnet的中文翻译
       http://manual.dojotoolkit.org/WikiHome/DojoDotBook http://www.blogjava.net/burnet/  
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息