http://www.matrix.org.cn/resource/article/2007-11-30/1312be72-9f14-11dc-bd16-451eadcf4db4.html
Restlet 指南[Matrix社區(qū)試讀版]
作者:cleverpig
關于本指南
本指南的翻譯工作經(jīng)過了Restlet社區(qū)的官方授權,cleverpig作為貢獻者完成了本文的翻譯和整理工作。在此發(fā)布Matrix社區(qū)試讀版的目的是為了讓更多的技術愛好者閱讀并提出翻譯中的不足之處,以提高本指南的質(zhì)量,以期修改后正式發(fā)布。
Servlet的限制
在2003年末,Jetty Web容器的作者、Servlet規(guī)范的貢獻者:Greg Wilkins在其博客上對Servlet的問題進行了如下總計:
* 沒有對協(xié)議與應用之間的關系進行清洗的劃分。
* 由于在設計Servlet時存在對阻塞IO的假設,因此不能充分利用非阻塞NIO機制。
* 所有的Servlet Web容器對于某些應用來講是過度設計的。
他提出構思新的API規(guī)范,使其能夠真實地脫離協(xié)議,并定義能夠暴露內(nèi)容和元數(shù)據(jù)的contentlets。這些想法就是Restlet項目創(chuàng)建的靈感源泉。在之后的文章中,Greg Wilkins解釋了為什么當前Servlet API限制非阻塞NIO API得到高效使用的詳細理由:這種傳統(tǒng)的用法針對每個HTTP請求都創(chuàng)建獨立的線程進行處理。并提出了他對下一代Servlet技術的設想。
另一個主要問題就是Servlet API鼓勵應用開發(fā)者在應用或者用戶會話級別直接將session狀態(tài)保存于內(nèi)存中,盡管這看上去不錯,但它造成了Servlet容器擴展性和高可用性的主要問題。為了克服這些問題,就必須實現(xiàn)復雜的負載均衡、session復制、持久化機制。這導致了可擴展性必然成為災難。
Restlet簡介
當復雜核心化模式日趨強大之時,面向?qū)ο笤O計范例已經(jīng)不總是Web開發(fā)中的最佳選擇,Java開發(fā)者需要認識到這一點,并且在開發(fā)新的Web服務端或是AJAX Web客戶端時開始思考更加RESTfully的設計。Restlet這個開源項目為那些要采用REST結構體系來構建應用程序的Java開發(fā)者提供了一個具體的解決方案。它的非常簡單易用的功能和RESTfully的Web框架,這使其成為了Web2.0開發(fā)中的又一利器。好吧,朋友們,下面就讓我們開始Restlet探索之旅吧!
1. 注冊一個Restlet實現(xiàn)
Restlet框架由兩部分構成。第一部分是"Restlet API", 這個中立的API完美地實現(xiàn)了REST概念并簡化了客戶端和服務端應用的調(diào)用處理。在使用它之前,我們還需要一個支持此API的Restlet實現(xiàn)。Restlet的諸多實現(xiàn)可以通過開源項目或者商業(yè)產(chǎn)品獲得。

API與實現(xiàn)的分離和Servlet API與web容器的分離(就像Jetty或Tomcat)、JDBC API與相應JDBC驅(qū)動的分離非常類似。目前,"Noelios Restlet Engine" (縮寫為NRE)是Restle tAPI的參考實現(xiàn)之一。當下載Restlet發(fā)布版本時,API和NRE就綁定在一起,以備隨時使用。如果你需要使用不同的實現(xiàn),那么只需要添加JAR 文件到classpath,并刪除com.noelios.restlet.jar這個NRE的JAR文件即可。
API實現(xiàn)的注冊過程是完全自動的,如果你對此存在疑問,那么請參考JAR規(guī)范。當完成實現(xiàn)裝載工作后,它將自動回調(diào)org.restlet.util.Engine.setInstance()方法,來進行自注冊。
2. 接收Web頁面的內(nèi)容
正如我們在Restlet介紹中所提到的,Restlet框架即是一個客戶端,又是一個服務端框架。例如,NRE能夠簡單地通過它的HTTP客戶端 connector(連接器)訪問遠程資源。在REST中,connector是一種軟件元素,它使兩個component(組件)之間能夠進行通訊,其典型的實現(xiàn)方式是通過某種網(wǎng)絡協(xié)議完成通訊。NRE提供了多種客戶端connector實現(xiàn),這些實現(xiàn)都基于現(xiàn)存的開源項目。在connector一節(jié)中,列舉出了所有可用的客戶端、服務端connector,并解釋了如何使用和配置它們。
下面,我們將獲取一個現(xiàn)存資源的表示法(representation )并將其輸出在JVM控制臺:
請注意上面的示例使用了最簡單的方式:通過通用的客戶端類(generic Client class)調(diào)用。更加靈活的方式是創(chuàng)建一個新的Request對象,然后請求客戶端去處理它。下面的示例展示了如何在調(diào)用時設置首選項(例如 referrer URI)。當然,也可以是接收回應時的首選語言或者媒體類型:
3. 偵聽瀏覽器
現(xiàn)在,我們將了解一下Restlet框架是如何偵聽客戶端請求并作出回應的。這里,我們選用了NRE HTTP服務端connector(例如基于Jetty的HTTP服務端connector),返回簡單的字符串表達式“Hello World!”。請注意在更加實際的應用中,我們可以創(chuàng)建一個獨立的類,此類繼承自Restlet類,而不依靠這里的匿名內(nèi)部類。
Restlet類與Servlet非常相似,并且在RESTful應用中處理調(diào)用時提供了有限的幫助。我們后面將看到一個提供了一些特定子類的框架,它能夠更抽象、簡單地進行處理。下面讓我們先看一個簡單的示例:
如果你運行并啟動服務端,那么你可以打開瀏覽器輸入http://localhost:8182。實際上,輸入任何的URI都可以工作,你也可以嘗試一下http://localhost:8182/test/tutorial。值得注意的是,如果你從另一臺服務器上測試服務端,那么就需要將localhost替換為服務器的IP地址或者它的域名。
4. REST架構概述
讓我們先從REST的視角審視一下典型的web架構。在下面的圖表中,端口代表了connector,而后者負責component之間的通訊(組件在圖中被表示為大盒子)。鏈接代表了用于實際通訊的特定協(xié)議(HTTP,SMTP等)。

