體驗rails1.2的REST風(fēng)格
Posted on 2007-03-20 20:04 dennis 閱讀(2920) 評論(0) 編輯 收藏 所屬分類: 動態(tài)語言 、java 、C#歷程??? 我們在 Web 應(yīng)用中處理來自客戶端的請求時,通常只考慮 GET 和 POST 這兩種 HTTP 請求方法。實際上,HTTP 還有 HEAD、PUT、DELETE 等請求方法。而在 REST 架構(gòu)中,用不同的 HTTP 請求方法來處理對資源的 CRUD(創(chuàng)建、讀取、更新和刪除)操作:
- POST: 創(chuàng)建
- GET: 讀取
- PUT: 更新
- DELETE: 刪除
經(jīng)過這樣的一番擴展,我們對一個資源的 CRUD 操作就可以通過同一個 URI 完成了。需要注意的是REST的核心就是資源(resources)這個概念。我們所說的webservice是一種建立在http協(xié)議上的遠(yuǎn)程調(diào)用,而REST就是把遠(yuǎn)程調(diào)用抽象成對遠(yuǎn)程資源的CRUD的操作,正好可以用HTTP的PUT GET POST DELETE來對應(yīng),而不是重新發(fā)明一個協(xié)議(比如soap,簡單對象訪問協(xié)議)。REST與AJAX的流行,甚至遠(yuǎn)至設(shè)計模式的興起,都充分說明一個現(xiàn)象,在成熟的應(yīng)用的基礎(chǔ)上創(chuàng)新而非擴展出復(fù)雜所謂“創(chuàng)新性”架構(gòu)在軟件行業(yè)是更為可靠。
??? 實戰(zhàn)體驗REST可以從IBM Developer的這篇文章開始《跨越邊界:Rest On Rails》。這篇文章是在Rails1.2發(fā)布之前出來的,有些地方已經(jīng)可以修改的更簡練,我把我的練習(xí)過程記錄下,并添加了C#調(diào)用REST風(fēng)格web service的例子。
??? 首先,你的機器上需要安裝rails1.2,并且假設(shè)你對rails有基本的了解,建立一個應(yīng)用叫service,命令行執(zhí)行:
??rails自動幫你生成應(yīng)用的基本結(jié)構(gòu)和基礎(chǔ)代碼,然后編輯config下面的database.yml設(shè)置數(shù)據(jù)庫,并建立數(shù)據(jù)service_development,我用的是mysql數(shù)據(jù)庫。
利用rails1.2新的scaffold命令:
這個命令將自動生成ActiveRecord,Controller以及View,在\app\models下可以發(fā)現(xiàn)自動生成的Model——person.rb。打開service\db\migrate下面的001_create_people.rb,編輯如下:
??def?self.up
????create_table?:people?do?|t|
?????t.column?:first_name,?:string,?:limit?=>?40
?????t.column?:last_name,?:string,?:limit?=>?40
?????t.column?:email,?:string,?:limit?=>?40
?????t.column?:phone,?:string,?:limit?=>?15
????end
??end
??def?self.down
????drop_table?:people
??end
end
利用rake命令自動建表,執(zhí)行
OK,一切就緒,啟動WEBric,訪問http://localhost:3000/people,顯示:

scaffold已經(jīng)幫我們自動生成了一個對person資源的crud操作,增刪改查似乎跟傳統(tǒng)的rails沒有什么不同嘛。如果你認(rèn)真觀察在操作過程中URL的變化情況就會發(fā)現(xiàn)在操作過程中URL的變化很小,而且與傳統(tǒng)rails的URL路由相比,省去了action名稱。出現(xiàn)的變化在/people、/people/1、/people/1;edit和/people/new這幾個之中。在/people的URL中隱藏這可能是http的POST或者GET的方法,前者用于create操作,而GET用于show操作,具體你可以查看app/controllers/目錄下的PeopleController類,每個action的前面都注釋了它們將對應(yīng)哪個HTTP方法。而/people/1中的1指的是資源的標(biāo)志符,比如這里person的id,通過這個ID來進(jìn)行資源的操作,也許是PUT方法(更新),也許是DELETE方法(刪除)。rails實現(xiàn)PUT和Delete是通過隱藏字段來實現(xiàn)的,查看編輯頁面生成的html源代碼,你將發(fā)現(xiàn)一個_method的隱藏字段,值為PUT。而另外兩個URL:/people/1;edit和/people/new,這兩個并非嚴(yán)格意義上的RESTful URL,它們只是為了顯示用,顯示form表單用于新建和編輯。關(guān)于RESTful風(fēng)格的URL的詳細(xì)討論請見《RESTfull Rails Development》文檔。
??? 如果rails只是這樣的威力,那就有點小提大做了,看看PeopleController的show action,它對應(yīng)于http的GET請求,返回people列表:
??#?GET?/people/1.xml
??def?show
????@person?=?Person.find(params[:id])
????respond_to?do?|format|
??????format.html?#?show.rhtml
??????format.xml??{?render?:xml?=>?@person.to_xml?}
????end
??end
神奇的地方在respond_to方法中,根據(jù)請求文件類型(http Header的ContentType),顯示html格式,或者xml格式(還有其他支持,比如json、RSS、Atom等等)。比如你添加了一個person,通過http://localhost:3000/people/1訪問,可以看到這個人員的具體信息:

