Dojo 这样一个完善且强大的 Web 开发控件库,实现了一套基于 RPC 的工具包,使得需要用到 RPC 技术的开发工程师们可以通过 Dojo 的这套 RPC 工具包快速的构建自己的 RPC 应用。这套工具包不仅封装了 RPC 的基本接口,参数传递方式,还提供了调用各种已存在的远程服务的示例。这篇文章将重点介绍 Dojo 的这套工具包的功能和使用方式,以及他们是如何与雅虎,谷歌,推特(twitter)等等协同工作的。
RPC 原理简介
RPC 采用客户机 / 服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。
首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息。最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
回页首
Dojo 的 RPC 接口
接下来我们会逐一介绍 Dojo 里面的关于 RPC 的前端接口,Dojo 针对 RPC 封装了很多非常方便的 API,用于适应各种情况。(注:文中会有一些后台示例代码,均为 PHP 代码)。
在使用 RPC 之前,Dojo 需要初始化 RPC 的 Service 类对象,基于此对象,我们便可以调用各式各样的远程接口。
清单 1. RPC Service 类对象初始化
dojox.rpc.tests.service = new dojox.rpc.Service(dojo.moduleUrl("dojox.rpc.tests.resources", "test.smd"));
可以看到,这里的“dojox.rpc.Service”对象初始化非常简单,绑定到一个“test.smd”文件即可(通过“dojo.moduleUrl”定位到这个 SMD 文件),所以他的精髓就在这个“test.smd”文件中。Dojo 中有很多定制好的“smd”文件,里面包含了很多服务端的配置信息,主要存放在“dojox/rpc/SMDLibrary”和“dojox/rpc/tests/resources”中,我们将要介绍的 RPC 基本接口的相关服务端配置文件主要为“dojox/rpc/tests/resources/test.smd”。
我们先来看看这个文件的内容:
清单 2. RPC 配置文件
(window["http://localhost/dojox/rpc/tests/resources/test.smd"]||function(val){ return val}) ( { transport: "POST", envelope: "URL", strictParameters: false, parameters: { 。。。。。。。。。。。。。。。。。。 }, services: { postEcho: { target: "echo.php", parameters: [ {name: "message", type: "string", optional: true} ] }, getEcho: { transport: "GET", target: "echo.php", parameters: [ {name: "message", type: "string", optional: true} ] } 。。。。。。。。。。。。。。。。。。 。。。。。。。。。。。。。。。。。。 } })
可以见到,这个配置文件其实就是一个 JSON 文件,他定义了远程调用的方法(e.g. “postEcho”,“getEcho”),包括他们的服务端地址(e.g. “echo.php”),以及方法的参数等等。当你配置好该文件后,你所需要做的仅仅是在代码中直接调用即可。我们先以普通 Post 请求为例。
普通 Post 和 Get 请求
先来看看 Dojo 的 API:
清单 3. Post 请求 RPC
this.svc = dojox.rpc.tests.service; var td = this.svc.postEcho({message: this.name,foo:2}) ; td.addCallback(this, function(result){ if (result==this.name){ d.callback(true); }else{ d.errback(new Error("Unexpected Return Value: ", result)); } });
可见,Dojo 的 RPC 接口非常简单,就是直接的方法调用:“this.svc.postEcho({message: this.name,foo:2})”,当然,你也可以通过“addCallback”来处理返回值。
这里的“postEcho”还有一种调用方式:
清单 4. Post 请求 RPC(参数顺序)
var td = this.svc.postEcho(this.name,2);
这里与清单 3 的区别主要在于参数的传递方式,Dojo 的 RPC 支持按照参数顺序传递参数,所以这里调用方式与之前清单 3 中的调用方式效果一样,Dojo 会自动将第一个参数“this.name”匹配到“message”参数值上。
Dojo 的 RPC 默认是 Post 请求,当然你也可以显示的将它变为 Get 请求:
清单 5. Get 请求的 RPC
getEcho: { transport: "GET", target: "echo.php", parameters: [ {name: "message", type: "string", optional: true} ] }, var td = this.svc.getEcho({message: this.name}); td.addCallback(this, function(result){ if (result==this.name){ d.callback(true); }else{ d.errback(new Error("Unexpected Return Value: ", result)); } });
前半段是关于 Get 请求的配置,后半段是该方法的调用,其方式与 Post 无异。主要的差别就是配置里面多了一个“transport: "GET"”,这样一来,Post 请求就变成了 Get 请求。
这里我们默认的返回值为基本字符类型“text”,也可以将它设置成为 JSON 的模式:
清单 6. Get 请求的 RPC 的 JSON 模式
getEchoJson: { transport: "GET", target: "echoJson.php", contentType:"application/json", parameters: [ {name: "message", type: "string", optional: true} ] },
可见,这里只需要修改一下配置,加上“contentType:"application/json",”即可。
你还能给你的方法加上命名空间,以方便区分:
清单 7. 方法的命名空间
"namespace.getEcho": { transport: "GET", target: "echo.php", parameters: [ {name: "message", type: "string", optional: true} ] }, var td = this.svc.namespace.getEcho(this.name); td.addCallback(this, function(result){ if (result==this.name){ d.callback(true); }else{ d.errback(new Error("Unexpected Return Value: ", result)); } });
我们可以定义诸如“namespace.getEcho”的方法,通过“this.svc.namespace.getEcho(this.name)”方式调用,这种写法不影响后台,他的功能与之前的“getEcho”相同。
REST 风格的 RPC
除了支持 Post 和 Get,Dojo 的 RPC 还支持 REST 风格的接口。REST 的接口风格主要包括 Get,Post,Put 和 Delete,其中 Get 用于取数据,Post 主要用于添加数据,Put 用于修改,Delete 用于删除。Dojo 的 RPC 也支持这种风格的 API,见如下代码示例:
清单 8. REST 风格的 RPC 接口 Put
restStore: { transport: "REST", contentType:"text/plain", preventCache: true, target: "fakestore.php", parameters: [ {name: "location", type: "string", optional: true} ] }, res = this.name + Math.random(); var td = this.svc.restStore.put({location: "res"},res); td.addCallback(this, function(result){ var td = this.svc.restStore({location: "res"}); td.addCallback(this, function(result){ if (result==res){ d.callback(true); }else{ d.errback(new Error("Unexpected Return Value: ", result)); } }); });
前半段是 REST 的 RPC 定义,注意这里的“transport: "REST"”。接下来后半段我们可以看到这里的“this.svc.restStore.put({location: "res"},res)”,这里调用“put”方法,所以他相当于是修改了 location 为“res”的对象的内容,所以当我们修改完成后在 callback 方法里面再次调用取值方法时:“this.svc.restStore({location: "res"})”,会拿到全新的“res”的内容,所以这里的“result”和“res”应该是相同的。
同样,它也支持 Post,Delete 等等方法:
清单 9. REST 风格的 RPC 接口 Post 和 Delete
var newRes = this.name + Math.random(); res += newRes; //test when given named params var td = this.svc.restStore.post({location: "res"},newRes); td.addCallback(this, function(result){ var td = this.svc.restStore({location: "res"}); td.addCallback(this, function(result){ if (result==res){ d.callback(true); }else{ d.errback(new Error("Unexpected Return Value: ", result)); } }); }); var td = this.svc.restStore['delete']({location: "res"}); td.addCallback(this, function(result){ var td = this.svc.restStore({location: "res"}); td.addCallback(this, function(result){ if (result=="deleted"){ d.callback(true); }else{ d.errback(new Error("Unexpected Return Value: ", result)); } }); });
这里的调用方式与之前的 Put 方法的方式基本一样。注意这里的“res”值:“res += newRes;”,他不是全新的值,而是在原始值上面的一个添加,完全符合 REST 里关于 Post 的定义。当我们调用“this.svc.restStore.post({location: "res"},newRes)”之后,服务端的数据同样应该被改为“原始值 +newRes”,所以前端的模拟值“res”和后台的返回值“result”仍然是一致的。
当然,这种 REST 风格的接口也是需要后端支持的,参考一下他的后端示例:
再来看看稍微复杂一点的例子:
清单 10. REST 风格 RPC 后端实现示例(PHP)
$fn = preg_replace("/\W/","",$_REQUEST["location"]); switch ($_SERVER["REQUEST_METHOD"]) { case "GET" : if (isset($_SESSION[$fn])) { print($_SESSION[$fn]); } else { $fh = fopen($fn, 'r'); print(fread($fh, filesize($fn))); fclose($fh); } break; case "PUT" : $contents = file_get_contents('php://input'); print($contents); $_SESSION[$fn]=$contents; break; case "POST" : if (isset($_SESSION[$fn])) { $old = $_SESSION[$fn]; } else { $fh = fopen($fn, 'r'); $old = fread($fh, filesize($fn)); fclose($fh); } $contents = file_get_contents('php://input'); $_SESSION[$fn]=$old . $contents; break; case "DELETE" : $_SESSION[$fn]="deleted"; break; }
可以看到,这里的服务端也是符合 REST 关于服务端实现的规则约束的,他处理了关于 Get,Post,Put 和 Delete 的不同情况。
JSONP 风格的 RPC
JSONP 是一个非官方的协议,它允许在服务器端集成 Script Tags 返回至客户端,通过 JavaScript Callback 的形式实现跨域访问。其本质就是在服务端调用前端的 JavaScript 方法,并在服务端将参数传入该前端方法中。这种方式在处理跨域请求时尤为重要。
Dojo 的 RPC 包将这种方式进行了包装,我们所要做的也仅仅是简单的调用一个方法即可:
清单 11. JSONP 风格的 RPC 前端代码示例
jsonpEcho: { transport: "JSONP", target: "jsonpEcho.php", callbackParamName: "testCallbackParam", parameters: [ {name: "message", type: "string", optional: true} ] }, var td = this.svc.jsonpEcho({message: this.name}); td.addCallback(this, function(result){ if (result==this.name){ d.callback(true); }else{ d.errback(new Error("Unexpected Return Value: ", result)); } });
前半段代码是关于 JSONP 的调用配置,注意这里的“transport: "JSONP"”和“callbackParamName: "testCallbackParam"”,这里的“callbackParamName”用于告诉服务端他需要回调的方法,这里为“testCallbackParam”。
当然,仅仅基于前端的代码,是不可能实现 JSONP 的,我们来看看他后端的代码示例:
清单 12. JSONP 风格的 RPC 后端代码示例(PHP)
$jsonp = false; $result = ""; if ($_REQUEST["testCallbackParam"]){ $jsonp=true; $result .= $_REQUEST['testCallbackParam'] . "('"; } if (!$_REQUEST["message"]){ $result .= "ERROR: message property not found"; } $result .= $_REQUEST["message"]; if ($jsonp) { $result .= "');"; } print $result;
细心的读者会发现,这里处理 JSONP 的返回值是一段函数的调用:
1. “$result .= $_REQUEST['testCallbackParam'] . "('";”
2. if ($jsonp) { $result .= "');"; }
本示例是拼接了两段字符串,构造了一段函数调用的语句,返回到客户端执行。这就是 JSONP 的实现方式。
JSON-RPC
我们先来看看 JSON-RPC 的定义:
JSON-RPC 是一个轻量级的远程调用协议。数据通讯由两部分组成:在一次连接的生命期内,一端将发出一个请求来调用另一端的函数。另一端将回应该请求,除非这个请求是一个公告。
请求部分:通过向一个远程服务器发送一个请求来调用一个远程函数。该请求是一个用 JSON 进行了编码 ( 序列化 ) 的对象。它有 3 个部分:
* 函数名(method):被调用方法名。
* 参数数组(params):被调用方法的参数列表。
* 标识码(请求 id,请求的标识码是用来匹配它所对应的回复):可以是任何类 型,用于与响应匹配。
回复部分:当调用请求结束时,服务器将回复该请求。回复同样是用 JSON 进行了编码的对象。它有 3 个部分:
* 返回值 - 是一个由被调用方法返回的对象,如果错误调用方法时,则其值为 null。
* 错误信息 - 如果没有错误调用方法,则其值为 null。
* 标识码 - 和请求的标识码(id)一致。
公告部分:公告(notification)是一种没有回复的请求 . 同样为用 JSON 编码对象。它的标识码(id)为空,其他和普通请求一致。
以上便是 JSON-RPC 的协议规则,其实他就是一个规范了请求数据格式和返回值数据格式等等的远程调用通信协议,这个协议很适合于轻量级的远程调用实现。Dojo 的 RPC 包对该协议也做了很好的封装。尤其是针对已有的 JSON-RPC 后端服务,我们的前端实现只需要基于 Dojo 的 RPC 包,加上一些简单的配置,就能快速的构建完成。Dojo 在底层会把我们的请求转换成 JSON-RPC 协议规定的格式:
清单 13. JSON-RPC 模式示例
postJsonRpc10Echo: { transport: "POST", envelope: "JSON-RPC-1.0", target: "jsonRpc10.php", parameters: [ {type: "string", optional: true} ] }, var td = this.svc.postJsonRpc10Echo(this.name); td.addCallback(this, function(result){ if (result==this.name){ d.callback(true); }else{ d.errback(new Error("Unexpected Return Value: ", result)); } });
这里我们先定义请求方式:“transport: "POST" ”,再通过“envelope: "JSON-RPC-1.0" ”定义 JSON-RPC 协议模式。最后在代码中简单的调用“postJsonRpc10Echo ”方法即可。
当然,服务端也应当满足 JSON-RPC 模式,参见如下示例:
清单 14. JSON-RPC 模式示例(后端 PHP)
$json = new Services_JSON; $results = array(); $results['error'] = null; $jsonRequest = file_get_contents('php://input'); $req = $json->decode($jsonRequest); $method = $req->method; $params = $req->params; switch($method) { case "postJsonRpc10EchoNamed": case "postJsonRpc10Echo": $results['result']=$params[0]; break; default: $results['result']=""; $results['error']="JSON-RPC 1.0 METHOD NOT FOUND"; break; } $results['id'] = $req->id; $encoded = $json->encode($results); print $encoded;
其实这里的实现和之前的大体相似,主要的不同就在于这里的请求格式是遵循 JSON-RPC 协议的 JSON 模式,可以参见他对请求的解析方式代码:
$req = $json->decode($jsonRequest);
$method = $req->method;
$params = $req->params;
可见其方法“method”和参数“params”符合 JSON-RPC 的规范。同样,返回值也是基于 JSON-RPC 的 JSON 格式:
$encoded = $json->encode($results);
print $encoded;
注意这里的参数取值“$results['result']=$params[0]”,因为我们传参方式是简单的直接传参“this.svc.postJsonRpc10Echo(this.name)”,所以这里是通过数组模式数序匹配取值,同样它也支持对象模式:“this.svc.postJsonRpc12Echo({message: this.name})”对应着“$results['result']=$params->message;”。
JSON-REST-Store
Dojo 里面有一个很好用的用于管理数据的 Store:“dojox.data.JsonRestStore”,他的实现就是基于 Dojo 的 RPC 接口的。通过这个接口,我们能够很方便并快速的构建我们的 REST 应用。这里我们简单介绍一下:
清单 15. JsonRestStore 基本用法
var testServices = new dojox.rpc.Service(dojo.moduleUrl("dojox.rpc.tests.resources", "test.smd")); var jsonStore = new dojox.data.JsonRestStore({service:testServices.jsonRestStore}); jsonRestStore: { transport: "REST", target: "fakestore.php", contentType:"application/json", parameters: [ {name: "location", type: "string", optional: true} ], returns: { properties:{ name:{ type:"string", minLength:3 } } } }, jsonStore.fetch({query:"query", onComplete: function(items, request){ t.is(4, items.length); d.callback(true); }, onError: dojo.partial(dojox.rpc.tests.stores.JsonRestStore.error, doh, d) });
从上上述示例中我们可以看到:这里还是先初始化了 RPC 对象,然后基于此 RPC 对象构造了我们的 JsonRestStore 对象。
第二段代码是关于这个 REST 的 Store 的定义,与之前介绍的基本无异,只是多了一个返回值的定义“returns”。所以,服务端在返回数据时必然会包含“name”这个属性及其属性值。
第三段代码就是 Store 的使用案例,其使用方式与基本 Store 无异,主要通过“fetch”取值,可以加入自己的检索条件等等。
同样,它也支持添加,修改,删除,甚至修改后的回滚等操作等操作:
清单 16. JsonRestStore 进阶用法
// 修改和保存 jsonStore.fetch({query:"query", onComplete: function(items, request){ var now = new Date().getTime(); jsonStore.setValue(items[0],"updated",now); jsonStore.setValue(items[0],"obj",{foo:'bar'}); // 修改和保存操作 jsonStore.setValue(items[0],"obj dup",items[0].obj); jsonStore.setValue(items[0],"testArray",[1,2,3,4]); jsonStore.save(); jsonStore.fetch({query:"obj1", onComplete: function(item, request){ t.is("Object 1", item.name); t.is(now, item.updated); t.is("bar", item.obj.foo); t.is(item.obj, item['obj dup']); d.callback(true); }, onError: dojo.partial( dojox.rpc.tests.stores.JsonRestStore.error, doh, d)}); }, onError: dojo.partial(dojox.rpc.tests.stores.JsonRestStore.error, doh, d) }); // 添加和删除 jsonStore.fetch({query:"obj1", onComplete: function(item, request){ var now = new Date().getTime(); var testArray = item.testArray; // 添加和删除操作 var newObject = {"name":"new object"}; testArray.push(newObject); jsonStore.save(); jsonStore.deleteItem(newObject,{parent:testArray}); jsonStore.save(); testArray.sort(function(obj1,obj2) { return obj1 < obj2; }); jsonStore.save(); testArray.sort(function(obj1,obj2) { return obj1 > obj2; }); jsonStore.save(); d.callback(true); }, onError: dojo.partial(dojox.rpc.tests.stores.JsonRestStore.error, doh, d) }); // 回滚 jsonStore.fetch({query:"obj1", onComplete: function(item, request){ jsonStore.setValue(item,"name","new name"); jsonStore.setValue(item,"newProp","new value"); jsonStore.unsetAttribute(item,"updated"); t.is(jsonStore.getValue(item,"name"),"new name"); t.is(jsonStore.getValue(item,"newProp"),"new value"); t.is(jsonStore.getValue(item,"updated"),undefined); // 回滚操作 jsonStore.revert(); t.is(jsonStore.getValue(item,"name"),"Object 1"); t.is(jsonStore.getValue(item,"newProp"),undefined); t.t(typeof jsonStore.getValue(item,"updated") == 'number'); d.callback(true); }, onError: dojo.partial(dojox.rpc.tests.stores.JsonRestStore.error, doh, d) });
以上是关于 JsonRestStore 的简单介绍,他还有延迟加载,分页等等功能,这里不再深入,有兴趣的读者可以参考 Dojo 的官方文档。
回页首
Dojo 的 RPC 的具体应用
之前我们介绍了 Dojo 的 RPC 接口,主要是基于一些 PHP 写的后台服务,接下来我们来看看现在如何利用 Dojo 来使用市面上流行的一些 RPC 服务。
Dojo 对一些比较流行的 RPC 服务,如雅虎,社交网络(FriendFeed),地理信息(Geonames),维基百科(Wikipedia)等等都写好了相应的 SMD 配置文件,使得我们能够非常方便的基于这些文件实现我们的 RPC 应用。同样,我们也能够模仿这些 SMD 文件去实现我们自己喜欢的 RPC 服务的 SMD 配置。针对这些市面上流行服务的 SMD 配置文件主要在“dojox/rpc/SMDLibrary”中,大家可以直接拿来使用,也可以添加一些自定义的配置信息。
雅虎 RPC
雅虎的 RPC 调用方式与之前我们介绍的方式基本一样。大家可以先参考雅虎 RPC 的官方文档描述,决定使用的接口之后再将其集成到我们的远程服务配置 SMD 文件里面。Dojo 的“yahoo.smd”配置文件已经配置好了很多服务,大家可以直接使用或添加自己需要的服务配置:
清单 17. 雅虎 RPC
questionSearch: { target: "http://answers.yahooapis.com/AnswersService/V1/questionSearch", parameters: [ { name: "query", type: "string", optional: false, "default": "" }, { name: "search_in", type: "string", optional: true, "default": "all" }, // 取值列表 "all", "question", "best_answer" { name: "category_id", type: "integer", optional: true, "default": null }, // (category_id, category_name) 其中之一为必须 { name: "category_name", type: "string", optional: true, "default": null }, { name: "region", type: "string", optional: true, "default": "us" }, // 取值列表 "us", "uk", "ca", "au", "in", "es", "br", "ar", "mx", "e1", "it", "de", "fr", "sg" { name: "date_range", type: "string", optional: true, "default": "all" }, // 取值列表 "all", "7", "7-30", "30-60", "60-90", "more90" { name: "sort", type: "string", optional: true, "default": "relevance" }, // 取值列表 "relevance", "date_desc", "date_asc" { name: "type", type: "string", optional: true, "default": "all" }, { name: "start", type: "integer", optional: true, "default": 0 }, { name: "results", type: "integer", optional: true, "default": 10 } ] },} var yd = dojox.rpc.tests.yahooService["questionSearch"]({query: "dojo toolkit"}); yd.addCallback(this, function(result){ if (result[method.expectedResult]){ d.callback(true); }else{ d.errback(new Error("Unexpected Return Value: ", result)); } });
前段代码是雅虎 RPC 服务的配置,这个服务主要为雅虎问答的检索。
后段代码用于调用服务,与之前介绍的方式无异。
雅虎还提供了很多的服务,包括音频,视屏的检索,图片查询,交通,旅游信息的查询等等,一下是关于音频,视屏的检索的 SMD 配置:
清单 18. 雅虎 RPC 服务
songSearch: { target: "http://search.yahooapis.com/AudioSearchService/V1/songSearch", parameters: [ { name: "artist", type: "string", optional: true, "default": "" }, { name: "artistid", type: "string", optional: true, "default": "" }, { name: "album", type: "string", optional: true, "default": "" }, { name: "albumid", type: "string", optional: true, "default": "" }, { name: "song", type: "string", optional: true, "default": "" }, { name: "songid", type: "string", optional: true, "default": "" }, { name: "type", type: "string", optional: true, "default": "all" }, // 取值列表 "all", "any", "phrase" { name: "results", type: "integer", optional: true, "default": 10 }, // 最大值 50 { name: "start", type: "integer", optional: true, "default": 1 } ] }, videoSearch: { target: "http://search.yahooapis.com/VideoSearchService/V1/videoSearch", parameters: [ { name: "query", type: "string", optional: false, "default": "" }, { name: "type", type: "string", optional: true, "default": "any" }, // 取值列表 "all", "any", "phrase" { name: "results", type: "integer", optional: true, "default": 10 }, // 最大值 50 { name: "start", type: "integer", optional: true, "default": 1 }, { name: "format", type: "string", optional: true, "default": "any" }, // 取值列表 "any", "avi", "flash", "mpeg", "msmedia", "quicktime", "realmedia" { name: "adult_ok", type: "boolean", optional: true, "default": null }, { name: "site", type: "string", optional: true, "default": null } ] },
基于这些 RPC 服务资源,我们能丰富我们的 Web 应用的内容,构建更加多彩的 Web 产品。
社交网络 FriendFeed 的 RPC
与雅虎 RPC 一样,他的使用方式也相当简单,只需要简单的配置加上方法的调用即可,以下为其服务的配置:
清单 19. FriendFeed 的 RPC 配置
services: { users: { target: "http://friendfeed.com/api/feed/user", parameters: [ { name: "nickname", type: "string", optional: false, "default": "" } ] }, entry: { target: "http://friendfeed.com/api/feed/entry", parameters: [ { name: "entry_id", type: "string", optional: false, "default": "" } ] }, search: { target: "http://friendfeed.com/api/feed/search", parameters: [ { name: "q", type: "string", optional: false, "default": "" } ] }, url: { target: "http://friendfeed.com/api/feed/url", parameters: [ { name: "url", type: "string", optional: false, "default": "" } ] }, domain: { target: "http://friendfeed.com/api/feed/domain", parameters: [ { name: "domain", type: "string", optional: false, "default":"" } ] } }
可以看到,FriendFeed 主要支持 5 种接口,大家可以利用这些接口给自己的 Web 应用添加相应的社交功能。
同样,还有推特 (Twitter),地理信息(Geonames)和维基百科(Wikipedia)等等 RPC 服务在 Dojo 中均有其默认 SMD 配置,有兴趣的读者可以深入研究。
回页首
应用举例
接下来我们以 Google 的 RPC 服务为例实现一个简单的检索应用:
图 1. Google 的 RPC 示例
这是一个用 Dojo 实现的 Web 搜索引擎,该引擎基于 Google 的 RPC 服务。我们通过查询 Google 的 RPC 文档,找到了 Google 的关于 Web 各种资源检索的 RPC 服务地址,并且通过解读他的参数文档,实现了我们的 SMD 文件的配置:
清单 20. Google 的 Web 检索配置
"parameters": [ // 检索参数 : { "name": "q", optional: false, "default":"" }, // 返回值大小 : large | small ( 每页 8 或 4 条内容 ) { "name": "rsz", optional:true, "default": "small" }, // 语言 : { "name": "hl", optional:true, "default": "en" }, ................... ], "services": { "webSearch": { "target": "http://ajax.googleapis.com/ajax/services/search/web", "parameters": [ { "name": "cx", "type":"string", "optional":true }, { "name": "cref", "type":"string", "optional":true }, ] }, "videoSearch": { "target": "http://ajax.googleapis.com/ajax/services/search/video", "parameters": [ { "name": "scoring", "type": "string", "optional": true } ] }, "blogSearch": { "target": "http://ajax.googleapis.com/ajax/services/search/blogs", "parameters": [ { "name": "scoring", "type": "string", "optional": true } ] }, "newsSearch": { "target": "http://ajax.googleapis.com/ajax/services/search/news", "parameters": [ { "name": "scoring", "type": "string", "optional": true }, { "name": "geo", "type":"string", optional:true } ] } .................... }
可以看到,这里有网页内容的检索服务,视屏检索服务,博客检索以及新闻检索等等服务,注意到开头的"parameters"配置,这里主要配置通用参数,这里的参数“q”便是检索关键字的形参,我们主要也是通过这个参数信息向 Google 的 RPC 后台发请求。
当用户通过我们构建的页面选择检索类型(视屏,新闻等等)并输入检索参数后,我们便会调用 Dojo 的 RPC 接口想 Google 发送查询请求。
清单 21. Google 信息检索
google = new dojox.rpc.Service(dojo.moduleUrl("dojox.rpc","SMDLibrary/google.smd")); //searchType 为"webSearch", "videoSearch"等等 google[searchType]({ q: dojo.byId("test").value }) .addCallback(function(returned){ var ret = returned.responseData; var info = ret.cursor; var data = ret.results || []; dojo.forEach(data,function(item){ var li = dojo.doc.createElement('li'); li.innerHTML = "<a target='_new' hr"+"ef='"+ (item.unescapedUrl || item.url) +"'>" + item.title + "</a><br />" + "<span class='summary'>" + (item.content || item.streetAddress || "unknown") + "</span>"; console.log(item); dojo.byId("top").appendChild(li); }); tehloader("hidden"); }) // something bad happened: .addErrback(function(err){ console.warn('ooops', err); tehloader("hidden"); }); };
请求返回后,将结果解析并展示在页面上。至此,一个简单的搜索引擎便制作完成。
回页首
结束语
这篇文章介绍了 Dojo 中的 RPC 的工具包,先从基本的 RPC 接口入手,介绍 Dojo 的 RPC 远程调用的配置和使用,并深入到高级的用法,包括不同方式的参数传递和方法的声明。然后依次介绍了基于 REST,JSONP 和 JSON-RPC 的 RPC 调用,同时也提到了一个基于 RPC 的 Store:“dojox.data.JsonRestStore”。最后,扩展到市面上一些流行的 RPC 服务,如雅虎,推特,维基百科等等。文章结束的时候,通过一个具体的基于 Google 的 RPC 服务的应用介绍了 Dojo 的 RPC 应用的开发流程。这些内容我们可以在开发过程中多关注一下,以尽可能多的完善我们的 Web 应用。