請注意,同一個component能夠具有任何數(shù)量的客戶端/服務端connector。例如,Web服務器B就具有一個用于回應用戶代理組件(User Agent component)的服務端connector,和多個發(fā)送請求到其它服務端的客戶端connector。
5. Component、virtual hosts和applications
另外,為了支持前面所表述的標準REST軟件架構元素,Restlet框架也提供了一套類:它們極大地簡化了在單一JVM中部署多個應用的工作。其目的在于提供一種RESTful、可移植的、比現(xiàn)存的Servlet API更加靈活的框架。在下面的圖表中,我們將看到三種Restlet,它們用于管理上述復雜情況:Components能夠管理多個Virtual Hosts和Applications。Virtual Hosts支持靈活的配置,例如同一個IP地址能夠分享多個域名、使用同一個域名實現(xiàn)跨越多個IP地址的負載均衡。最后,我們使用應用去管理一套相關的 Restlet、Resource、Representations。另外,應用確保了在不同Restlet實現(xiàn)、不同Virtual Hosts之上的可移植性和可配置性。這三種Restlet的協(xié)助為我們提供了眾多的功能:譬如訪問日志、請求自動解碼、配置狀態(tài)頁設置等。
為了展示這些類,讓我們嘗試一個簡單的示例。首先,我們創(chuàng)建一個component,然后在其上添加一個HTTP服務端connector,并偵聽 8182端口。接著創(chuàng)建一個簡單的、具有追蹤功能的Restlet,將它放置到組件默認的Virtual Hosts上。這個默認的主機將捕捉那些沒有路由到指定Virtual Hosts的請求(詳見Component.hosts屬性)。在后面的一個示例中,我們還將介紹應用類的使用方法。請注意,目前你并不能在控制臺輸出中看到任何的訪問日志。
讓我們通過在瀏覽器中輸入http://localhost:8182/trace/abc/def?param=123來進行測試,得到測試結果如下:
6. 為靜態(tài)文件提供服務
你遇到過提供靜態(tài)頁面(類似Javadocs)服務的web應用?如果正在使用,那么可以直接編寫一個Directory類,而無需為它建立Apache服務。請見下面如何使用:
正如你所注意到的,我們通過傳遞應用的父組件上下文(context)的方式來實例化應用,而不是在第5章中提到的代碼那樣簡單。而其主要原因是應用在分配客戶端請求時,請求的分配工作需要客戶端connector來完成,而后者被component所控制,并在所有被包含其中的應用之間貢獻。
為了運行此示例,你需要為ROOT_URI提供一個有效值,該值依賴于你的Restlet安裝路徑。默認情況下,它被設置為"file:///D: /Restlet/www/docs/api/"。請注意,這里不需要任何附加的配置。如果你希望自定義在文件擴展名和元數(shù)據(jù)(metadata,包括媒體類型、語言、編碼等)之間的映射,或是提供一個與眾不同的索引名,你可以使用應用的“metadataService”屬性。
7. 訪問日志
有目的地記錄web應用的活動是一種常見的需求。Restlet組件能夠在默認的情況下生成類似Apache風格的日志、甚至自定義日志。通過使用 JDK內(nèi)置的日志功能,logger能夠配置為像任何標準JDK日志那樣過濾信息、對它們進行重新格式化或者發(fā)送它們到指定位置。并且支持日志的循環(huán)(rotation);細節(jié)請查看java.util.logging包。
值得注意的是,你能夠通過修改component的"logService"屬性來為java.util.logging框架自定義logger名。如果希望完全掌控日志的配置,你需要通過設置系統(tǒng)屬性來聲明一個配置文件:
關于配置文件格式的細節(jié),請查看JDK的LogManager類。
8. 顯示錯誤頁
另外一個常見的需求是:在調(diào)用處理過程中某些期望結果沒有出現(xiàn)時,能夠自定義返回的狀態(tài)頁面。也許它是某個資源沒有找到或者一個可接受的表示是無效的。在這種情況下,或者遇到任何無法處理的異常時,Application或者Component將自動提供一個默認的狀態(tài)頁面。此服務與 org.restlet.util.StatusService類相關聯(lián),并可以作為被稱為“statusService”的Application或者 Component的屬性而被訪問。
為了自定義默認的信息,你只需要簡單地創(chuàng)建StatusService類的子類,并覆蓋其getRepresentation(Status, Request, Response)方法。然后設置這個類的實例為指定的“statusService”屬性即可。
9. 對敏感資源的訪問保護
當你需要保護對某些Restlet的訪問時,可以使用下面的方法:一種通用的方法是依靠cookie來識別客戶端(或者客戶端session),并根據(jù)你的應用狀態(tài)檢查給定的用戶ID或者session ID,從而判斷次訪問是否被允許。Restlet通過訪問Request或者Response中的Cookie和CookieSetting對象支持cookie。
另一種方法是基于標準HTTP認證機制。Neolios Restlet引擎目前允許基于簡單HTTP方案的證書發(fā)送、接收和基于Amazon Web服務方案的證書發(fā)送。
當接收到調(diào)用時,開發(fā)者能夠通過Request.challengeResponse.identifier/secret類中的Guard filter(保護過濾器)使用已經(jīng)解析好的證書。過濾器是一種特殊的Restlet,它能夠在調(diào)用相應Restlet之前進行預處理,或者在相應 Restlet調(diào)用返回后進行后期處理。如果你熟知Servlet API,這里的過濾器概念和Servlet API中的Filter接口非常接近。看一下我們?nèi)绾涡薷膹那暗拇a來對目錄訪問進行訪問保護:

