Restlet項(xiàng)目(http://www.restlet.org)為“建立REST概念與Java類(lèi)之間的映射”提供了一個(gè)輕量級(jí)而全面的框架。它可用于實(shí)現(xiàn)任何種類(lèi)的REST式系統(tǒng),而不僅僅是REST式Web服務(wù);而且,事實(shí)證明它自從2005年誕生之時(shí)起,就是一個(gè)可靠的軟件。
estlet項(xiàng)目受到Servlet API、JSP(Java Server
Pages)、HttpURLConnection及Struts等Web開(kāi)發(fā)技術(shù)的影響。該項(xiàng)目的主要目標(biāo)是:在提供同等功能的同時(shí),盡量遵守Roy
Fielding博士論文中所闡述的REST的目標(biāo)。它的另一個(gè)主要目標(biāo)是:提出一個(gè)既適于客戶(hù)端應(yīng)用又適于服務(wù)端的應(yīng)用的、統(tǒng)一的Web視圖。
Restlet的思想是:HTTP客戶(hù)端與HTTP服務(wù)器之間的差別,對(duì)架構(gòu)來(lái)說(shuō)無(wú)所謂。一個(gè)軟件應(yīng)可以既充當(dāng)Web客戶(hù)端又充當(dāng)Web服務(wù)器,而無(wú)須采用兩套完全不同的APIs。
Restlet包括Restlet API和Noelios Restlet Engine(NRE)兩部分,NRE是對(duì)Restlet
API的一種參考實(shí)現(xiàn)。這種劃分,使得不同實(shí)現(xiàn)可以具有同樣的API。NRE包括若干HTTP服務(wù)器連接器(HTTP server
connector),它們都是基于Mortbay的Jetty、Codehaus的AsyncWeb,以及Simple框架這些流行的HTTP
Java開(kāi)源項(xiàng)目的。它甚至提供一個(gè)適配器(adapter),使你可以在標(biāo)準(zhǔn)Servlet容器(如Apache
Tomcat)內(nèi)部署一個(gè)Restlet應(yīng)用。
Restlet還提供兩個(gè)HTTP客戶(hù)端連接器(HTTP client
connector)。它們一個(gè)是基于官方的HttpURLConnection類(lèi),一個(gè)是基于A(yíng)pache的HTTP客戶(hù)端庫(kù)。還有一個(gè)連接器允許你容
易地按REST風(fēng)格通過(guò)XML文檔來(lái)處理JDBC源(source);此外,一個(gè)基于JavaMail
API的SMTP連接器允許你發(fā)送內(nèi)容為XML的Email。
Restlet
API包括一些能夠創(chuàng)建基于字符串、文件、流(stream)、通道(channel)及XML文檔的表示(representation),它支持
SAX、DOM及XSLT。使用FreeMaker或Apache
Velocity模板引擎,你可以很容易地創(chuàng)建基于JSP式模板的表示(representations)。你甚至可以像普通Web服務(wù)器那樣,用一個(gè)支
持內(nèi)容協(xié)商(content negotiation)的Directory類(lèi)來(lái)返回靜態(tài)文件與目錄。
簡(jiǎn)單性(simplicity)和靈活性(flexibility)是貫穿整個(gè)框架的設(shè)計(jì)原則。Restlet API旨在把HTTP、URI及REST的概念抽象成一系列類(lèi)(classes),同時(shí)又不把低層信息(如原始HTTP報(bào)頭)完全隱藏起來(lái)。
基本概念
Restlet在術(shù)語(yǔ)上參照了Roy
Fielding博士論文在講解REST時(shí)采用的術(shù)語(yǔ),如:資源(resource)、表示(representation)、連接器
(connector)、組件(component)、媒體類(lèi)型(media
type)、語(yǔ)言(language),等等。這些術(shù)語(yǔ)你應(yīng)該不會(huì)陌生。Restlet增加了一些專(zhuān)門(mén)的類(lèi)(如Application、Filter、
Finder、Router和Route),用以簡(jiǎn)化restlets的彼此結(jié)合,以及簡(jiǎn)化把收到的請(qǐng)求(incoming
requests)映射為處理它們的資源。
圖12-1:Restlet的類(lèi)層次結(jié)構(gòu)
抽象類(lèi)Uniform及其具體子類(lèi)Restlet,是Restlet的核心概念。正如其名稱(chēng)所暗示的,Uniform暴露一個(gè)符合REST規(guī)定的統(tǒng)
一接口(uniform interface)。雖然該接口是按HTTP統(tǒng)一接口定義的,但它也可用于其他協(xié)議(如FTP和SMTP)。
handle是一個(gè)重要的方法,它接受兩個(gè)參數(shù):Request和Response。正如你可以從圖12-1中看到的,每個(gè)暴露于網(wǎng)上的調(diào)用處理者
(call
handler)(無(wú)論作為客戶(hù)端還是服務(wù)端)都是Restlet的一個(gè)子類(lèi)——也就是說(shuō),它是一個(gè)restlets——并遵守這個(gè)統(tǒng)一接口。由于有統(tǒng)一
接口,restlets可以非常復(fù)雜的方式組合在一起。
Restlet支持的每一個(gè)協(xié)議都是通過(guò)handle方法暴露的。這就是說(shuō),HTTP(服務(wù)器和客戶(hù)端)、HTTPS、SMTP,以及JDBC、文
件系統(tǒng),甚至類(lèi)加載器(class loaders)都是通過(guò)調(diào)用handle方法來(lái)操作的。這減少了開(kāi)發(fā)者需掌握的APIs的數(shù)量。
過(guò)濾、安全、數(shù)據(jù)轉(zhuǎn)換及路由是“通過(guò)把Restlet的子類(lèi)鏈起來(lái)”進(jìn)行處理的。Filters可以在處理下個(gè)restlet調(diào)用之前或之后進(jìn)行處
理。Filters實(shí)例的工作方式與Rails過(guò)濾器差不多,只不過(guò)Filters實(shí)例跟其他Restlet類(lèi)一樣響應(yīng)handle方法,而不是具有一個(gè)
專(zhuān)門(mén)的API。
一個(gè)Router restlet有許多附屬的Restlet對(duì)象,它把每個(gè)收到的協(xié)議調(diào)用(incoming protocol
call)路由給適當(dāng)?shù)腞estlet處理器。路由(routing)通常是根據(jù)目標(biāo)URI進(jìn)行的。跟Rails不同的是,Restlet沒(méi)有對(duì)資源層次
結(jié)構(gòu)(resource hierarchy)作URI規(guī)則限定,所以可以隨意設(shè)置想要的URI,只要對(duì)Routers作相應(yīng)編程就行了。
除了這一常見(jiàn)用途,Router還可用于其他用途。你可以用一個(gè)Router在多臺(tái)遠(yuǎn)程機(jī)器之間以動(dòng)態(tài)負(fù)載均衡的方式來(lái)轉(zhuǎn)發(fā)調(diào)用。即使在這種復(fù)雜的
情況下,它也一樣響應(yīng)Restlet的統(tǒng)一接口,并且可成為一個(gè)更大路由系統(tǒng)中的一個(gè)組件。VirtualHost類(lèi)(Router的一個(gè)子類(lèi))使我們可
以在同一臺(tái)物理主機(jī)上運(yùn)行多個(gè)具有不同域名的應(yīng)用。在過(guò)去,你要引入一個(gè)前端Web服務(wù)器(如Apache的httpd)才能實(shí)現(xiàn)此功能;而用
Restlet的話(huà),它只是另一個(gè)響應(yīng)統(tǒng)一接口的Router實(shí)現(xiàn)。
一個(gè)Application對(duì)象能夠管理一組restlets,并提供常見(jiàn)的服務(wù),比方說(shuō)對(duì)壓縮的請(qǐng)求進(jìn)行透明解碼,或者利用method查詢(xún)參數(shù)
在重載的POST(overloaded
POST)之上實(shí)現(xiàn)PUT和DELETE請(qǐng)求。最后,Component對(duì)象可以包含并編配(orchestrate)一組Connectors、
VirtualHosts及Applications(作為獨(dú)立Java應(yīng)用運(yùn)行的,或者嵌入在一個(gè)更大系統(tǒng)(如J2EE環(huán)境)中的)。
在第6章,我向你介紹了“把一個(gè)問(wèn)題劃分為一組響應(yīng)HTTP統(tǒng)一接口的資源”的步驟。在第7章,為了處理Ruby on
Rails的簡(jiǎn)單化假設(shè)(simplifying
assumptions),我對(duì)該步驟作了相應(yīng)的調(diào)整。因?yàn)镽estlet沒(méi)有做簡(jiǎn)單化假設(shè)(simplifying
assumptions),所以我們無(wú)須對(duì)此步驟進(jìn)行修改。它可以實(shí)現(xiàn)任何REST式系統(tǒng)。如果你剛好想實(shí)現(xiàn)一個(gè)REST式面向資源的Web服務(wù),可以按
愿意的方式來(lái)組織和實(shí)現(xiàn)這些資源。Restlet確實(shí)提供了一些便于創(chuàng)建面向資源的應(yīng)用的類(lèi)。其中特別值得一提的是Resource類(lèi),它可作為你所有應(yīng)
用資源的基礎(chǔ)。
我在本書(shū)中一直用URI模板作為一組URIs的簡(jiǎn)化表達(dá)(見(jiàn)第9章)。Restlet用URI模板來(lái)進(jìn)行URI與資源的映射。假如用Restlet來(lái)實(shí)現(xiàn)第7章那個(gè)社會(huì)性書(shū)簽服務(wù)的話(huà),它也許要指定一個(gè)代表特定書(shū)簽的URI:
/users/{username}/bookmarks/{URI}
你可以在把Resource子類(lèi)附加到Router上時(shí)使用這種語(yǔ)法。假如你不相信真會(huì)這么好的話(huà),可以等到下一節(jié),那時(shí)我會(huì)實(shí)際實(shí)現(xiàn)部分書(shū)簽服務(wù)。
編寫(xiě)Restlet客戶(hù)端
在示例2-1中,你看到的是一個(gè)從Yahoo!搜索服務(wù)獲取XML搜索結(jié)果的Ruby客戶(hù)端。示例12-3是一個(gè)用Java參照Restlet 1.0實(shí)現(xiàn)的具有同樣功能的客戶(hù)端。要確保把以下JAR包寫(xiě)在你的classpath中,才能成功編譯并運(yùn)行接下來(lái)的例子:
- org.restlet.jar(Restlet API)
- com.noelios.restlet.jar (Noelios Restlet Engine核心)
- com.noelios.restlet.ext.net.jar (基于JDK的HttpURLConnection的HTTP客戶(hù)端連接器)
這些JAR包可以在Restlet發(fā)布包中的lib目錄里找到。要確保你的Java環(huán)境支持Java SE
5.0(或更高)版本。如果你確實(shí)需要的話(huà),可以用Retrotranslator(http://retrotranslator.
sourceforge.net/)輕易地把Restlet代碼反移植(backport)到J2SE 4.0版上去。
示例12-3:Yahoo!搜索服務(wù)的一個(gè)Restlet客戶(hù)端
// YahooSearch.java
import org.restlet.Client;
import org.restlet.data.Protocol;
import org.restlet.data.Reference;
import org.restlet.data.Response;
import org.restlet.resource.DomRepresentation;
import org.w3c.dom.Node;
/**
* 用返回XML的Yahoo!搜索服務(wù)來(lái)搜索Web
*/
public class YahooSearch {
static final String BASE_URI =
"http://api.search.yahoo.com/WebSearchService/V1/webSearch";
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("You need to pass a term to search");
} else {
// 獲取一個(gè)資源,即一個(gè)包含搜索結(jié)果的XML文檔
String term = Reference.encode(args[0]);
String uri = BASE_URI + "?appid=restbook&query=" + term;
Response response = new Client(Protocol.HTTP).get(uri);
DomRepresentation document = response.getEntityAsDom();
// 用XPath找出數(shù)據(jù)結(jié)構(gòu)中重要部分
String expr = "/ResultSet/Result/Title";
for (Node node : document.getNodes(expr)) {
System.out.println(node.getTextContent());
}
}
}
}
跟示例2-1一樣,你可以在執(zhí)行這個(gè)類(lèi)時(shí)把一個(gè)搜索關(guān)鍵字作為命令行參數(shù)傳給它。比如像下面這樣:
$ java YahooSearch xslt
XSL Transformations (XSLT)
The Extensible Stylesheet Language Family (XSL)
XSLT Tutorial
...
該示例證明了“用Restlet從Web服務(wù)獲取XML數(shù)據(jù),并用標(biāo)準(zhǔn)工具處理它”是極其簡(jiǎn)單的事。Yahoo!資源的URI是用一個(gè)常量和用戶(hù)提
供的搜索關(guān)鍵字構(gòu)造而成的。客戶(hù)端連接器(client
connector)是用HTTP協(xié)議來(lái)初始化的。XML文檔是通過(guò)get方法獲得的,該方法對(duì)應(yīng)于HTTP統(tǒng)一接口的GET方法。當(dāng)調(diào)用返回時(shí),程序?qū)?
得到一個(gè)DOM表示。跟前面的Ruby例子一樣,XPath是對(duì)XML進(jìn)行查詢(xún)的最簡(jiǎn)單方式。
跟前面的Ruby例子一樣,這個(gè)程序也忽略了XML文檔里的XML名稱(chēng)空間(namespaces)。Yahoo!為整個(gè)文檔采用名稱(chēng)空間urn:
yahoo:srch,但我是直接引用標(biāo)簽的,比方說(shuō),我用ResultSet,而不是urn:yahoo:srch:ResultSet。前面的
Ruby例子忽略名稱(chēng)空間,是因?yàn)镽uby的默認(rèn)XML解析器不支持名稱(chēng)空間。Java的XML解析器支持名稱(chēng)空間,而且Restlet
API令正確處理名稱(chēng)空間變得更加容易。雖然對(duì)上面那個(gè)簡(jiǎn)單例子來(lái)說(shuō),它們區(qū)別不大,但支持名稱(chēng)空間可以避免一些因名稱(chēng)空間而導(dǎo)致的微妙的問(wèn)題。
當(dāng)然,若一直用urn:yahoo:srch:ResultSet,是比較煩人的。Restlet
API可以容易地把一個(gè)簡(jiǎn)短前綴跟一個(gè)名稱(chēng)空間進(jìn)行關(guān)聯(lián),然后就可以在XPath表達(dá)式中使用這個(gè)簡(jiǎn)短前綴而不是整個(gè)名稱(chēng)空間了。示例12-4對(duì)示例12
-3后半部分代碼作了改動(dòng),它使用了帶名稱(chēng)空間的Xpath,這樣就不會(huì)把來(lái)自Yahoo!的ResultSet標(biāo)簽與來(lái)自其他名稱(chēng)空間的標(biāo)簽搞混了。
示例12-4:支持名稱(chēng)空間的文檔處理代碼
DomRepresentation document = response.getEntityAsDom();
// 把該名稱(chēng)空間與前綴‘y’關(guān)聯(lián)起來(lái)
document.setNamespaceAware(true);
document.putNamespace("y", "urn:yahoo:srch");
// 用XPath找出數(shù)據(jù)結(jié)構(gòu)中重要部分
String expr = "/y:ResultSet/y:Result/y:Title/text()";
for (Node node : document.getNodes(expr)) {
System.out.println(node.getTextContent());
}
示例2-15是Yahoo!搜索服務(wù)的另一個(gè)Ruby客戶(hù)端。它請(qǐng)求的是JSON格式(而不是XML格式)的搜索數(shù)據(jù)。示例12-5是一個(gè)與之功能等價(jià)的Restlet客戶(hù)端。它通過(guò)Restlet里的另兩個(gè)JAR文件獲取JSON支持:
- org.restlet.ext.json_2.0.jar(用于JSON的Restlet擴(kuò)展)
- org.json_2.0/org.json.jar(JSON官方程序庫(kù))
示例12-5:Yahoo!的JSON搜索服務(wù)的一個(gè)Restlet客戶(hù)端
// YahooSearchJSON.java
import org.json.JSONArray;
import org.json.JSONObject;
import org.restlet.Client;
import org.restlet.data.Protocol;
import org.restlet.data.Reference;
import org.restlet.data.Response;
import org.restlet.ext.json.JsonRepresentation;
/**
* 用返回JSON的Yahoo!搜索服務(wù)來(lái)搜索Web
*/
public class YahooSearchJSON {
static final String BASE_URI =
"http://api.search.yahoo.com/WebSearchService/V1/webSearch";
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("You need to pass a term to search");
} else {
// 獲取一個(gè)資源,即一個(gè)包含搜索結(jié)果的JSON文檔
String term = Reference.encode(args[0]);
String uri = BASE_URI + "?appid=restbook&output=json&query=" + term;
Response response = new Client(Protocol.HTTP).get(uri);
JSONObject json = new JsonRepresentation(response.getEntity())
.toJsonObject();
// 在JSON文檔中尋找并顯示標(biāo)題
JSONObject resultSet = json.getJSONObject("ResultSet");
JSONArray results = resultSet.getJSONArray("Result");
for (int i = 0; i < results.length(); i++) {
System.out.println(results.getJSONObject(i).getString("Title"));
}
}
}
}
當(dāng)你為Yahoo!的Web服務(wù)編寫(xiě)客戶(hù)端時(shí),可以選擇表示格式(representation
format)。Restlet核心API支持XML,另外還可以通過(guò)擴(kuò)展支持JSON。正如你所預(yù)料的那樣,這兩個(gè)例子的區(qū)別僅僅在于對(duì)響應(yīng)的處理上。
JsonRepresentation類(lèi)可以把響應(yīng)實(shí)體主體(response
entity-body)轉(zhuǎn)換成一個(gè)JSONObject實(shí)例(而Ruby的JSON庫(kù)是把JSON數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換成一個(gè)本地?cái)?shù)據(jù)結(jié)構(gòu))。該數(shù)據(jù)結(jié)構(gòu)只能進(jìn)
行人工遍歷,因?yàn)槟壳癑SON中還沒(méi)有類(lèi)似XPath的查詢(xún)語(yǔ)言。
編寫(xiě)Restlet服務(wù)
接下來(lái)的例子會(huì)稍微復(fù)雜一些。我將向你展示如何設(shè)計(jì)并實(shí)現(xiàn)一個(gè)服務(wù)端應(yīng)用。在第7章,我用Ruby on Rails實(shí)現(xiàn)了一個(gè)書(shū)簽管理應(yīng)用,現(xiàn)在我用Restlet來(lái)重新實(shí)現(xiàn)其部分功能。為了簡(jiǎn)單起見(jiàn),該應(yīng)用只支持對(duì)用戶(hù)及其書(shū)簽進(jìn)行安全的(safe)操作。
Java包結(jié)構(gòu)是這樣的:
org restlet
example
book
rest
ch7
-Application
-ApplicationTest
-Bookmark
-BookmarkResource
-BookmarksResource
-User
-UserResource
也就是說(shuō),Bookmark等類(lèi)都在org.restlet.example.book.rest.ch7包里。
我不打算在此展示完整的代碼。如果需要,你可以去本書(shū)的官方網(wǎng)站(http://www.oreilly.
com/catalog/9780596529260),那里提供了本書(shū)的所有示例程序代碼。你也可以在restlet.org(http:
//www.restlet.org)上找到本例的完整代碼。如果你已經(jīng)下載了Restlet的話(huà),那么也可以在
src/org/restlet.example/org/restlet/example/book/rest目錄里找到本節(jié)的示例代碼。
我將從一些簡(jiǎn)單的代碼開(kāi)始。示例12-6是Application.main方法,它用來(lái)建立Web服務(wù)器,并開(kāi)始處理請(qǐng)求。
示例12-6:Application.main方法:建立Web服務(wù)器
public static void main(String... args) throws Exception {
// 用HTTP服務(wù)器連接器創(chuàng)建一個(gè)組件
Component comp = new Component();
comp.getServers().add(Protocol.HTTP, 3000);
// 把應(yīng)用附加到默認(rèn)主機(jī)上,并啟動(dòng)
comp.getDefaultHost().attach("/v1", new Application());
comp.start();
}
資源與URI設(shè)計(jì)
由于Restlet未對(duì)資源設(shè)計(jì)作特別的限制,所以你完全可以根據(jù)ROA的設(shè)計(jì)原則來(lái)進(jìn)行資源類(lèi)(resource
classes)及URIs的設(shè)計(jì)。在第7章,我要圍繞“Rails的基于控制器的架構(gòu)”來(lái)進(jìn)行設(shè)計(jì);而這里,我不需要圍繞Restlet架構(gòu)來(lái)進(jìn)行設(shè)
計(jì)。圖12-2 展示了URI是如何經(jīng)由Router映射到資源,再映射到下層restlet類(lèi)的。
圖12-2:社會(huì)性書(shū)簽應(yīng)用的Restlet架構(gòu)
為了理解如何用Java代碼實(shí)現(xiàn)這些映射,我們來(lái)看一下Application類(lèi)及它的createRoot 方法(見(jiàn)示例12-7)。它跟示例7-3所示的Rails routes.rb文件在功能上是等價(jià)的。
示例12-7:Application.createRoot方法:實(shí)現(xiàn)URI模板到restlet的映射
public Restlet createRoot() {
Router router = new Router(getContext());
// 為用戶(hù)資源增加路由
router.attach("/users/{username}", UserResource.class);
// 為用戶(hù)的書(shū)簽資源增加路由
router.attach("/users/{username}/bookmarks", BookmarksResource.class);
// 為書(shū)簽資源增加路由
Route uriRoute = router.attach("/users/{username}/bookmarks/{URI}",
BookmarkResource.class);
uriRoute.getTemplate().getVariables()
.put("URI", new Variable(Variable.TYPE_URI_ALL));
}
在我創(chuàng)建一個(gè)Application對(duì)象(比如像示例12-6中的那樣)時(shí),這段代碼便會(huì)運(yùn)行。它會(huì)在資源類(lèi)UserResource與URI模板
“/users/(username)”之間建立起清晰而直觀(guān)的映射關(guān)系。Router先拿請(qǐng)求的目標(biāo)URI跟URI模板(URI
templates)進(jìn)行比較,然后把請(qǐng)求轉(zhuǎn)發(fā)給一個(gè)新建的相應(yīng)的資源類(lèi)實(shí)例。模板變量的值被存放在請(qǐng)求的屬性地圖(attributes
map)里(跟Rails例子中的params地圖類(lèi)似),以便于在Resource代碼中使用。這既有效,又易于理解;當(dāng)你事隔很久再回顧代碼時(shí),這很
有幫助。
請(qǐng)求處理和表示
假定一個(gè)客戶(hù)端向URI
http://localhost:3000/v1/users/jerome發(fā)出GET請(qǐng)求。我有一個(gè)監(jiān)聽(tīng)本地主機(jī)3000端口的Component對(duì)
象,和一個(gè)隸屬于 /v1
的Application對(duì)象。該Application有一個(gè)Router和一組Route對(duì)象,這些Route對(duì)象正等待著跟各個(gè)URI模板匹配的請(qǐng)
求。
URI路徑片段“/users/jerome”跟模板“/users/{username}”相匹配,而該模板的Route是與UserResource
類(lèi)(大致等價(jià)于Rails UsersController類(lèi))相關(guān)聯(lián)的。
Restlet通過(guò)初始化一個(gè)新的UserResource對(duì)象,并調(diào)用它的handleGet方法來(lái)處理該請(qǐng)求。示例12-8是UserResource類(lèi)的構(gòu)造方法。
示例12-8:UserResource類(lèi)的構(gòu)造方法
/**
* 構(gòu)造方法
*
* @param context
* 上級(jí)上下文
* @param request
* 要處理的請(qǐng)求
* @param response
* 要返回的響應(yīng)
*/
public UserResource(Context context, Request request, Response response) {
super(context, request, response);
this.userName = (String) request.getAttributes().get("username");
ChallengeResponse cr = request.getChallengeResponse();
this.login = (cr != null) ? cr.getIdentifier() : null;
this.password = (cr != null) ? cr.getSecret() : null;
this.user = findUser();
if (user != null) {
getVariants().add(new Variant(MediaType.TEXT_PLAIN));
}
}
至此,這個(gè)架構(gòu)已經(jīng)建立了一個(gè)Request對(duì)象,它包含了我所需要的關(guān)于請(qǐng)求的所有信息。username屬性來(lái)自URI,認(rèn)證證書(shū)來(lái)自請(qǐng)求的
Authorization報(bào)頭。我還調(diào)用findUser方法來(lái)根據(jù)認(rèn)證證書(shū)在數(shù)據(jù)庫(kù)中查找用戶(hù)(為節(jié)省篇幅,我就不在此展示findUser方法的代
碼了)。這些工作在第7章都是由Rails過(guò)濾器完成的。
在框架把一個(gè)UserResource實(shí)例化后,它會(huì)對(duì)資源對(duì)象調(diào)用適當(dāng)?shù)膆andle方法。HTTP統(tǒng)一接口中的每一個(gè)方法,都有一個(gè)對(duì)應(yīng)handle方法。 在這個(gè)例子中,Restlet架構(gòu)最后的任務(wù)是調(diào)用UserResource.handleGet。
由于我沒(méi)有定義UserResource.handleGet這個(gè)方法,所以它將具有繼承Resource.
handleGet方法的行為。HandleGet的默認(rèn)行為是找到最符合客戶(hù)端要求的資源的表示。客戶(hù)端通過(guò)內(nèi)容協(xié)商(content-
negotiation)來(lái)表達(dá)它的要求。Restlet通過(guò)Accept報(bào)頭的值來(lái)決定返回哪個(gè)表示。由于這里只有一個(gè)表示格式,所以客戶(hù)端的要求不起
作用。這是由getVariants和getRepresentation方法處理的。由于在上述構(gòu)造方法中把text/
plain定義為唯一支持的表示格式,所以我的getRepresentation方法的實(shí)現(xiàn)是很簡(jiǎn)單的(見(jiàn)示例12-9)。
示例12-9:UserResoure.getRepresentation:構(gòu)造一個(gè)用戶(hù)的表示
@Override
public Representation getRepresentation(Variant variant) {
Representation result = null;
if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) {
// 創(chuàng)建一個(gè)文本表示
StingBuilder sb=new StringBuilder();
sb.append("------------\n");
sb.append("User details\n");
sb.append("------------\n\n");
sb.append("Name: ").append(this.user.getFullName()).append('\n');
sb.append("Email: ").append(this.user.getEmail()).append('\n');
result = new StringRepresentation(sb);
}
return result;
}
雖然這只是一個(gè)資源的一個(gè)方法,但其他資源,以及UserResource的其他HTTP方法的工作原理都差不多,比如:對(duì)用戶(hù)的PUT請(qǐng)求將被路
由給UserResource.handlePut,等等。正如我前面所提到的,這里的代碼只是社會(huì)性書(shū)簽應(yīng)用所有代碼的一部分;所以,如果你有興趣進(jìn)一
步學(xué)習(xí)的話(huà),可以去下載一個(gè)完整的示例代碼來(lái)閱讀。
現(xiàn)在,你應(yīng)該了解Restlet架構(gòu)是如何把收到的(incoming)HTTP請(qǐng)求路由給特定的Resource類(lèi),然后再路由給該類(lèi)的特定方法
了。你也應(yīng)該知道如何由資源狀態(tài)來(lái)構(gòu)造表示(representations)了。一般,只要關(guān)注Application和Router代碼一次就行,因
為一個(gè)Router可用于你的所有資源。
編譯、運(yùn)行與測(cè)試
Application類(lèi)實(shí)現(xiàn)了運(yùn)行社會(huì)性書(shū)簽服務(wù)的HTTP服務(wù)器。你需要在classpath中加入以下JAR文件:
- org.restlet.jar
- com.noelios.restlet.jar
- com.noelios.restlet.ext.net.jar
- org.simpleframework_3.1/org.simpleframework.jar
- com.noelios.restlet.ext.simple_3.1.jar
- com.db4o_6.1/com.db4o.jar
這些JAR包可以在Restlet發(fā)布包中的lib目錄里找到。有兩點(diǎn)需要注意:第一,Web服務(wù)器的實(shí)際工作是由一個(gè)非常緊湊的、基于
Simple框架的HTTP服務(wù)器連接器來(lái)處理的;第二,我們是用強(qiáng)大的db4o對(duì)象數(shù)據(jù)庫(kù)(而不是關(guān)系數(shù)據(jù)庫(kù))來(lái)存儲(chǔ)領(lǐng)域?qū)ο螅ㄓ脩?hù)和書(shū)簽)的。在編譯
好所有示例文件后,運(yùn)行org.restlet.example.book.rest.ch7.
Application,它將作為服務(wù)器的端點(diǎn)(endpoint)。
ApplicationTest類(lèi)為服務(wù)提供了一個(gè)客戶(hù)端接口。它采用上節(jié)描述的Restlet客戶(hù)端類(lèi)來(lái)添加和刪除用戶(hù)和書(shū)簽。它是通過(guò)HTTP統(tǒng)一接口進(jìn)行工作的:用PUT請(qǐng)求創(chuàng)建用戶(hù)和書(shū)簽,用DELETE請(qǐng)求刪除用戶(hù)和書(shū)簽。
在命令行下運(yùn)行ApplicationTest類(lèi),你將得到以下消息:
Usage depends on the number of arguments:
- Deletes a user : userName, password
- Deletes a bookmark : userName, password, URI
- Adds a new user : userName, password, "full name", email
- Adds a new bookmark : userName, password, URI, shortDescription,
longDescription, restrict[true / false]
你可以用這個(gè)程序來(lái)添加一些用戶(hù),并增加一些書(shū)簽。然后,你就可以在Web瀏覽器中通過(guò)訪(fǎng)問(wèn)適當(dāng)?shù)腢RI(如http://localhost:3000/v1/users/jerome等)來(lái)瀏覽用戶(hù)書(shū)簽的HTML表示了。
小結(jié)
Restlet項(xiàng)目在2007年初發(fā)布了1.0正式版。它只用了12個(gè)多月的開(kāi)發(fā)時(shí)間。目前,該項(xiàng)目具有一個(gè)繁榮的開(kāi)發(fā)與用戶(hù)群體。Restlet
郵件列表很友好,不論是新手,還是有經(jīng)驗(yàn)的開(kāi)發(fā)者,它都?xì)g迎。作為該項(xiàng)目的創(chuàng)建者,Noelios咨詢(xún)公司是主要的開(kāi)發(fā)力量,他們也提供專(zhuān)業(yè)的支持計(jì)劃與
培訓(xùn)。
在本書(shū)編寫(xiě)之時(shí),1.0版處于維護(hù)中,新的1.1版已經(jīng)開(kāi)始開(kāi)發(fā)了。該項(xiàng)目計(jì)劃將來(lái)把Restlet API提交給JCP(Java
Community
Process)。還有一個(gè)用于REST式Web服務(wù)的高層API,它已由Sun公司提交給JCP(JSR311)。這個(gè)高層API使得“把Java領(lǐng)域
對(duì)象暴露為REST式資源”更加容易。這將是對(duì)Restlet
API(尤其是其Resource類(lèi))的一個(gè)很好的補(bǔ)充。Noelios咨詢(xún)公司是最初的專(zhuān)家組成員,他們將根據(jù)標(biāo)準(zhǔn)的進(jìn)展來(lái)對(duì)Restlet引擎作相應(yīng)
的更新。