我們再通過http://localhost:3000/people/3.xml訪問看到的卻是一個xml文件:

不僅如此,我們也可以通過其他語言編寫客戶端來調(diào)用http://localhost:3000/people/1這個url,慢著,這不正是web service遠(yuǎn)程調(diào)用嗎?沒錯,REST風(fēng)格的web service相比于wsdl、soap定義的web service簡單了太多太多,也更加實用。我們來編寫一個java類調(diào)用http://localhost:3000/people獲得所有的人員列表:
import?java.io.BufferedReader;
import?java.io.InputStreamReader;
import?java.io.OutputStreamWriter;
import?java.net.HttpURLConnection;
import?java.net.URL;
import?java.net.URLConnection;
public?class?RESTDemo?{
????/**
?????*?@param?args
?????*/
????public?static?void?main(String[]?args)?{
????????RESTDemo?restDemo?=?new?RESTDemo();
????????????restDemo.get();
????????
????}
????void?get()?{
????????try?{
????????????URL?url?=?new?URL("http://localhost:3000/people");
????????????URLConnection?urlConnection?=?url.openConnection();
????????????urlConnection.setRequestProperty("accept",?"text/xml");
????????????BufferedReader?in?=?new?BufferedReader(new?InputStreamReader(
????????????????????urlConnection.getInputStream()));
????????????String?str;
????????????while?((str?=?in.readLine())?!=?null)?{
????????????????System.out.println(str);
????????????}
????????????in.close();
????????}?catch?(Exception?e)?{
????????????System.out.println(e);
????????}
????}
}
我們沒有什么服務(wù)端接口class,我們也不用生成什么stub,我們調(diào)用的最常見最常見的http協(xié)議,發(fā)送的是默認(rèn)的GET請求,rails自動將該請求轉(zhuǎn)發(fā)給show action。注意,我們這里把accept設(shè)置為text/xml,show方法根據(jù)此格式返回一個xml文檔,下面是輸出:
<people>
??<person>
????<email>killme2008@gmail.com</email>
????<first-name>dennis</first-name>
????<id?type="integer">1</id>
????<last-name>zane</last-name>
????<phone>1355XXXXXXX</phone>
??</person>
</people>
如果僅僅是GET請求是不夠的,我們說過,把遠(yuǎn)程調(diào)用抽象成對遠(yuǎn)程資源的CRUD操作,那么如何create、delete和update遠(yuǎn)程資源呢?同樣很簡單,比如我們通過C#遠(yuǎn)程調(diào)用,創(chuàng)建一個新person,還記的我說過嗎?/people可以是POST請求,他將調(diào)用PeopleController的create方法:
using?System.Net;
using?System.IO;
using?System.Text;
namespace?demo
{
????class?RESTDemo
????{
????????static?void?Main(string[]?args)
????????{
????????????string?xmlText?=?"<person>?"?+?"<first-name>jordan</first-name>"
????????????????????+?"<last-name>jordan</last-name>"
????????????????????+?"<email>maggie@tate.com</email>"
????????????????????+?"<phone>010-XXXXXXXX</phone>"?+?"</person>";
????????????Uri?address?=?new?Uri("http://localhost:3000/people");??
???
????????????//?創(chuàng)建web請求
????????????HttpWebRequest?request?=?WebRequest.Create(address)?as?HttpWebRequest;??
???
????????????//?設(shè)置請求類型為POST,調(diào)用create?action
????????????request.Method?=?"POST";??
????????????request.ContentType?=?"application/xml";
????????????byte[]?xmlBytes?=?Encoding.ASCII.GetBytes(xmlText);
????????????using?(Stream?reqStream?=?request.GetRequestStream())
????????????{
????????????????reqStream.Write(xmlBytes,?0,?xmlBytes.Length);
????????????}
????????????using?(WebResponse?wr?=?request.GetResponse())
????????????{
????????????????wr.
????????????????//打印返回的http頭
????????????????Console.WriteLine(wr.Headers.ToString());
???????????????
????????????}??????????????
???????????
????????}
????}
}
執(zhí)行此程序,刷新http://localhost:3000/people,可以看到新建了一個人員如下