請注意:認證和授權的最終結果是完全可定制的,這只需要通過authenticate()和authorize()方法便可完成。任何自定義的機制都能夠被用來檢查給定的證書是否有效、通過認證的用戶是否被授權繼續(xù)訪問相應Restlet。下面是我們簡單地硬編碼了用戶、密碼對。為了測試,我們使用了客戶端Restlet API:
你可以修改這里的user ID或者password,來檢查服務端返回的response。請別忘記了在啟動客戶端之前,先執(zhí)行Restlet服務端程序。請注意,如果你從另一臺機器上測試服務端,那么在瀏覽器中輸入URI時需要將"localhost"替換為服務器的IP地址或者域名。由于使用了默認接收任何類型URI的 VirtualHost,因此服務端無需任何修改。
10. URI重寫和重定向
Restlet框架的另一個優(yōu)點是對cool URI的內(nèi)建支持。Jacob Nielsen在他的AlertBox中給出了對URI設計的重要性的絕佳描述。
首先介紹的工具是Redirector,它能夠?qū)ool URI重寫為另一個URI,并接著進行相應的自動重定向。這里支持一些重定向類型:通過客戶端/瀏覽器的外部重定向、類似代理行為的connector重定向。在下面的例子中,我們將基于Google為名為"mysite.org"的站點定義一個檢索服務。與URI相關的"/search"就是檢索服務,它通過"kwd"參數(shù)接收一些檢索關鍵字:
請注意,Redirector只需要三個參數(shù)。第一個參數(shù)是父級上下文,第二個參數(shù)定義了如何基于URI模板重寫URI。這里的URI模板將被Template類處理。第三個參數(shù)定義了重定向類型:出于簡化的目的,我們選擇了客戶端重定向。
同時,當調(diào)用被傳遞給application時,我們使用了Route類從request中提取查詢參數(shù)“kwd”。如果發(fā)現(xiàn)參數(shù),參數(shù)將被復制到request的“keywords”屬性中,以便Redirector在格式化目標URI時使用。
11. 路由器和分層URI
作為Redirector的補充,我們還具有另一個管理cool URI的工具:Router(路由器)。它們是一種特殊的Restlet,能夠使其它Restlet(例如Finder和Filter)依附于它們,并基于URI模板進行自動委派調(diào)用(delegate call)。通常,你可以將Router設置為Application的根。
這里,我們將解釋一下如何處理下面的URI模板:
1. /docs/ 用于顯示靜態(tài)文件
2. /users/{user} 用于顯示用戶帳號
3. /users/{user}/orders 用于顯示特定用戶的所有訂單
4. /users/{user}/orders/{order} 用于顯示特定的訂單
實際上,這些URI包含了可變的部分(在大括號中)并且沒有文件擴展名,這在傳統(tǒng)的web容器中很難處理。而現(xiàn)在,你只需要做的只是使用URI模板將目標Restlet附著到Router上。在Restlet框架運行時,與request的URI最為匹配的Route將接收調(diào)用,并調(diào)用它所附著的 Restlet。同時,request的屬性表也將自動更新為URI模板變量。

請看下面的具體實現(xiàn)代碼。在真實的應用中,你可能希望創(chuàng)建單獨的子類來代替我們這里使用的匿名類:
請注意,變量的值是直接從URI中提取的,因此這是沒有精確解碼的。為了實現(xiàn)這樣的工作,請查看手冊中的decode(String)方法。
12. 抵達目標資源
在前面的示例中,在從目標URI中提取那些有趣部分時,我們利用了Restlet框架非常靈活的路由特性對request進行路由。但是,我們沒有注意request方法和客戶端對于它所期望的response的偏好。于是,我們?nèi)绾尾拍軐estlet處理器和后臺系統(tǒng)、域?qū)ο舐?lián)系在一起呢?
到目前為止,我們已經(jīng)介紹了一些在Restlet中超越傳統(tǒng)Servlet API的特性。但我們并沒有在"Restlet"這個框架名稱中使用"REST"。如果你還沒有做的話,我推薦你學習一些關于REST架構風格和將其應用于Web應用的最佳實踐。這里提供了相關的FAQ記錄,希望能給你一些啟示,同時我們也運營著很有用的REST搜索引擎(基于Google)。如果你對傳統(tǒng)MVC框架有一定了解,那么你可以閱讀一下另一個FAQ記錄,它提供了對MVC與Restlet關系的詳細說明。

