在dojo中使用JSON-RPC
1 JSON-RPC规范
JSON-RPC 是一种轻量级远程过程调用协议,类似JAVA的RMI和.NET中的Remoting。在此协议中,通讯双方的请求对象和响应对象使用JSON编码方式,通过前面的“JSON编码简介”可以简单了解其编码规则。
1.1 请求,响应和通知对象
params –
方法参数数组。
id –
请求
ID
。可以为任何类型,用于将响应与其应答的请求相匹配。
服务端的响应对象也具有三个属性:
id -
它必须是与响应的请求相同的
ID
。
1.2 通讯协议
JSON-RPC没有规定具体的传输协议,可以在普通的TCP/IP socket通讯中使用,也可以使用HTTP协议。
在socket通讯中,序列化的请求和响应对象通过字节流的方式在通讯双方传递。请求和响应可以在任意时间发起,除了通知外,通讯双方必须对所有请求对象进行响应。一个响应只能发送到一个请求方。在关闭连接时,所有通讯双方未响应的请求必须抛出异常。无效的请求或响应必须引起连接关闭。
在带有某些限制的情况下,可以使用HTTP协议进行通讯。通讯双方的会话,包括一个HTTP客户端和一个HTTP服务器,可能跨越多个HTTP请求。客户端可以使用一个HTTP POST请求向对方发送一个或多个序列化的请求、通知或响应对象。服务器端必须对请求对象进行响应,也可以发送自己的通知或请求对象。客户端必须使用另一个HTTP POST对接收到的请求对象进行响应。为了实现服务器端向客户端的主动通讯,客户端可以发送空的HTTP POST以重新建立连接。无效请求将导致连接关闭。在客户端,一个无效的响应将对所有没有应答的请求抛出异常,关闭连接也将对所有没有应答的请求抛出异常。
1.3 会话举例
简单的会话举例如下:
-->{"method":"echo","params":["HelloJSON-RPC"],"id":1}
服务器响应结果为HelloJSON-RPC,对应id为1,没有错误
<--{"result":"HelloJSON-RPC","error":null,"id":1}
带有通知的通讯会话
...
-->{"method":"postMessage","params":["Helloall!"],"id":99}
<--{"result":1,"id":99}
<--{"method":"handleMessage","params":["user1","wewerejusttalking"],"id":null}
<--{"method":"handleMessage","params":["user3","sorry,gottagonow,ttyl"],"id":null}
-->{"method":"postMessage","params":["Ihaveaquestion:"],"id":101}
<--{"method":"userLeft","params":["user3"],"id":null}
<--{"result":1,"id":101}
...
-->{"method":"postMessage","params":["Helloall!"],"id":99}
<--{"result":1,"id":99}
<--{"method":"handleMessage","params":["user1","wewerejusttalking"],"id":null}
<--{"method":"handleMessage","params":["user3","sorry,gottagonow,ttyl"],"id":null}
-->{"method":"postMessage","params":["Ihaveaquestion:"],"id":101}
<--{"method":"userLeft","params":["user3"],"id":null}
<--{"result":1,"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){ /* dosomethingwith thedata */ },
mimetype: " text/plain "
});
url: " http://foo.bar.com/sampleData.txt ",
load: function (type,data,evt){ /* dosomethingwith thedata */ },
mimetype: " text/plain "
});
dojo.io.bind({
url:"http://foo.bar.com/sampleData.txt",
load:function(type,evt){/*dosomethingwith thedata*/},
error:function(type,error){/*dosomethingwiththeerror*/},
mimetype:"text/plain"
});
url:"http://foo.bar.com/sampleData.txt",
load:function(type,evt){/*dosomethingwith thedata*/},
error:function(type,error){/*dosomethingwiththeerror*/},
mimetype:"text/plain"
});
同样也可以只创建一个单独的函数,然后在内部根据返回类型来进行不同的处理:
dojo.io.bind({
url:"http://foo.bar.com/sampleData.txt",
handle:function(type,evt){
if(type=="load"){
//dosomethingwiththedataobject
}elseif(type=="error"){
//here,"data"isourerrorobject
//respondtotheerrorhere
}else{
//othertypesofeventsmightgetpassed,handlethemhere
}
},
mimetype:"text/plain"
});
url:"http://foo.bar.com/sampleData.txt",
handle:function(type,evt){
if(type=="load"){
//dosomethingwiththedataobject
}elseif(type=="error"){
//here,"data"isourerrorobject
//respondtotheerrorhere
}else{
//othertypesofeventsmightgetpassed,handlethemhere
}
},
mimetype:"text/plain"
});
下面的代码提交一段javascript程序段,然后让服务器运行它,一般我们这么做是为了加速程序运行,注意mimetype:
dojo.io.bind({
url:"http://foo.bar.com/sampleData.js",evaldObj){/*dosomething*/},
mimetype:"text/javascript"
});
url:"http://foo.bar.com/sampleData.js",evaldObj){/*dosomething*/},
mimetype:"text/javascript"
});
如果想确保程序使用XMLHTTP,可以指定传输协议类型:
dojo.io.bind({
url:"http://foo.bar.com/sampleData.js",
mimetype:"text/plain",//getplaintext,don'teval()
transport:"XMLHTTPTransport"
});
url:"http://foo.bar.com/sampleData.js",
mimetype:"text/plain",//getplaintext,don'teval()
transport:"XMLHTTPTransport"
});
dojo.io.bind()同样支持来自于表单提交的数据。
dojo.io.bind({
url:"http://foo.bar.com/processForm.cgi",
formNode:document.getElementById("formToSubmit")
});
url:"http://foo.bar.com/processForm.cgi",
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,
content:request
});
}
subtract=function(x,y:y};
dojo.io.bind({
url:"subract",
load:onSubtractResults,
mimetype:"text/plain"
content:request
});
}
request={x:x,y:y};
dojo.io.bind({
url:"add.PHP",
load:onAddResults,
content:request
});
}
subtract=function(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",
{"name":"y"}
]
}
]
}
"serviceType":"JSON-RPC",
"serviceURL":"rpcProcessor.PHP",
"methods":[
{
"name":"add",
"parameters":[
{"name":"x"},
{"name":"y"}
]
},
{
"name":"subtract",
{"name":"y"}
]
}
]
}
varmyObject=newdojo.rpc.JsonService(testService.smd);
myObject.add(3,5);
服务器端的myObject.add()会返回一个延缓类(deferred object)。延缓类允许开发者根据返回数据的类型附加一个或更多的回叫(callbacks)和错误处理(errbacks)。这里有一个简单的例子:
varmyDeferred=myObject.add(3,5);
myDeferred.addCallback(contentCallBack);
myDeferred.addCallback(contentCallBack);
或者象最后的例子中,在一条语句中完成:
testClass.contentC().addCallbacks(contentCallBack,contentErrBack);
以上的例子都是基于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",
{
"name":"triggerRpcError"
},
]
}
{
"SMDVersion":".1",
"objectName":"erroringClass",
"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" onclick='testClass.myecho("blah").addCallbacks(contentCallBack,contentErrBack);'>
Echo blah
</button>
<button dojoType="Button" onclick='testClass.contentB().addCallbacks(contentCallBack,contentErrBack);'>
ContentB()
</button>
<button dojoType="Button" onclick='testClass.contentC().addCallbacks(callBackForContentC,contentErrBack);'>
Get Object
</button>
<button dojoType="Button" onclick='testClass.add(5,6).addCallbacks(contentCallBack,contentErrBack);'>
5+6=?
</button>
<button dojoType="Button" onclick='testClass.triggerRpcError().addCallbacks(contentCallBack,contentErrBack);'>
Error from server
</button>
<button dojoType="Button" onclick='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/
原文链接:https://www.f2er.com/dojo/292001.html