[web] 使用 web.rest 实现 rest-rpc

转载来源:http://bbs.aardio.com/forum.php?mod=viewthread&tid=11218&extra=page%3D1

用web.rest可以把任何普通的HTTP API转换为aardio中的函数调用(我们称之为 rest-rpc )标准库中用于支持 rest-rpc 的库:

web.rest.client 请求参数使用urlencode编码,服务器返回文本数据。
web.rest.xmlClient 请求参数使用urlencode编码,服务器返回xml格式数据。
web.rest.jsonLiteClient 请求参数使用urlencode编码,服务器返回JSON格式数据。
web.rest.jsonClient 请求参数与服务器返回数据都使用JSON格式。

除了与服务器交互的数据格式不同以外, 这几个库的接口用法完全一样,可以看看这几个库的源码实际上他们都是调用 web.rest.client 这一个库。

先看一个最简单的例子:

import console;
import web.rest.jsonClient;

var http = web.rest.jsonClient();
var jsonstore = http.api("https://www.jsonstore.io/e5fd2bdf0e6b3ba3fe4aa61eebd11740cf2fe10e7fad1b5d2fb77c876498baf5");

//增
var result = jsonstore.user[1].post(
    name = "jon.snow";
    age = 31;
)

//改
var result = jsonstore.user[1].age.put(32);

//查
var result = jsonstore.user[1].get();

//删
var result = jsonstore.user[1].delete();
console.dump(result);

console.pause()

下面讲解具体用法

一、使用 web.rest 执行基本的HTTP请求

web.rest下面的支持库最简单的用法就是作为一个HTTP客户端使用,该客户端对象简化了get,post,put,patch,delete等常用的HTTP请求操作,并提供编码请求数据、解码返回数据的功能,下面是一个最简单的示例:

import console; 
import web.rest.jsonLiteClient;

var restClient = web.rest.jsonLiteClient();  
var jsonData = restClient.post("http://eu.httpbin.org/post",{ 
    用户名 = "用户名";
    密码 = "密码";
} ) 

console.dumpJson(jsonData)
console.pause(true);

从上面的示例可以看出,我们上传参数的是aardio中的对象,返回的数据也被自动解码为aardio对象,虽然HTTP传输使用的是JSON数据,但使用时不需要去管JSON的编解码等一系列的操作。

二、使用 web.rest 将 Web API 转换为 aardio函数

web.rest 不仅仅可以用来做上面这些简单的HTTP请求、以及编解码的操作,他还可以将基本符合REST风格的Web API转换为aardio中的函数对象(rest-rpc),这非常有意思,REST本身缺乏WebService那样的WSDL接口描述服务,但是aardio设计了一种简单可行的声明语法,可以非常方便的把混乱的Web API转换为统一的aardio函数。

首先我们看一下REST API的URL一般会是这种格式 http://主机/资源目录名/资源目录名/资源名
aardio的web.rest库模块中的客户端对象提供一个api 函数用于声明一个API接口,api 函数的定义如下:

var restApi = restClient.api("接口URL描述","默认HTTP请求动词")

其中接口URL描述可以直接指定一个web api的网址,在该网址中还可以使用变量,变量放在花括号中,例如:http://主机/{变量名}/资源目录名/资源名 aardio并不关心变量名的内容是什么,只关心它们出现的前后顺序,当调远程函数时会使用对象名字、函数名字替换接口URL中的变量生成新的请求URL。

下面是一个简单的示例:

import console;
import web.rest.jsonClient; 

// 创建REST客户端
var restClient = web.rest.jsonClient(); 

//声明一个rest-rpc接口,第一个参数指定URL描述
var restApi = restClient.api("http://eu.httpbin.org/api/{program}/{lang}")

/*
下面调用接口函数,
在请求时下面代码中的接口名"language"替换接口URL描述中的变量{program}
接口名"aardio"则替换接口URL描述中的变量{lang}
最后生成的请求URL为 http://eu.httpbin.org/api/language/aardio 
*/
var result = restApi.language.aardio()

console.log("请求的URL",restClient.lastRequestUrl )
restClient.lastResponse(); //输出服务端最后返回的数据

console.pause();

接口URL中连接的变量名还可以合并为{...}
例如 "http://eu.httpbin.org/api/{program}/{lang}" 可以简写为 "http://eu.httpbin.org/api/{...}"
当 {...} 出现在尾部时还可以直接省略,例如 "http://eu.httpbin.org/api/"