總結一下,request中含有標識目標資源的URI,而目標資源就是調(diào)用的主旨。這種資源信息被保存在Request.resourceRef屬性中,并能夠像我們之前所見那樣服務于路由機制。因此在處理request時的首要目標就是發(fā)現(xiàn)目標資源。。。Resource類的實例或者其子類中的某個。為了幫助我們完成此項任務,我們可以使用專用的Finder,一個Restlet子類,它將Resource類引用作為參數(shù)并在request到來時自動實例化它。然后Finder將動態(tài)將調(diào)用分配給最新創(chuàng)建的實例,實際上就是根據(jù)request方法調(diào)用它的handle*()方法中的某一個(handleGet,handleDelete等)。當然,我們可以自定義這種行為,甚至使用Router的attach()方法,將URI模板和 Resource類作為其參數(shù)透明地創(chuàng)建Finder!現(xiàn)在,讓我們看一下展示了示例中主框架類之間關系的全景圖表:
回到代碼中,我們在這里重構了Application.createRoot()方法。出于簡化目的,我們沒有提供具有靜態(tài)文件的目錄。你可以發(fā)現(xiàn)將Resource類直接指派給Router的方法。
我們最后將重審一下UserResource類。這個類繼承自org.restlet.resource.Resource類,因此它覆蓋了具有三個參數(shù)的構造方法。此方法初始化了"context"、"request"和"response"屬性。接著,我們使用從"/users/{user} "URI模板中提取出的"user"屬性,將它的值保存在一個方便使用的成員變量中。然后,我們便可以在整個application中查找與"user" 相關的域?qū)ο罅恕W罱K,我們聲明了用于暴露給用戶的表示變量(representation variants),在這個簡單的例子中只是文字而已。它將用于在運行時透明地完成一些內(nèi)容導航,以便為每個request選擇適合的變量,所有這些工作都是透明的。
你可以查看本指南中提供的代碼包并對應用進行測試,并能夠以僅接受Get請求的方式獲得在第十一章中的相同行為。如果你希望使用PUT方法,那么就需要在UserResource中創(chuàng)建一個"allowPut()"方法并簡單地返回"true",并且添加一個"put (Representation)"方法來處理調(diào)用。關于詳細內(nèi)容請查閱Restlet的Javadocs。
結論
我們已經(jīng)涵蓋了Restlet框架的許多方面。在你打算行動之前,讓我們先回顧一下展示了本指南的主要概念和它們之間關系的兩個層次圖表:

這里是核心表示類:

除了本指南,你最好的信息來源就是Restlet API的Javadocs、Restlet擴展和NRE。還可以閱讀一下connector一節(jié),它列舉出了客戶端和服務端connector,并解釋了如何使用、配置它們。集成一節(jié)列出了提供可插入特性的所有可用擴展:例如與servlet容器的集成、動態(tài)表示的生成等。你還可以在我們的討論組中提出問題并幫助別人。
相關資源:
http://www.restlet.org/documentation/1.0/tutorial
http://www.restlet.org/about/introduction
摘要:
當復雜核心化模式日趨強大之時,面向?qū)ο笤O計范例已經(jīng)不總是Web開發(fā)中的最佳選擇,Java開發(fā)者需要認識到這一點,并且在開發(fā)新的Web服務端或是 AJAX Web客戶端時開始思考更加RESTfully的設計。Restlet這個開源項目為那些要采用REST結構體系來構建應用程序的Java開發(fā)者提供了一個具體的解決方案。朋友們,下面就讓我們開始Restlet探索之旅吧!作者:cleverpig
關于本指南
本指南的翻譯工作經(jīng)過了Restlet社區(qū)的官方授權,cleverpig作為貢獻者完成了本文的翻譯和整理工作。在此發(fā)布Matrix社區(qū)試讀版的目的是為了讓更多的技術愛好者閱讀并提出翻譯中的不足之處,以提高本指南的質(zhì)量,以期修改后正式發(fā)布。
Servlet的限制
在2003年末,Jetty Web容器的作者、Servlet規(guī)范的貢獻者:Greg Wilkins在其博客上對Servlet的問題進行了如下總計:
* 沒有對協(xié)議與應用之間的關系進行清洗的劃分。
* 由于在設計Servlet時存在對阻塞IO的假設,因此不能充分利用非阻塞NIO機制。
* 所有的Servlet Web容器對于某些應用來講是過度設計的。
他提出構思新的API規(guī)范,使其能夠真實地脫離協(xié)議,并定義能夠暴露內(nèi)容和元數(shù)據(jù)的contentlets。這些想法就是Restlet項目創(chuàng)建的靈感源泉。在之后的文章中,Greg Wilkins解釋了為什么當前Servlet API限制非阻塞NIO API得到高效使用的詳細理由:這種傳統(tǒng)的用法針對每個HTTP請求都創(chuàng)建獨立的線程進行處理。并提出了他對下一代Servlet技術的設想。
另一個主要問題就是Servlet API鼓勵應用開發(fā)者在應用或者用戶會話級別直接將session狀態(tài)保存于內(nèi)存中,盡管這看上去不錯,但它造成了Servlet容器擴展性和高可用性的主要問題。為了克服這些問題,就必須實現(xiàn)復雜的負載均衡、session復制、持久化機制。這導致了可擴展性必然成為災難。
Restlet簡介
當復雜核心化模式日趨強大之時,面向?qū)ο笤O計范例已經(jīng)不總是Web開發(fā)中的最佳選擇,Java開發(fā)者需要認識到這一點,并且在開發(fā)新的Web服務端或是AJAX Web客戶端時開始思考更加RESTfully的設計。Restlet這個開源項目為那些要采用REST結構體系來構建應用程序的Java開發(fā)者提供了一個具體的解決方案。它的非常簡單易用的功能和RESTfully的Web框架,這使其成為了Web2.0開發(fā)中的又一利器。好吧,朋友們,下面就讓我們開始Restlet探索之旅吧!
1. 注冊一個Restlet實現(xiàn)
Restlet框架由兩部分構成。第一部分是"Restlet API", 這個中立的API完美地實現(xiàn)了REST概念并簡化了客戶端和服務端應用的調(diào)用處理。在使用它之前,我們還需要一個支持此API的Restlet實現(xiàn)。Restlet的諸多實現(xiàn)可以通過開源項目或者商業(yè)產(chǎn)品獲得。

