[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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章