注意head,get,post,put,patch,delete等默认的HTTP请求操作作为函数名时不会被添加到生成的URL中。
这些默认的HTTP方法名在 web.rest.client._defaultMethod 中指定,例如使用 restApi.language.get() 显示的指定HTTP请求动词为“GET”。如果不指定HTTP请求动词,则使用调用 restClient.api("接口URL描述","默认HTTP请求动词") 函数时第二个参数指定的HTTP请求动词,不指定该参数时默认为"POST"

HTTP规定了九种动词(Verbs)用于指定请求方法:GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS,
而在 rest-rpc 中用到的有五种 GET,POST,PUT,DELETE,PATCH,他们的用途如下:

GET:用于获取数据
POST: 用于创建数据
PUT: 用于替换数据、也可用于更新数据
DELETE: 用于删除数据
PATCH:用于更新数据

三、使用 web.rest 上传下载文件

如果一个REST API在请求时需要上传、下载文件,那么所有调用规则如前不变,
你仅仅需要做的是,在调用API以前指定接受、或发送文件的回调函数以获取上传、下载的进度,

上传文件示例:

restClient.sendFile( "上传文件路径" 
    ,function(str,sendSize,contentLength){
        ..io.print("正在上传",sendSize,contentLength);
    }
); 

//在后面再简单的调用API就可以了,例如
restApi.upload()

下载文件示例:

restClient.receiveFile( "上传文件路径" 
    , function(str,receiveSize,contentLength){
        ..io.print("正在下载",receiveSize,contentLength);
    }
); 

//在后面再简单的调用API就可以了,例如
restApi.download()

web.rest 也可以支持 multipart/form-data 编码上传文件,示例:

import console; 
import web.rest.client;

var http = web.rest.client(); 
http.sendMultipartForm( {
        file = "@d:\文件路径"; 
        username = "测试";
    },function(str,sendSize,contentLength){
        console.log("正在上传",sendSize,contentLength);
    } 
);
var str =http.post("http://eu.httpbin.org/post") 
console.pause(,str)

四、web.rest客户端对象的错误处理

web.rest客户端对象的错误处理与inet.http相同:
请求成功返回服务器数据,失败返回空值,错误信息,错误代码等。
下面具体用代码演示详细的错误处理代码( 注意下面为了演示所有的细节,代码写的比较长,实际开发中不必要写的这么细)

import console; 
import web.rest.jsonLiteClient;

var restClient = web.rest.jsonLiteClient();  

/*
web.rest客户端对象所以执行HTTP请求的函数遵守以下规则:
如果成功,则第一个返回值jsonData为服务端返回数据解码并创建的aardio对象。
在HTTP请求遇到错误时,第一个返回值jsonData为空,第二个返回值errMsg为错误信息,返回值errCode为错误代码
一般我们可以省略errMsg,errCode这两个返回值不用写,直接判断返回值是否为空即可。  
*/
var jsonData,errMsg,errCode = restClient.post("http://eu.httpbin.org/post",{ 
    用户名 = "用户名";
    密码 = "密码";
} ) 

//jsonData非空为请求成功
if( jsonData ){
    console.dumpJson(jsonData);
}
else {
    /*
    出错了,如果restClient.lastStatusCode非空则说明服务端返回了HTTP状态代码
    */
    if(  restClient.lastStatusCode ){
        console.log( restClient.lastStatusMessage() ) //查看该状态码的说明
        restClient.lastResponse() //输出服务端最后返回的信息
    }
    else {
        //这通常是没有成功发送请求,在请求到达服务器以前就出错了
        console.log("HTTP请求遇到错误,WinInet错误代码:",errCode )
        console.log("关于WinInet错误代码的详细说明:http://support.microsoft.com/kb/193625 ")
    }

}
console.pause();

当然上面的代码一般在调试故障时才需要,一般没必要把错误处理写的这么细,上面的代码也可以简化如下:

import web.rest.jsonLiteClient; 
var restClient = web.rest.jsonLiteClient();   

var 鸭子 = restClient.post("http://eu.httpbin.org/post",{ 
    用户名 = "用户名";
    密码 = "密码";
} ) 

if( 鸭子[["翅膀"]] ){ //这句相当于 if( 鸭子 and 鸭子.翅膀 )
    io.print("不管服务器给我的是什么鸭子,总之有翅膀的都是好鸭子")
} 
else {
    //我的网络错误处理模块.错误统一分析("怎么回事没翅膀还能叫鸭子吗?",restClient.lastStatusCode)
    return null,"网络错误"
}

五、关于 rest-rpc