API與實現(xiàn)的分離和Servlet API與web容器的分離(就像Jetty或Tomcat)、JDBC API與相應JDBC驅(qū)動的分離非常類似。目前,"Noelios Restlet Engine" (縮寫為NRE)是Restle tAPI的參考實現(xiàn)之一。當下載Restlet發(fā)布版本時,API和NRE就綁定在一起,以備隨時使用。如果你需要使用不同的實現(xiàn),那么只需要添加JAR 文件到classpath,并刪除com.noelios.restlet.jar這個NRE的JAR文件即可。
API實現(xiàn)的注冊過程是完全自動的,如果你對此存在疑問,那么請參考JAR規(guī)范。當完成實現(xiàn)裝載工作后,它將自動回調(diào)org.restlet.util.Engine.setInstance()方法,來進行自注冊。
2. 接收Web頁面的內(nèi)容
正如我們在Restlet介紹中所提到的,Restlet框架即是一個客戶端,又是一個服務端框架。例如,NRE能夠簡單地通過它的HTTP客戶端 connector(連接器)訪問遠程資源。在REST中,connector是一種軟件元素,它使兩個component(組件)之間能夠進行通訊,其典型的實現(xiàn)方式是通過某種網(wǎng)絡協(xié)議完成通訊。NRE提供了多種客戶端connector實現(xiàn),這些實現(xiàn)都基于現(xiàn)存的開源項目。在connector一節(jié)中,列舉出了所有可用的客戶端、服務端connector,并解釋了如何使用和配置它們。
下面,我們將獲取一個現(xiàn)存資源的表示法(representation )并將其輸出在JVM控制臺:
// Outputting the content of a Web page
Client client = new Client(Protocol.HTTP);
client.get("http://www.restlet.org").getEntity().write(System.out);
請注意上面的示例使用了最簡單的方式:通過通用的客戶端類(generic Client class)調(diào)用。更加靈活的方式是創(chuàng)建一個新的Request對象,然后請求客戶端去處理它。下面的示例展示了如何在調(diào)用時設置首選項(例如 referrer URI)。當然,也可以是接收回應時的首選語言或者媒體類型:
// Prepare the request
Request request = new Request(Method.GET, "http://www.restlet.org");
request.setReferrerRef("http://www.mysite.org");
// Handle it using an HTTP client connector
Client client = new Client(Protocol.HTTP);
Response response = client.handle(request);
// Write the response entity on the console
Representation output = response.getEntity();
output.write(System.out);
3. 偵聽瀏覽器
現(xiàn)在,我們將了解一下Restlet框架是如何偵聽客戶端請求并作出回應的。這里,我們選用了NRE HTTP服務端connector(例如基于Jetty的HTTP服務端connector),返回簡單的字符串表達式“Hello World!”。請注意在更加實際的應用中,我們可以創(chuàng)建一個獨立的類,此類繼承自Restlet類,而不依靠這里的匿名內(nèi)部類。
Restlet類與Servlet非常相似,并且在RESTful應用中處理調(diào)用時提供了有限的幫助。我們后面將看到一個提供了一些特定子類的框架,它能夠更抽象、簡單地進行處理。下面讓我們先看一個簡單的示例:
// Creating a minimal Restlet returning "Hello World"
Restlet restlet = new Restlet() {
@Override
public void handle(Request request, Response response) {
response.setEntity("Hello World!", MediaType.TEXT_PLAIN);
}
};
// Create the HTTP server and listen on port 8182
new Server(Protocol.HTTP, 8182, restlet).start();
如果你運行并啟動服務端,那么你可以打開瀏覽器輸入http://localhost:8182。實際上,輸入任何的URI都可以工作,你也可以嘗試一下http://localhost:8182/test/tutorial。值得注意的是,如果你從另一臺服務器上測試服務端,那么就需要將localhost替換為服務器的IP地址或者它的域名。
4. REST架構概述
讓我們先從REST的視角審視一下典型的web架構。在下面的圖表中,端口代表了connector,而后者負責component之間的通訊(組件在圖中被表示為大盒子)。鏈接代表了用于實際通訊的特定協(xié)議(HTTP,SMTP等)。

請注意,同一個component能夠具有任何數(shù)量的客戶端/服務端connector。例如,Web服務器B就具有一個用于回應用戶代理組件(User Agent component)的服務端connector,和多個發(fā)送請求到其它服務端的客戶端connector。
5. Component、virtual hosts和applications
另外,為了支持前面所表述的標準REST軟件架構元素,Restlet框架也提供了一套類:它們極大地簡化了在單一JVM中部署多個應用的工作。其目的在于提供一種RESTful、可移植的、比現(xiàn)存的Servlet API更加靈活的框架。在下面的圖表中,我們將看到三種Restlet,它們用于管理上述復雜情況:Components能夠管理多個Virtual Hosts和Applications。Virtual Hosts支持靈活的配置,例如同一個IP地址能夠分享多個域名、使用同一個域名實現(xiàn)跨越多個IP地址的負載均衡。最后,我們使用應用去管理一套相關的 Restlet、Resource、Representations。另外,應用確保了在不同Restlet實現(xiàn)、不同Virtual Hosts之上的可移植性和可配置性。這三種Restlet的協(xié)助為我們提供了眾多的功能:譬如訪問日志、請求自動解碼、配置狀態(tài)頁設置等。