好極了,GET和POST都有了,那么PUT對應(yīng)的更新和DELETE對應(yīng)的刪除又該怎么做呢,唯一的區(qū)別就是設(shè)置請求類型不同而已,java調(diào)用如下:
????????try?{
????????????String?xmlText?=?"<person>?"?+?"<first-name>test</first-name>"
????????????????????+?"<last-name>test</last-name>"
????????????????????+?"<email>maggie@tate.com</email>"
????????????????????+?"<phone>010-XXXXXXXX</phone>"?+?"</person>";
????????????URL?url?=?new?URL("http://localhost:3000/people/1");
????????????HttpURLConnection?conn?=?(HttpURLConnection)?url.openConnection();
????????????conn.setDoOutput(true);
??????????? //設(shè)置請求為PUT
????????????conn.setRequestMethod("PUT");
????????????conn.setRequestProperty("Content-Type",?"text/xml");
????????????OutputStreamWriter?wr?=?new?OutputStreamWriter(conn
????????????????????.getOutputStream());
????????????wr.write(xmlText);
????????????wr.flush();
????????????wr.close();
????????}?catch?(Exception?e)?{
????????????System.out.println("Error"?+?e);
????????}
????}
????void?delete()?{
????????try?{
????????????URL?url?=?new?URL("http://localhost:3000/people/2");
????????????HttpURLConnection?conn?=?(HttpURLConnection)?url.openConnection();
????????????conn.setDoOutput(true);
??????????? //設(shè)置請求為DELETE
????????????conn.setRequestMethod("DELETE");
????????????conn.setRequestProperty("Content-Type",?"text/xml");
????????????if(conn.getResponseCode()==200)
????????????????System.out.println("刪除成功!");
????????}catch?(Exception?e)?{
????????????System.out.println("Error"?+?e);
????????}
????}
這里的put方法將第一個人員的名字改了,而delete方法干脆將剛才C#添加的人員刪除掉。異構(gòu)系統(tǒng)的遠(yuǎn)程調(diào)用變的如此簡單很輕松,把什么EJB、CORBA、SOAP統(tǒng)統(tǒng)忘掉吧。想象這樣的場景,所有的網(wǎng)站都提供REST風(fēng)格的API,這個世界將是什么模樣?
??? REST帶來的不僅僅是web service的改變,對MVC架構(gòu)同樣具有很重要的意義,過去我們的復(fù)用通常在MODEL層,我們一直希望復(fù)用業(yè)務(wù)邏輯層,卻沒有想過是否能復(fù)用Controller甚至View呢?REST為我們提供了可能,比如以一個很經(jīng)常被提到的例子來說,用戶加入某個圈子這個操作跟圈子的管理員將用戶加入圈子的操作是一樣,但是操作成功后的跳轉(zhuǎn)顯示的頁面也許不同,過去也許我們是通過寫兩個不同的Action來實現(xiàn),而現(xiàn)在,同一個Action(加入圈子這個操作)只負(fù)責(zé)發(fā)送數(shù)據(jù)(XML格式的文檔),而頁面的展示將留給客戶端去選擇,從而復(fù)用了Controller,減少了Action和View層的代碼量。進(jìn)一步,請你想象,REST與AJAX的技術(shù)結(jié)合產(chǎn)生多么有趣的畫面。REST僅用于提供數(shù)據(jù),展現(xiàn)更多的交給了客戶端。
??? 本文僅僅是我接觸REST這兩天的學(xué)習(xí)總結(jié),對于REST的應(yīng)用才剛剛起步,需要更多的探討和實踐。其實java實現(xiàn)REST也是相當(dāng)簡單的,servlet本身就是很好的模型,恐怕沒有多人注意到HttpServlet類中的doPut和doDelete方法,我們過去太強調(diào)GET和POST,反而忽視了PUT和DELETE可能帶來的改變。java開源世界中已經(jīng)有了REST風(fēng)格的框架,比如cetia4,這是一個servlet-base的REST框架,值的關(guān)注。