实际上REST本身是试图对URL进行规范,但现在流行的所谓REST API多数只剩下了“URL"而扔掉了“规范”,实际上REST里其他东西没什么用,最有用的就是“URL”这个词,把URL本身当接口来设计简洁、轻快、实用,这才是REST API广泛流行的真正原因,很多人解释不清为什么很多声称REST API的API压根就不符合REST的原则,其实很简单,REST这个词被过度神化了,所以不要去纠结哪个API符不符合REST原则,基本上,你没有任何必要去学习这个东西(因为REST里真正有用的只是一种解决问题的态度,而条款都没有用)。

实际上使用web.rest可以把任何普通的HTTP API转换为aardio中的函数调用(我们称之为 rest-rpc ),但遵守下面的规则可以做的更好。

1、HTTP服务端提供的接口URL要能使用以下的URL描述规则:

 URL中的资源名应当能使用{模板变量}代替、{模板变量}的先后关系应当对应资源名的出现顺序。
 {模板变量}包含在花括号里
 可以使用多个数字或字母,数值的大小并不重要,URL描述仅关心资源出现的先后关系。
 可以使用 {...} 表示不定个数的模板变量。

 http://主机/资源分类/资源目录/资源名/资源ID 
 使用URL描述语法转换结果就是这样: http://主机/{res}/{category}/{name}/{id} 
 也可以使用 http://主机/{res}/{...} 表示。  
 如果 {...} 出现在最后则可以省略

 HTTP://主机/资源分类?资源目录=目录名&资源名=资源名&资源ID=资源ID 
 使用URL描述语法转换以后:
 HTTP://主机/{res}?资源目录={category}&资源名={name}&资源ID={id}

 可以看到资源名是不是写到参数里都能清晰的展现资源定位,要注意 Web API 并不是浏览器,
 URL并不会出现在浏览器的地址栏,
 设计一个友好的API URL重要的是编程语言里能不能更好的理解并自动分析转换。 
 例如:
 aardio中的 web.rest.client 就按照这种URL描述语法自动的将URL描述转换为aardio中的函数对象。

2、URL不应当包含以下HTTP指令动词:

GET: 表示获取资源
POST: 表示新增数据
PUT: 表示替换数据
DELETE: 表示删除数据
PATCH: 表示更新数据

可选在URL的最后一层目录添加扩展的操作动词,例如:
http://host/group/user/userid/ 使用get读取用户信息
http://host/group/user/userid/password/change 使用扩展的change方法修改用户密码

如果按这种规则实现服务端的API,那么在aardio里用 web.rest.client 调用起来就很方便,示例:

import web.rest.jsonClient;
var client = web.rest.jsonClient()
var api = client.api("http://host/{group}/{..}")

// GET方法读取用户信息
var userInfo = api.xgroup.user[userId].get()

// 使用扩展的change方法修改密码
api.xgroup.user[userId].passord.change(
    pwd = "旧密码";
    newPwd = "新密码";
)

为什么不直接在每一个请求里写具体的URL呢?要考虑到实现一个API的扩展库,API服务端的地址可能发生变更,使用上面的方法就可以简单的维护一个声明URL参数即可。

3、rest-rpc 的URL中不应出现文件后缀名.

例如: http://host/x/y.aardio 应当在服务器上移动到 http://host/x/y/main.aardio,然后提供给客户端的API应隐藏默认的文档名,即 http://host/x/y/ 这样的好处是服务端变更实现会非常方便。

4、rest-rpc 的URL中不应出现IP地址,即使是测试期间,也应尽可能的使用域名替代IP地址。

怎么修改header或是在header里面添加内容?

//每次请求都写的HTTP头
restClient._http.addHeaders = { name = value }

//单次请求写的HTTP头
restClient._http.headers = { name = value }

//在发送请求前自己写HTTP头
restClient.beforeSend = function(){
     restClient._http.writeHeader("name:value")
}

补一个multipart/form-data表单提交的例子:
http://bbs.aardio.com/forum.php?mod=viewthread&tid=13629

import console; 
import web.rest.client;
import string.html;

var http = web.rest.client();
http.api("https://oa.tongda2000.com/logincheck.php").post(
     UNAME="lijia";
     encode_type=1;
);

http.sendMultipartForm( {
        TO_ID = "912";
        TO_NAME = "王征";
        SUBJECT = "测试邮件标题!";
        SEND_FLAG = 1;
        SMS_REMIND = "on";
        FROM_WEBMAIL = "[email protected]";
        TD_HTML_EDITOR_CONTENT= /*测试邮件内容*/
    }
    ,function(str,sendSize,contentLength){
        console.log("正在向服务器提交数据",math.size64(sendSize).format() );
    } 
);

var html = http.api("https://oa.tongda2000.com/general/email/new/submit.php").post()
console.log( string.html.toText(html) );
console.pause(true);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章