為了展示這些類,讓我們嘗試一個簡單的示例。首先,我們創(chuàng)建一個component,然后在其上添加一個HTTP服務端connector,并偵聽 8182端口。接著創(chuàng)建一個簡單的、具有追蹤功能的Restlet,將它放置到組件默認的Virtual Hosts上。這個默認的主機將捕捉那些沒有路由到指定Virtual Hosts的請求(詳見Component.hosts屬性)。在后面的一個示例中,我們還將介紹應用類的使用方法。請注意,目前你并不能在控制臺輸出中看到任何的訪問日志。
// Create a new Restlet component and add a HTTP server connector to it
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8182);
// Create a new tracing Restlet
Restlet restlet = new Restlet() {
@Override
public void handle(Request request, Response response) {
// Print the requested URI path
String message = "Resource URI : " + request.getResourceRef()
+ '\n' + "Root URI : " + request.getRootRef()
+ '\n' + "Routed part : "
+ request.getResourceRef().getBaseRef() + '\n'
+ "Remaining part: "
+ request.getResourceRef().getRemainingPart();
response.setEntity(message, MediaType.TEXT_PLAIN);
}
};
// Then attach it to the local host
component.getDefaultHost().attach("/trace", restlet);
// Now, let's start the component!
// Note that the HTTP server connector is also automatically started.
component.start();
讓我們通過在瀏覽器中輸入http://localhost:8182/trace/abc/def?param=123來進行測試,得到測試結果如下:
Resource URI : http://localhost:8182/trace/abc/def?param=123
Root URI : http://localhost:8182/trace
Routed part : http://localhost:8182/trace
Remaining part: /abc/def?param=123
6. 為靜態(tài)文件提供服務
你遇到過提供靜態(tài)頁面(類似Javadocs)服務的web應用?如果正在使用,那么可以直接編寫一個Directory類,而無需為它建立Apache服務。請見下面如何使用:
// Create a component
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8182);
component.getClients().add(Protocol.FILE);
// Create an application
Application application = new Application(component.getContext()) {
@Override
public Restlet createRoot() {
return new Directory(getContext(), ROOT_URI);
}
};
// Attach the application to the component and start it
component.getDefaultHost().attach("", application);
component.start();
正如你所注意到的,我們通過傳遞應用的父組件上下文(context)的方式來實例化應用,而不是在第5章中提到的代碼那樣簡單。而其主要原因是應用在分配客戶端請求時,請求的分配工作需要客戶端connector來完成,而后者被component所控制,并在所有被包含其中的應用之間貢獻。
為了運行此示例,你需要為ROOT_URI提供一個有效值,該值依賴于你的Restlet安裝路徑。默認情況下,它被設置為"file:///D: /Restlet/www/docs/api/"。請注意,這里不需要任何附加的配置。如果你希望自定義在文件擴展名和元數(shù)據(jù)(metadata,包括媒體類型、語言、編碼等)之間的映射,或是提供一個與眾不同的索引名,你可以使用應用的“metadataService”屬性。
7. 訪問日志
有目的地記錄web應用的活動是一種常見的需求。Restlet組件能夠在默認的情況下生成類似Apache風格的日志、甚至自定義日志。通過使用 JDK內(nèi)置的日志功能,logger能夠配置為像任何標準JDK日志那樣過濾信息、對它們進行重新格式化或者發(fā)送它們到指定位置。并且支持日志的循環(huán)(rotation);細節(jié)請查看java.util.logging包。
值得注意的是,你能夠通過修改component的"logService"屬性來為java.util.logging框架自定義logger名。如果希望完全掌控日志的配置,你需要通過設置系統(tǒng)屬性來聲明一個配置文件:
System.setProperty("java.util.logging.config.file", "/your/path/logging.config");
關于配置文件格式的細節(jié),請查看JDK的LogManager類。
8. 顯示錯誤頁
另外一個常見的需求是:在調(diào)用處理過程中某些期望結果沒有出現(xiàn)時,能夠自定義返回的狀態(tài)頁面。也許它是某個資源沒有找到或者一個可接受的表示是無效的。在這種情況下,或者遇到任何無法處理的異常時,Application或者Component將自動提供一個默認的狀態(tài)頁面。此服務與 org.restlet.util.StatusService類相關聯(lián),并可以作為被稱為“statusService”的Application或者 Component的屬性而被訪問。
為了自定義默認的信息,你只需要簡單地創(chuàng)建StatusService類的子類,并覆蓋其getRepresentation(Status, Request, Response)方法。然后設置這個類的實例為指定的“statusService”屬性即可。
9. 對敏感資源的訪問保護
當你需要保護對某些Restlet的訪問時,可以使用下面的方法:一種通用的方法是依靠cookie來識別客戶端(或者客戶端session),并根據(jù)你的應用狀態(tài)檢查給定的用戶ID或者session ID,從而判斷次訪問是否被允許。Restlet通過訪問Request或者Response中的Cookie和CookieSetting對象支持cookie。
另一種方法是基于標準HTTP認證機制。Neolios Restlet引擎目前允許基于簡單HTTP方案的證書發(fā)送、接收和基于Amazon Web服務方案的證書發(fā)送。
當接收到調(diào)用時,開發(fā)者能夠通過Request.challengeResponse.identifier/secret類中的Guard filter(保護過濾器)使用已經(jīng)解析好的證書。過濾器是一種特殊的Restlet,它能夠在調(diào)用相應Restlet之前進行預處理,或者在相應 Restlet調(diào)用返回后進行后期處理。如果你熟知Servlet API,這里的過濾器概念和Servlet API中的Filter接口非常接近。看一下我們?nèi)绾涡薷膹那暗拇a來對目錄訪問進行訪問保護:
// Create a Guard
Guard guard = new Guard(getContext(),
ChallengeScheme.HTTP_BASIC, "Tutorial");
guard.getSecrets().put("scott", "tiger".toCharArray());
// Create a Directory able to return a deep hierarchy of files
Directory directory = new Directory(getContext(), ROOT_URI);
guard.setNext(directory);
return guard;

