過去的 年間一個趨勢主導了商業軟件工具的開發用復雜性對抗復雜性這一趨勢在任何地方都沒有比在分布式計算領域更明顯C 和 Java; 社區已經看到一些驚人復雜的框架被構建出來支持分布式通信分布式計算環境(DCE)支持用 C 語言編寫的應用程序之間的遠程過程調用公共對象請求代理架構(CORBA)標准支持面向對象應用程序之間的通信企業 JavaBean(EJB)規范提供安全性持久性事務消息和遠程的服務對各個框架的宣傳甚囂塵上但是這些框架都沒有滿足預期有些甚至因為它們的復雜性而成為災難在這些框架中只有 EJB 屬於大力簡化的結果有潛力在分布式應用程序上成功市場可能給也可能不給這個面臨強敵的框架另一個空間但 EJB 仍然需要交付使用
最新的大型分布式框架是 Web 服務Web 服務技術讓應用程序可以用平台獨立或編程語言獨立的方式相互通信Web 服務標准也受到復雜性惡魔的威脅但是稱作 REST 的替代策略承諾了更簡單的方式本文介紹了如何在 Ruby on Rails 中添加 REST 風格的 Web 服務並從 Ruby 和 Java 代碼調用服務
關於本系列
在 跨越邊界 系列中作者 Bruce Tate 推動了這樣一個概念今天的 Java 程序員通過學習其他方法和語言可以受益編程陣營中 Java 技術是所有開發項目最佳選擇的情況已經變了其他框架正在改變 Java 框架構建的方式從其他語言學到的概念有助於 Java 編程編寫的 Python(或 RubySmalltalk 或 ……)代碼可以改變 Java 編碼的方式
本系列介紹了與 Java 開發有根本不同但是卻直接適用的編程概念和技術在某些情況下需要集成這些技術以利用它在其他情況下將可以直接應用這些概念單獨的工具不如其他語言和框架中影響 Java 社區的開發人員框架甚至基本方法的思想重要
Web 服務領域
就像 EJBCORBA 和 DCE 一樣Web 服務的核心抽象也是遠程過程調用Web 服務利用叫做 SOAP(最初SOAP 代表簡單對象存取協議但是這個術語現在降級了)的協議用 XML 表示消息的結構這裡有一個技巧如果協議用代表簡單的 S 開始那它就不簡單Web 服務定義語言(WSDL)提供了服務的標准規范像 SOAP 一樣WSDL 也是一個棘手而復雜的 API而 SOAP 和 WSDL 僅僅涉及到了構成 Web 服務這個大怪物的眾多 API 的表面Web 服務需要一次大修感謝 Roy Fielding 的一份有影響的博士論文Web 服務得到了大修
Fielding 的論文描述了 REST 應用程序聯網策略REST 與全堆棧 Web 服務根本不同主要原因有三個
REST 的核心抽象是遠程資源而不是遠程過程調用
REST 沒有發明一個詳盡的標准列表
而是采用現有的 Internet 標准
包括 HTTP
XML 和 TCP/IP
REST 沒有覆蓋每個可能場景
而是覆蓋了最常見的問題
請把 REST 想像成浏覽REST 客戶使用與浏覽器相同的 HTTP 命令訪問資源當 REST 客戶訪問到資源的表示時客戶轉換到一個狀態使用不同的 HTTP 命令REST 客戶可以創建讀取更新或刪除資源的記錄
例如以典型的博客為例通過輸入 URL例如 得到貼子的列表然後如果想編輯博客條目可以在 URL 中輸入 HTTP 參數(例如 /edit?article=)然後顯示編輯表單由於每個博客條目都有自己的 URL所以點擊鏈接或直接輸入 URL就可以用 HTTP 命令讀取修改或刪除內容
簡而言之REST 可以
用 TCP/IP 命名標准命名 Web 上的資源
用 HTTP 查詢和操縱這些資源
使用基於文本的標准消息格式(例如 XML 或 HTML)來構造數據
Ruby on Rails 用 REST 對 Web 服務提供了優秀的支持
Action Web Services 概述
Rails 用叫做 Action Web Services 的模塊實現 Web 服務許多開發框架鼓勵視圖和 Web 服務使用獨立的控制器這個策略可以維護控制器之間的風格一致問題是針對所服務的每種內容都需要一個新控制器例如Ajax 用戶界面要求從控制器取得到 JavaScript 的遠程 XML 調用
不必為 Web 服務專門分配一個控制器使用 Rails可以通用地用同一個控制器向基於 HTML 的視圖基於 XML 的 Web 服務和基於 XML 的 JavaScript 組件提供內容理解 Action Web Services 的最好方式就是在工作應用程序的環境下查看它的實際作用
請用自己選擇的數據庫管理器創建一個叫做 service_development
的數據庫接下來用以下命令創建 Rails 項目和模型
> rails service
> script/generate model Person
在生成模型之後就有了一個叫做 db/migrate/_create_peoplerb 的遷移請把這個遷移編輯成像清單 一樣
清單 people 表的遷移
class CreatePeople < ActiveRecord::Migration
def selfup
create_table :people do |t|
lumn :first_name :string :limit =>
lumn :last_name :string :limit =>
lumn :email :string :limit =>
lumn :phone :string :limit =>
end
end
def selfdown
drop_table :people
end
end
把 config/databaseyml 中的數據庫配置修改成與自己的數據庫配置匹配並輸入 rake migrate
最後輸入 script/generate scaffold Person People
為 Person
模型和 People
控制器生成工作台現在可以用 script/server
啟動服務器了請把浏覽器指向 localhost:/people以看到針對 Person
的經典的 Rails 腳手架圖 顯示了帶有標准 Rails 腳手架的應用程序
圖 簡單的 Rails 應用程序
在我介紹 Rails 的 Web 服務之前請查看控制器代碼編輯 app/controllers/people_controllerrb使之與清單 的代碼匹配
清單 PeopleController 的控制器代碼
class PeopleController < ApplicationController
def index
list
render :action => list
end
# GETs should be safe (see
)
verify :method => :post :only => [ :destroy :create :update
]
:redirect_to => { :action => :list }
def list
@person_pages @people = paginate :people :per_page =>
end
def show
@person = Personfind(params[:id])
end
def new
@person = Personnew
end
def create
@person = Personnew(params[:person])
if @personsave
flash[:notice] = Person was successfully created
redirect_to :action => list
else
render :action => new
end
end
def edit
@person = Personfind(params[:id])
end
def update
@person = Personfind(params[:id])
if @personupdate_attributes(params[:person])
flash[:notice] = Person was successfully updated
redirect_to :action => show :id => @person
else
render :action => edit
end
end
def destroy
Personfind(params[:id])destroy
redirect_to :action => list
end
end
如果跟著做過這個系列以前的 Ruby on Rails 項目就會知道典型的控制器方法的一般流程是
用戶通過跟隨鏈接或指定 URL通過 HTTP 發送請求
Web 服務器根據域的配置把請求轉給 Ruby on Rails
Rails 路由器根據 URL 模式把請求路由給控制器默認模式是//主機名/控制器/動作/參數
路由器用與動作相同的參數調用控制器上的方法
動作參數為視圖設置實例變量並呈現視圖
動作方法把實例變量拷貝到視圖
例如請看 清單 中的 show
方法控制器設置視圖使用的 @person
實例變量因為方法沒有指定視圖的名稱所以 Rails 用與控制器動作相同的名稱調用視圖 —— 在這個示例中視圖位於 app/views/people/showrhtml
再來看 list
方法如果想讓這個方法呈現 XML需要
刪除分頁
把
people
實例變量轉換成 XML
呈現 XML 而不是 HTML
Rails 使得處理 Web 服務和呈現來自同一 Web 服務的視圖成為可能實際上也不需要分頁為了把 Web 服務的 list
方法簡化一些可以把控制器中的 list
方法變成像清單 一樣清除分頁還需要刪除靠近 app/views/people/listrhtml 代碼底部的 Next Page 和 Previous Page 鏈接
清單 簡化 list
def list
@people = Personfind_all
end
由於刪除了分頁也就刪除了讓用戶界面更健壯的一個特性但是又得到了一些回報可以用相同的代碼來驅動 Web 服務和視圖如果日後發現需要分頁可以編寫一些定制的助手
現在基本應用程序出來了可以添加一些 Web 服務了
向 Rails 控制器添加 Web 服務
如果我想說大話我可以說 現在已經有了一個 Web 服務記得我對 REST 說過什麼?這種風格的 Web 服務使用指定的資源我的 Rails 應用程序也具有指定的資源host_name/people/list 調用我的 list
服務REST 風格的 Web 服務也使用 TCP/IP 和 HTTP我的 Rails 應用程序就是這麼做的而且格式良好的 HTML 就是 XML 的子集也滿足最後一條 REST 要求只需在 localhost:/people/list 上調用 HTTP get
並解析結果就可以得到人員列表這就是關鍵REST 的工作方式與 Internet 的工作方式一樣但這並不是真正基於 REST 的 Web 服務理想情況下應當提供反映 Person
含義的 XML 文檔而不是用戶界面的結構
真正的服務應當產生純數據的表示一個專門針對服務的預期客戶而構建的表示但是示例應用程序有兩個客戶終端用戶和 REST 客戶要為兩個目的重用相同的代碼需要給 Rails 提供更多信息Rails 的設計者可能決定使用額外的 URL 參數但是處理 URL 可是一項費勁的工作Rails 不應當用這些細節增加用戶負擔相反HTTP 提供了指定更多信息的工具HTTP 頭
要理解 Web 服務的 REST 模型了解一點 HTTP 是有幫助的curl
(請把它想像成 查看 URL)命令允許用一個命令查詢 URL並查看響應基於 Unix 的操作系統默認包含 curl
可以為其他操作系統下載免費的 curl
工具通過輸入 //someurl
可以將請求限制成只輸出默認的響應體(浏覽器呈現的 HTML)輸入 curl //someurl
可以得到更多信息這個命令返回 HTTP 頭如清單 所示可以看到頭配置由表示每個請求的配置的鍵值對組成
清單 用 curl 調用 HTTP 請求
> curl i Alive
Date: Tue Jun :: GMT
ContentType: text/html; charset=UTF
Server: WEBrick/ (Ruby//)
ContentLength:
SetCookie: _session_id=defbcdd; path=/
後面將頻繁地看到 HTTP get
put
post
和 delete
命令REST 利用達些命令執行經典的 CRUD(CRUD 是create readupdate 和 delete 的共同縮寫)HTTP 命令到 CRUD 的映射是這樣的
Create(創建)
HTTP
put
Read(讀取)
HTTP
get
Update(更新)
HTTP
post
Delete(刪除)
HTTP
delete
浏覽器利用 HTTP 頭通過相同的服務器端代碼來滿足不同類型的請求行為良好的應用程序提供正確處理文檔的充足信息其中一條信息叫做 HTTP Accept
頭只要多花一點力氣控制器就能利用一些助手用 Accept
頭決定如何響應進入的請求然後控制器可以呈現適當的響應請把 PeopleController
中的 list
方法改成像清單 一樣
清單 擴展 list方法以呈現 XML
def list
# wants is determined by the http Accept header in the request
@people = Personfind_all
respond_to do |wants|
l
wantsxml { render :xml => @peopleto_xml }
end
end
在清單 中可以看到完整的基於 REST 的 Web 服務生成的代碼是 Rails 中小型的特定於域的語句的優美示例它擴展 Ruby 以構造一種 switch 語句它的工作方式是這樣的
respond_to
方法接受單個代碼塊並傳遞一個實例變量(標為 wants
)到代碼塊
wants
對每個可能的類型都有一個方法控制器可以為控制器期望的每個類型指定一個代碼塊
如果方法名稱與 HTTP Accept
頭中的類型匹配wants
方法執行對應的代碼塊
如果沒有指定代碼塊(例如 l
)Rails 就執行默認動作(在這個示例中呈現 app/views/people/listrhtml)
這個策略允許在所有預期的客戶之間共享相同的設置代碼如果需要添加期望 HTML 的 JavaScript 客戶以便讓應用程序支持 Ajax只需要添加 wantsjs如清單 所示
清單 為 JavaScript 客戶呈現 HTML
def list
# wants is determined by the http Accept header in the request
@people = Personfind_all
respond_to do |wants|
l
wantsjs
wantsxml { render :xml => @peopleto_xml }
end
end
現在已經看到了如何向只讀的方法中添加 REST Web 服務show
方法也類似如清單 所示
清單 實現 show
def show
@person = Personfind(params[:id])
respond_to do |wants|
l
wantsxml { render :xml => @personto_xml }
end
end
您可能已經注意到通過 REST 看到的只有只讀服務原因是讓應用程序處理提交和刪除所需要的工作比較少刪除不需要額外的支持因為當前的代碼已經用 URL 指定了要刪除的人的 IDRails 自動轉換 post
請求中進入的 XML所以不需要構建任何服務器端支持實際上應用程序不用變就能刪除更新和創建可以修補每個方法呈現的 HTTP 響應但是客戶代碼實際就在 HTTP 返回碼之後
現在是調用 Web 服務的時候了
調用 Web 服務
使用現有 HTTP 協議這一策略使得調用變得簡單清單 顯示了 Ruby 版本請注意 HTTP Accept
頭記住控制器根據這個頭決定內容的類型
清單 從 Ruby 調用服務
require net/http
Net::HTTPstart(localhost ) do |http|
response = httpget(/people/list Accept => text/xml)
#Do something with the response
puts Code: #{de}
puts Message: #{ssage}
puts Body:\n #{responsebody}
end
清單 中的 Web 服務調用在//localhost:/people/list 上調用 HTTP get
方法並輸出響應Ruby 有很好的庫可以處理生成的 XML但是它們超出了本文的范圍不需要用 Ruby 調用這個服務只需要 HTTP 的庫清單 顯示這個服務的 Java 調用
清單 用 Java 代碼調用服務
package comrapidredws;
import *;
import javaio*;
public class SimpleGet {
void get() {
try {
URL url = new URL(//localhost:/people/list);
URLConnection urlConnection = urlopenConnection();
urlConnectionsetRequestProperty(accept text/xml);
BufferedReader in =
new BufferedReader(new InputStreamReader(urlConnectiongetInputStream()));
String str;
while ((str = inreadLine()) != null) {
Systemoutprintln(str);
}
inclose();
}
catch (Exception e) {
Systemoutprintln(e);
}
}
像其 Ruby 等價物一樣這個代碼打開一個 URL 連接把 Accept
頭設置成 text/xml
發出 get
並輸出結果Java 代碼有許多 XML 框架但是我在這個示例中硬編碼了 XML以保持示例簡單
post
的調用也相似清單 顯示了簡單的 post
清單 用 Java 代碼調用 HTTP post
void post() {
try {
String xmlText = <person> +
<firstname>Maggie</firstname> +
<lastname>Maggie</lastname> +
<email></email> +
</person>;
URL url = new URL(//localhost:/people/create);
HttpURLConnection conn = (HttpURLConnection)urlopenConnection();
connsetDoOutput(true);
connsetRequestMethod(POST);
connsetRequestProperty(ContentType text/xml);
OutputStreamWriter wr = new
OutputStreamWriter(conngetOutputStream());
wrwrite(xmlText);
wrflush();
BufferedReader rd = new BufferedReader(new
InputStreamReader(conngetInputStream()));
String line;
while ((line = rdreadLine()) != null) {
Systemoutprintln(line);
}
wrclose();
rdclose();
} catch (Exception e) {
Systemoutprintln(Error + e);
}
}
這個 HTTP post
通過在//localhost:/people/create 上調用 post
並在 HTTP 文檔體中傳遞一個 XML 文檔創建了一個新 Person
(通常應當用 Java XML 庫構建 XML 文檔這次我還是硬編碼了 XML 文檔以保持示例簡單)Rails 支持自動把進入的 XML 轉換成 Person
屬性的 Ruby 散列表
結束語
在本文中已經看到只用少量代碼就使控制器支持基於 REST 的 Web 服務動態類型化的 Internet 語句例如 Ruby大量地利用 REST 代替基於 SOAP 的 Web 服務一些簡單的調用包括漂亮的 responds_to
語法和對進入提交的自動 XML 轉換使得可以容易地利用同一控制器處理 Web 服務遠程 JavaScript 請求或 HTML
Java 語言對 REST 也有非常好的支持畢竟servlet 實際上是服務器端基於 REST 的 Web 服務可以在 Java 端使用 servlet在 Ruby 端使用 Rails 控制器把利用兩個平台優勢的應用程序組合在一起這就是 Web 服務的漂亮之處您真正需要的所有東西就是超群出眾的勇氣
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26105.html