體驗rails1.2的REST風格
REST這個名詞已經聽過許久,在javaeye的ruby版上也看到不少的討論,一開始是搞不明白的,似乎跟webservice有關。今天讀了《RESTfull Rails Development》和幾篇介紹REST的文章開始有點明白。REST 是英文 Representational State Transfer 的縮寫,有中文翻譯為“具象狀態傳輸”。讀這篇文章《學習REST》對於初次接觸REST的人來說更好理解。我們在 Web 應用中處理來自客戶端的請求時,通常隻考慮 GET 和 POST 這兩種 HTTP 請求方法。實際上,HTTP 還有 HEAD、PUT、DELETE 等請求方法。而在 REST 架構中,用不同的 HTTP 請求方法來處理對資源的 CRUD(創建、讀取、更新和刪除)操作:
- POST: 創建
- GET: 讀取
- PUT: 更新
- DELETE: 刪除
經過這樣的一番擴展,我們對一個資源的 CRUD 操作就可以通過同一個 URI 完成了。需要注意的是REST的核心就是資源(resources)這個概念。我們所說的webservice是一種建立在http協議上的遠程調用,而REST就是把遠程調用抽象成對遠程資源的CRUD的操作,正好可以用HTTP的PUT GET POST DELETE來對應,而不是重新發明一個協議(比如soap,簡單對象訪問協議)。REST與AJAX的流行,甚至遠至設計模式的興起,都充分說明一個現象,在成熟的應用的基礎上創新而非擴展出複雜所謂“創新性”架構在軟件行業是更為可靠。
實戰體驗REST可以從IBM Developer的這篇文章開始《跨越邊界:Rest On Rails》。這篇文章是在Rails1.2發布之前出來的,有些地方已經可以修改的更簡練,我把我的練習過程記錄下,並添加了C#調用REST風格web service的例子。
首先,你的機器上需要安裝rails1.2,並且假設你對rails有基本的了解,建立一個應用叫service,命令行執行:
rails自動幫你生成應用的基本結構和基礎代碼,然後編輯config下麵的database.yml設置數據庫,並建立數據service_development,我用的是mysql數據庫。
利用rails1.2新的scaffold命令:
這個命令將自動生成ActiveRecord,Controller以及View,在\app\models下可以發現自動生成的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命令自動建表,執行
OK,一切就緒,啟動WEBric,訪問https://localhost:3000/people,顯示:

scaffold已經幫我們自動生成了一個對person資源的crud操作,增刪改查似乎跟傳統的rails沒有什麼不同嘛。如果你認真觀察在操作過程中URL的變化情況就會發現在操作過程中URL的變化很小,而且與傳統rails的URL路由相比,省去了action名稱。出現的變化在/people、/people/1、/people/1;edit和/people/new這幾個之中。在/people的URL中隱藏這可能是http的POST或者GET的方法,前者用於create操作,而GET用於show操作,具體你可以查看app/controllers/目錄下的PeopleController類,每個action的前麵都注釋了它們將對應哪個HTTP方法。而/people/1中的1指的是資源的標誌符,比如這裏person的id,通過這個ID來進行資源的操作,也許是PUT方法(更新),也許是DELETE方法(刪除)。rails實現PUT和Delete是通過隱藏字段來實現的,查看編輯頁麵生成的html源代碼,你將發現一個_method的隱藏字段,值為PUT。而另外兩個URL:/people/1;edit和/people/new,這兩個並非嚴格意義上的RESTful URL,它們隻是為了顯示用,顯示form表單用於新建和編輯。關於RESTful風格的URL的詳細討論請見《RESTfull Rails Development》文檔。
如果rails隻是這樣的威力,那就有點小提大做了,看看PeopleController的show action,它對應於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方法中,根據請求文件類型(http Header的ContentType),顯示html格式,或者xml格式(還有其他支持,比如json、RSS、Atom等等)。比如你添加了一個person,通過https://localhost:3000/people/1訪問,可以看到這個人員的具體信息:

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

不僅如此,我們也可以通過其他語言編寫客戶端來調用https://localhost:3000/people/1這個url,慢著,這不正是web service遠程調用嗎?沒錯,REST風格的web service相比於wsdl、soap定義的web service簡單了太多太多,也更加實用。我們來編寫一個java類調用https://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("https://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);
}
}
}
我們沒有什麼服務端接口class,我們也不用生成什麼stub,我們調用的最常見最常見的http協議,發送的是默認的GET請求,rails自動將該請求轉發給show action。注意,我們這裏把accept設置為text/xml,show方法根據此格式返回一個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請求是不夠的,我們說過,把遠程調用抽象成對遠程資源的CRUD操作,那麼如何create、delete和update遠程資源呢?同樣很簡單,比如我們通過C#遠程調用,創建一個新person,還記的我說過嗎?/people可以是POST請求,他將調用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("https://localhost:3000/people");
// 創建web請求
HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest;
// 設置請求類型為POST,調用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());
}
}
}
}
執行此程序,刷新https://localhost:3000/people,可以看到新建了一個人員如下

好極了,GET和POST都有了,那麼PUT對應的更新和DELETE對應的刪除又該怎麼做呢,唯一的區別就是設置請求類型不同而已,java調用如下:
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("https://localhost:3000/people/1");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
//設置請求為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("https://localhost:3000/people/2");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
//設置請求為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#添加的人員刪除掉。異構係統的遠程調用變的如此簡單很輕鬆,把什麼EJB、CORBA、SOAP統統忘掉吧。想象這樣的場景,所有的網站都提供REST風格的API,這個世界將是什麼模樣?
REST帶來的不僅僅是web service的改變,對MVC架構同樣具有很重要的意義,過去我們的複用通常在MODEL層,我們一直希望複用業務邏輯層,卻沒有想過是否能複用Controller甚至View呢?REST為我們提供了可能,比如以一個很經常被提到的例子來說,用戶加入某個圈子這個操作跟圈子的管理員將用戶加入圈子的操作是一樣,但是操作成功後的跳轉顯示的頁麵也許不同,過去也許我們是通過寫兩個不同的Action來實現,而現在,同一個Action(加入圈子這個操作)隻負責發送數據(XML格式的文檔),而頁麵的展示將留給客戶端去選擇,從而複用了Controller,減少了Action和View層的代碼量。進一步,請你想象,REST與AJAX的技術結合產生多麼有趣的畫麵。REST僅用於提供數據,展現更多的交給了客戶端。
本文僅僅是我接觸REST這兩天的學習總結,對於REST的應用才剛剛起步,需要更多的探討和實踐。其實java實現REST也是相當簡單的,servlet本身就是很好的模型,恐怕沒有多人注意到HttpServlet類中的doPut和doDelete方法,我們過去太強調GET和POST,反而忽視了PUT和DELETE可能帶來的改變。java開源世界中已經有了REST風格的框架,比如cetia4,這是一個servlet-base的REST框架,值的關注。
文章轉自莊周夢蝶 ,原文發布時間5.17
最後更新:2017-05-17 12:02:00