請注意:認證和授權的最終結果是完全可定制的,這只需要通過authenticate()和authorize()方法便可完成。任何自定義的機制都能夠被用來檢查給定的證書是否有效、通過認證的用戶是否被授權繼續(xù)訪問相應Restlet。下面是我們簡單地硬編碼了用戶、密碼對。為了測試,我們使用了客戶端Restlet API:
// Prepare the request
Request request = new Request(Method.GET, "http://localhost:8182/");
// Add the client authentication to the call
ChallengeScheme scheme = ChallengeScheme.HTTP_BASIC;
ChallengeResponse authentication = new ChallengeResponse(scheme,
"scott", "tiger");
request.setChallengeResponse(authentication);
// Ask to the HTTP client connector to handle the call
Client client = new Client(Protocol.HTTP);
Response response = client.handle(request);
if (response.getStatus().isSuccess()) {
// Output the response entity on the JVM console
response.getEntity().write(System.out);
} else if (response.getStatus()
.equals(Status.CLIENT_ERROR_UNAUTHORIZED)) {
// Unauthorized access
System.out
.println("Access authorized by the server, " +
"check your credentials");
} else {
// Unexpected status
System.out.println("An unexpected status was returned: "
+ response.getStatus());
}
你可以修改這里的user ID或者password,來檢查服務端返回的response。請別忘記了在啟動客戶端之前,先執(zhí)行Restlet服務端程序。請注意,如果你從另一臺機器上測試服務端,那么在瀏覽器中輸入URI時需要將"localhost"替換為服務器的IP地址或者域名。由于使用了默認接收任何類型URI的 VirtualHost,因此服務端無需任何修改。
10. URI重寫和重定向
Restlet框架的另一個優(yōu)點是對cool URI的內(nèi)建支持。Jacob Nielsen在他的AlertBox中給出了對URI設計的重要性的絕佳描述。
首先介紹的工具是Redirector,它能夠?qū)ool URI重寫為另一個URI,并接著進行相應的自動重定向。這里支持一些重定向類型:通過客戶端/瀏覽器的外部重定向、類似代理行為的connector重定向。在下面的例子中,我們將基于Google為名為"mysite.org"的站點定義一個檢索服務。與URI相關的"/search"就是檢索服務,它通過"kwd"參數(shù)接收一些檢索關鍵字:
// Create an application
Application application = new Application(component.getContext()) {
@Override
public Restlet createRoot() {
// Create a Redirector to Google search service
String target =
"http://www.google.com/search?q=site:mysite.org+{keywords}";
return new Redirector(getContext(), target,
Redirector.MODE_CLIENT_TEMPORARY);
}
};
// Attach the application to the component's default host
Route route = component.getDefaultHost().attach("/search", application);
// While routing requests to the application, extract a query parameter
// For instance :
// http://localhost:8182/search?kwd=myKeyword1+myKeyword2
// will be routed to
// http://www.google.com/search?q=site:mysite.org+myKeyword1%20myKeyword2
route.extractQuery("keywords", "kwd", true);
請注意,Redirector只需要三個參數(shù)。第一個參數(shù)是父級上下文,第二個參數(shù)定義了如何基于URI模板重寫URI。這里的URI模板將被Template類處理。第三個參數(shù)定義了重定向類型:出于簡化的目的,我們選擇了客戶端重定向。
同時,當調(diào)用被傳遞給application時,我們使用了Route類從request中提取查詢參數(shù)“kwd”。如果發(fā)現(xiàn)參數(shù),參數(shù)將被復制到request的“keywords”屬性中,以便Redirector在格式化目標URI時使用。
11. 路由器和分層URI
作為Redirector的補充,我們還具有另一個管理cool URI的工具:Router(路由器)。它們是一種特殊的Restlet,能夠使其它Restlet(例如Finder和Filter)依附于它們,并基于URI模板進行自動委派調(diào)用(delegate call)。通常,你可以將Router設置為Application的根。
這里,我們將解釋一下如何處理下面的URI模板:
1. /docs/ 用于顯示靜態(tài)文件
2. /users/{user} 用于顯示用戶帳號
3. /users/{user}/orders 用于顯示特定用戶的所有訂單
4. /users/{user}/orders/{order} 用于顯示特定的訂單
實際上,這些URI包含了可變的部分(在大括號中)并且沒有文件擴展名,這在傳統(tǒng)的web容器中很難處理。而現(xiàn)在,你只需要做的只是使用URI模板將目標Restlet附著到Router上。在Restlet框架運行時,與request的URI最為匹配的Route將接收調(diào)用,并調(diào)用它所附著的 Restlet。同時,request的屬性表也將自動更新為URI模板變量。

