研究目的
使用rest的方式減少網址複雜度。
Servlet的限制
在2003年末,Jetty Web容器的作者、Servlet規範的貢獻者:Greg Wilkins在其博客上對Servlet的問題進行了如下總計:
* 沒有對協議與應用之間的關係進行清洗的劃分。
* 由於在設計Servlet時存在對阻塞IO的假設,因此不能充分利用非阻塞NIO機制。
* 所有的Servlet Web容器對於某些應用來講是過度設計的。
他提出構思新的API規範,使其能夠真實地脫離協議,並定義能夠暴露內容和中繼資料的contentlets。這些想法就是Restlet項目創建的靈感源泉。在之後的文章中,Greg Wilkins解釋了為什麼當前Servlet API限制非阻塞NIO API得到高效使用的詳細理由:這種傳統的用法針對每個HTTP請求都創建獨立的執行緒進行處理。並提出了他對下一代Servlet技術的設想。
* 沒有對協議與應用之間的關係進行清洗的劃分。
* 由於在設計Servlet時存在對阻塞IO的假設,因此不能充分利用非阻塞NIO機制。
* 所有的Servlet Web容器對於某些應用來講是過度設計的。
他提出構思新的API規範,使其能夠真實地脫離協議,並定義能夠暴露內容和中繼資料的contentlets。這些想法就是Restlet項目創建的靈感源泉。在之後的文章中,Greg Wilkins解釋了為什麼當前Servlet API限制非阻塞NIO API得到高效使用的詳細理由:這種傳統的用法針對每個HTTP請求都創建獨立的執行緒進行處理。並提出了他對下一代Servlet技術的設想。
Restlet簡介
當複雜核心化模式日趨強大之時,物件導向設計範例已經不總是Web開發中的最佳選擇,Java開發者需要認識到這一點,並且在開發新的Web服務端或是AJAX Web用戶端時開始思考更加RESTfully的設計。Restlet這個開源專案為那些要採用REST結構體系來構建應用程式的Java開發者提供了一個具體的解決方案。它的非常簡單易用的功能和RESTfully的Web框架,這使其成為了Web2.0開發中的又一利器。好吧,朋友們,下面就讓我們開始Restlet探索之旅吧!
當複雜核心化模式日趨強大之時,物件導向設計範例已經不總是Web開發中的最佳選擇,Java開發者需要認識到這一點,並且在開發新的Web服務端或是AJAX Web用戶端時開始思考更加RESTfully的設計。Restlet這個開源專案為那些要採用REST結構體系來構建應用程式的Java開發者提供了一個具體的解決方案。它的非常簡單易用的功能和RESTfully的Web框架,這使其成為了Web2.0開發中的又一利器。好吧,朋友們,下面就讓我們開始Restlet探索之旅吧!
REST架構概述
讓我們先從REST的視角審視一下典型的web架構。在下面的圖表中,埠代表了connector,而後者負責component之間的通訊(元件在圖中被表示為大盒子)。連結代表了用於實際通訊的特定協定(HTTP,SMTP等)。
請注意,同一個component能夠具有任何數量的用戶端/服務端connector。例如,Web伺服器B就具有一個用於回應使用者代理元件(User Agent component)的服務端connector,和多個發送請求到其它服務端的用戶端connector。
讓我們先從REST的視角審視一下典型的web架構。在下面的圖表中,埠代表了connector,而後者負責component之間的通訊(元件在圖中被表示為大盒子)。連結代表了用於實際通訊的特定協定(HTTP,SMTP等)。
請注意,同一個component能夠具有任何數量的用戶端/服務端connector。例如,Web伺服器B就具有一個用於回應使用者代理元件(User Agent component)的服務端connector,和多個發送請求到其它服務端的用戶端connector。
Component、virtual
hosts和applications
另外,為了支援前面所表述的標準REST軟體架構元素,Restlet框架也提供了一套類:它們極大地簡化了在單一JVM中部署多個應用的工作。其目的在於提供一種RESTful、可移植的、比現存的Servlet API更加靈活的框架。在下面的圖表中,我們將看到三種Restlet,它們用於管理上述複雜情況:Components能夠管理多個Virtual Hosts和Applications。Virtual Hosts支援靈活的配置,例如同一個IP位址能夠分享多個功能變數名稱、使用同一個功能變數名稱實現跨越多個IP位址的負載均衡。最後,我們使用應用去管理一套相關的 Restlet、Resource、Representations。另外,應用確保了在不同Restlet實現、不同Virtual Hosts之上的可攜性和可配置性。這三種Restlet的協助為我們提供了眾多的功能:譬如訪問日誌、請求自動解碼、配置狀態頁設置等。
另外,為了支援前面所表述的標準REST軟體架構元素,Restlet框架也提供了一套類:它們極大地簡化了在單一JVM中部署多個應用的工作。其目的在於提供一種RESTful、可移植的、比現存的Servlet API更加靈活的框架。在下面的圖表中,我們將看到三種Restlet,它們用於管理上述複雜情況:Components能夠管理多個Virtual Hosts和Applications。Virtual Hosts支援靈活的配置,例如同一個IP位址能夠分享多個功能變數名稱、使用同一個功能變數名稱實現跨越多個IP位址的負載均衡。最後,我們使用應用去管理一套相關的 Restlet、Resource、Representations。另外,應用確保了在不同Restlet實現、不同Virtual Hosts之上的可攜性和可配置性。這三種Restlet的協助為我們提供了眾多的功能:譬如訪問日誌、請求自動解碼、配置狀態頁設置等。
為了展示這些類,讓我們嘗試一個簡單的示例。首先,我們創建一個component,然後在其上添加一個HTTP服務端connector,並偵聽 8182埠。接著創建一個簡單的、具有追蹤功能的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 component = new Component();
//
Adds a new server connector in the map supporting the given protocol on the
specified port.
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()
+ '
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()
+ '
'
+ "Root URI : " +
request.getRootRef()
+ '
+ '
'
+ "Routed part : "
+ request.getResourceRef().getBaseRef() + '
+ request.getResourceRef().getBaseRef() + '
'
+ "Remaining part: "
+ request.getResourceRef().getRemainingPart();
response.setEntity(message, MediaType.TEXT_PLAIN);
}
};
// Then attach it to the local host
+ "Remaining part: "
+ request.getResourceRef().getRemainingPart();
response.setEntity(message, MediaType.TEXT_PLAIN);
}
};
// Then attach it to the local host
//
Attaches a target Restlet to this router with an empty URI pattern.
component.getDefaultHost().attach("/trace", restlet);
// Now, let's start the component!
// Note that the HTTP server connector is also automatically started.
component.start();
component.getDefaultHost().attach("/trace", restlet);
// Now, let's start the component!
// Note that the HTTP server connector is also automatically started.
component.start();
URI重寫和重定向
Restlet框架的另一個優點是對cool URI的內建支持。Jacob Nielsen在他的AlertBox中給出了對URI設計的重要性的絕佳描述。
首先介紹的工具是Redirector,它能夠將cool URI重寫為另一個URI,並接著進行相應的自動重定向。這裡支持一些重定向類型:通過用戶端/流覽器的外部重定向、類似代理行為的connector重定向。在下面的例子中,我們將基於Google為名為"mysite.org"的網站定義一個檢索服務。與URI相關的"/search"就是檢索服務,它通過"kwd"參數接收一些檢索關鍵字:
Restlet框架的另一個優點是對cool URI的內建支持。Jacob Nielsen在他的AlertBox中給出了對URI設計的重要性的絕佳描述。
首先介紹的工具是Redirector,它能夠將cool URI重寫為另一個URI,並接著進行相應的自動重定向。這裡支持一些重定向類型:通過用戶端/流覽器的外部重定向、類似代理行為的connector重定向。在下面的例子中,我們將基於Google為名為"mysite.org"的網站定義一個檢索服務。與URI相關的"/search"就是檢索服務,它通過"kwd"參數接收一些檢索關鍵字:
// 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);
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只需要三個參數。第一個參數是父級上下文,第二個參數定義了如何基於URI範本重寫URI。這裡的URI範本將被Template類處理。第三個參數定義了重定向類型:出於簡化的目的,我們選擇了用戶端重定向。
同時,當調用被傳遞給application時,我們使用了Route類從request中提取查詢參數“kwd”。如果發現參數,參數將被複製到request的“keywords”屬性中,以便Redirector在格式化目標URI時使用。
路由器和分層URI
同時,當調用被傳遞給application時,我們使用了Route類從request中提取查詢參數“kwd”。如果發現參數,參數將被複製到request的“keywords”屬性中,以便Redirector在格式化目標URI時使用。
路由器和分層URI
作為Redirector的補充,我們還具有另一個管理cool URI的工具:Router(路由器)。它們是一種特殊的Restlet,能夠使其它Restlet(例如Finder和Filter)依附於它們,並基於URI範本進行自動委派調用(delegate call)。通常,你可以將Router設置為Application的根。
這裡,我們將解釋一下如何處理下面的URI範本:
1. /docs/ 用於顯示靜態檔
2. /users/{user} 用於顯示使用者帳號
3. /users/{user}/orders 用於顯示特定使用者的所有訂單
4. /users/{user}/orders/{order} 用於顯示特定的訂單
實際上,這些URI包含了可變的部分(在大括弧中)並且沒有檔副檔名,這在傳統的web容器中很難處理。而現在,你只需要做的只是使用URI範本將目標Restlet附著到Router上。在Restlet框架運行時,與request的URI最為匹配的Route將接收調用,並調用它所附著的 Restlet。同時,request的屬性工作表也將自動更新為URI範本變數。
這裡,我們將解釋一下如何處理下面的URI範本:
1. /docs/ 用於顯示靜態檔
2. /users/{user} 用於顯示使用者帳號
3. /users/{user}/orders 用於顯示特定使用者的所有訂單
4. /users/{user}/orders/{order} 用於顯示特定的訂單
實際上,這些URI包含了可變的部分(在大括弧中)並且沒有檔副檔名,這在傳統的web容器中很難處理。而現在,你只需要做的只是使用URI範本將目標Restlet附著到Router上。在Restlet框架運行時,與request的URI最為匹配的Route將接收調用,並調用它所附著的 Restlet。同時,request的屬性工作表也將自動更新為URI範本變數。
請看下面的具體實現代碼。在真實的應用中,你可能希望創建單獨的子類來代替我們這裡使用的匿名類:
// 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();
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中提取的,因此這是沒有精確解碼的。為了實現這樣的工作,請查看手冊中的decode(String)方法。
實作
Resin配置方法
:
1.
首先下載Restlet1.1.9版本
2.
將裡面lib包中的 org.restlet.jar , com.noelios.restlet.jar
, com.noelios.restlet.ext.servlet_2.5.jar放入專案目錄的/lib以及/resin ext-lib資料夾。
3.
然後修改resin/wapps/專案/WEB-INF/web.xml檔案,新增以下
<context-param>
<param-name>org.restlet.component</param-name>
<param-value>com.firstResource.RestComponent</param-value>
</context-param>
<servlet>
<servlet-name>RestletServlet</servlet-name>
<servlet-class>com.noelios.restlet.ext.servlet.ServerServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RestletServlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
|
4.
其中RestComponent就是所有Application 管理者,用來分配uri給相對的Appication。
5.
建立RestComponent類
public class RestComponent extends Component {
public
RestComponent() {
getServers().add(Protocol.HTTP, 8080);
getDefaultHost().attach("/member",
new
MemberApp(getContext()));
getDefaultHost().attach("/circle",
new
CircleApp(getContext()));
getDefaultHost().attach("/calculate",
new
CalculateApp(getContext()));
}
}
|
MemberApp(Application類)
@Override
public synchronized Restlet createRoot() {
// Create a router Restlet that defines routes.
Router
router = new Router(getContext());
router.attachDefault(MemberProfileResource.class);
router.attach("/{value}/photo", MemberPhotoResource.class);
router.attach("/{value}/profile", MemberProfileResource.class);
router.attach("/{value}/company", MemberCompanyResource.class);
return router;
}
|
Application可以在將近來的url再分派給底下的Resource類別實作。
/{value}/profile,{}表示這個url的可變數,後面以/profile結尾
MemberProfileResource(Resource類)
public class MemberProfileResource
extends Resource {
String value;
public
MemberProfileResource(Context context, Request request,
Response
response) {
super(context, request,
response);
this.value = (String)
getRequest().getAttributes().get("value");
// This representation has only one type of
representation.
getVariants().add(new Variant(MediaType.TEXT_PLAIN));
}
/**
* Returns a full representation for a given
variant.
*/
@Override
public Representation
getRepresentation(Variant variant) {
byte[] ba_mrscid =
BytesUtil.hexToBytes(value);
Representation
representation = new StringRepresentation(
"test",
MediaType.TEXT_PLAIN);
return
representation;
}
}
|
可以使用getRequest().getAttributes().get("value");
取得url命名之變數,這裡是得到value的值,然後將其存入類別裡的成員變數中,最後Resource會在getRepresentation中實作回應的內容,這邊我們就使用value作為faceboss的會員hex_mrscid,透過MemberInfoKeeper取得會員資料。
2011.09.26 補充 :
如果中文顯示亂碼請在representation中加入下面程式碼
representation.setCharacterSet(CharacterSet.UTF_8);
|
取得url參數(parameter)方法
for (Parameter parameter
: form) {
if(parameter.getName().equals("value")){
}
}
|
本文參考於cleverpig作為貢獻者完成了本文的翻譯和整理工作,在這裡為其說明在resin上坐實作並推廣,有興趣的人可以到restlet官方上參考英文文件。
沒有留言:
張貼留言