請看下面的具體實現(xiàn)代碼。在真實的應用中,你可能希望創(chuàng)建單獨的子類來代替我們這里使用的匿名類:
// Create a component
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8182);
component.getClients().add(Protocol.FILE);
// Create an application
Application application = new Application(component.getContext()) {
@Override
public Restlet createRoot() {
// Create a root router
Router router = new Router(getContext());
// Attach a guard to secure access to the directory
Guard guard = new Guard(getContext(),
ChallengeScheme.HTTP_BASIC, "Restlet tutorial");
guard.getSecrets().put("scott", "tiger".toCharArray());
router.attach("/docs/", guard);
// Create a directory able to expose a hierarchy of files
Directory directory = new Directory(getContext(), ROOT_URI);
guard.setNext(directory);
// Create the account handler
Restlet account = new Restlet() {
@Override
public void handle(Request request, Response response) {
// Print the requested URI path
String message = "Account of user \""
+ request.getAttributes().get("user") + "\"";
response.setEntity(message, MediaType.TEXT_PLAIN);
}
};
// Create the orders handler
Restlet orders = new Restlet(getContext()) {
@Override
public void handle(Request request, Response response) {
// Print the user name of the requested orders
String message = "Orders of user \""
+ request.getAttributes().get("user") + "\"";
response.setEntity(message, MediaType.TEXT_PLAIN);
}
};
// Create the order handler
Restlet order = new Restlet(getContext()) {
@Override
public void handle(Request request, Response response) {
// Print the user name of the requested orders
String message = "Order \""
+ request.getAttributes().get("order")
+ "\" for user \""
+ request.getAttributes().get("user") + "\"";
response.setEntity(message, MediaType.TEXT_PLAIN);
}
};
// Attach the handlers to the root router
router.attach("/users/{user}", account);
router.attach("/users/{user}/orders", orders);
router.attach("/users/{user}/orders/{order}", order);
// Return the root router
return router;
}
};
// Attach the application to the component and start it
component.getDefaultHost().attach(application);
component.start();
請注意,變量的值是直接從URI中提取的,因此這是沒有精確解碼的。為了實現(xiàn)這樣的工作,請查看手冊中的decode(String)方法。
12. 抵達目標資源
在前面的示例中,在從目標URI中提取那些有趣部分時,我們利用了Restlet框架非常靈活的路由特性對request進行路由。但是,我們沒有注意request方法和客戶端對于它所期望的response的偏好。于是,我們?nèi)绾尾拍軐estlet處理器和后臺系統(tǒng)、域?qū)ο舐?lián)系在一起呢?
到目前為止,我們已經(jīng)介紹了一些在Restlet中超越傳統(tǒng)Servlet API的特性。但我們并沒有在"Restlet"這個框架名稱中使用"REST"。如果你還沒有做的話,我推薦你學習一些關于REST架構風格和將其應用于Web應用的最佳實踐。這里提供了相關的FAQ記錄,希望能給你一些啟示,同時我們也運營著很有用的REST搜索引擎(基于Google)。如果你對傳統(tǒng)MVC框架有一定了解,那么你可以閱讀一下另一個FAQ記錄,它提供了對MVC與Restlet關系的詳細說明。

總結一下,request中含有標識目標資源的URI,而目標資源就是調(diào)用的主旨。這種資源信息被保存在Request.resourceRef屬性中,并能夠像我們之前所見那樣服務于路由機制。因此在處理request時的首要目標就是發(fā)現(xiàn)目標資源。。。Resource類的實例或者其子類中的某個。為了幫助我們完成此項任務,我們可以使用專用的Finder,一個Restlet子類,它將Resource類引用作為參數(shù)并在request到來時自動實例化它。然后Finder將動態(tài)將調(diào)用分配給最新創(chuàng)建的實例,實際上就是根據(jù)request方法調(diào)用它的handle*()方法中的某一個(handleGet,handleDelete等)。當然,我們可以自定義這種行為,甚至使用Router的attach()方法,將URI模板和 Resource類作為其參數(shù)透明地創(chuàng)建Finder!現(xiàn)在,讓我們看一下展示了示例中主框架類之間關系的全景圖表:
回到代碼中,我們在這里重構了Application.createRoot()方法。出于簡化目的,我們沒有提供具有靜態(tài)文件的目錄。你可以發(fā)現(xiàn)將Resource類直接指派給Router的方法。
// Create a router
Router router = new Router(getContext());
// Attach the resources to the router
router.attach("/users/{user}", UserResource.class);
router.attach("/users/{user}/orders", OrdersResource.class);
router.attach("/users/{user}/orders/{order}",
OrderResource.class);
// Return the root router
return router;
我們最后將重審一下UserResource類。這個類繼承自org.restlet.resource.Resource類,因此它覆蓋了具有三個參數(shù)的構造方法。此方法初始化了"context"、"request"和"response"屬性。接著,我們使用從"/users/{user} "URI模板中提取出的"user"屬性,將它的值保存在一個方便使用的成員變量中。然后,我們便可以在整個application中查找與"user" 相關的域?qū)ο罅恕W罱K,我們聲明了用于暴露給用戶的表示變量(representation variants),在這個簡單的例子中只是文字而已。它將用于在運行時透明地完成一些內(nèi)容導航,以便為每個request選擇適合的變量,所有這些工作都是透明的。
public class UserResource extends Resource {
String userName;
Object user;
public UserResource(Context context, Request request,
Response response) {
super(context, request, response);
this.userName = (String) request.getAttributes().get("user");
this.user = null; // Could be a lookup to a domain object.
// Here we add the representation variants exposed
getVariants().add(new Variant(MediaType.TEXT_PLAIN));
}
@Override
public Representation getRepresentation(Variant variant) {
Representation result = null;
if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) {
result = new StringRepresentation("Account of user \""
+ this.userName + "\"");
}
return result;
}
}
你可以查看本指南中提供的代碼包并對應用進行測試,并能夠以僅接受Get請求的方式獲得在第十一章中的相同行為。如果你希望使用PUT方法,那么就需要在UserResource中創(chuàng)建一個"allowPut()"方法并簡單地返回"true",并且添加一個"put (Representation)"方法來處理調(diào)用。關于詳細內(nèi)容請查閱Restlet的Javadocs。
結論
我們已經(jīng)涵蓋了Restlet框架的許多方面。在你打算行動之前,讓我們先回顧一下展示了本指南的主要概念和它們之間關系的兩個層次圖表:

這里是核心表示類:

除了本指南,你最好的信息來源就是Restlet API的Javadocs、Restlet擴展和NRE。還可以閱讀一下connector一節(jié),它列舉出了客戶端和服務端connector,并解釋了如何使用、配置它們。集成一節(jié)列出了提供可插入特性的所有可用擴展:例如與servlet容器的集成、動態(tài)表示的生成等。你還可以在我們的討論組中提出問題并幫助別人。
相關資源:
http://www.restlet.org/documentation/1.0/tutorial
http://www.restlet.org/about/introduction