Restlet是一个Java下的轻量级REST框架。通过拥抱REST(REST是一种Web架构风格)它模糊了Web站点和Web服务之间的界限,从而帮助开发人员构建Web应用。每一个主要的REST概念(REST concept)都有一个对应的Java类。你的REST化的Web设计和你的代码之间的映射是非常简单直接的。
为什么有必要创建另一种框架?难道Servlet API还不够好用吗?
Servlet AIP在1998年发布,从那个时候起它的核心设计一直没有很大的变化。它是Java EE的众多API中最成功的一个,但是它的几个设计缺陷和一些限制损害了它。举个例子,URI模式和它的处理者(handler)之间的映射是受限制的,而且其配置都集中在一个配置文件中。还有,它把socket流的控制直接交给了应用系统开发人员,Servlet容器阻碍了我们充分使用NIO特性对IO操作进行优化。另一个主要问题就是Servlet API鼓励应用开发者在应用或者用户会话级别直接将session状态保存于内存中,尽管这看上去不错,但它造成了Servlet容器扩展性和高可用性的主要问题。为了克服这些问题,就必须实现复杂的负载均衡、session复制、持久化机制。这导致了可扩展性必然成为灾难。
如何看待别的框架中对REST的支持(例如Axis2,或者CXF/XFire)?
这些支持非常有效,但是作用非常有限。我的主要观点是设计这些项目是为了符合WS-*/SOAP Stack,它们与REST世界并不非常契合。在REST世界里,定义了一个全新的范例:面向资源的设计,而非通过远程方法调用这样的范例。例如Axis2仅仅支持GET和POST两种HTTP方法,它需要远程方法的传递需要一个URI参数。这在REST中式不允许的,这种做法也不能被称之为REST化。XFire1.2不支持REST,但是它发布了一个项目用于将POJO映射到REST化的Web服务。这有点类似最近发布的JSR-311,此JSR试图基于一套annotation和助手类标准化这种映射。
REST与HTTP协议
REST软件架构是由Roy Thomas Fielding博士在2000年首次提出的。他为我们描绘了开发基于互联网的网络软件的蓝图。REST软件架构是一个抽象的概念,是一种为了实现这一互联网的超媒体分布式系统的行动指南。利用任何的技术都可以实现这种理念。而实现这一软件架构最著名的就是HTTP协议。通常我们把REST也写作为REST/HTTP,在实际中往往把REST理解为基于HTTP的REST软件架构,或者更进一步把REST和HTTP看作为等同的概念。今天,HTTP是互联网上应用最广泛的计算机协议。HTTP不是一个简单的运载数据的协议,而是一个具有丰富内涵的网络软件的协议。它不仅仅能够对于互联网资源进行唯一定位,而且还能告诉我们对于该资源进行怎样运作。这也是REST软件架构当中最重要的两个理念。而REST软件架构理念是真正理解HTTP协议而形成的。有了REST软件架构理念出现,才使得软件业避免了对HTTP协议的片面理解。只有正确的理论指导,才能避免在软件开发的实际工作过程中少走弯路。
REST与URI(资源定位)
REST软件架构之所以是一个超媒体系统,是因为它可以把网络上所有资源进行唯一的定位,不管你的文件是图片、文件Word还是视频文件,也不管你的文件是txt文件格式、xml文件格式还是其它文本文件格式。它利用支持HTTP的TCP/IP协议来确定互联网上的资源。
REST与CRUD原则
REST软件架构遵循了CRUD原则,该原则告诉我们对于资源(包括网络资源)只需要四种行为:创建(Create)、获取(Read)、更新(Update)和销毁(DELETE)就可以完成对其操作和处理了。其实世界万物都是遵循这一规律:生、变、见、灭。所以计算机世界也不例外。这个原则是源自于我们对于数据库表的数据操作:insert(生)、select(见)、update(变)和delete(灭),所以有时候CRUD也写作为RUDI,其中的I就是insert。这四个操作是一种原子操作,即一种无法再分的操作,通过它们可以构造复杂的操作过程,正如数学上四则运算是数字的最基本的运算一样。
REST与网络服务
尽管在Java语言世界中网络服务目前是以SOAP技术为主,但是REST将是是网络服务的另一选择,并且是真正意义上的网络服务。基于REST思想的网络服务不久的将来也会成为是网络服务的主流技术。REST不仅仅把HTTP作为自己的数据运输协议,而且也作为直接进行数据处理的工具。而当前的网络服务技术都需要使用其它手段来完成数据处理工作,它们完全独立于HTTP协议来进行的,这样增加了大量的复杂软件架构设计工作。REST的思想充分利用了现有的HTTP技术的网络能力。在德国电视台上曾经出现过一个这样的五十万欧元智力题:如何实现网络服务才能充分利用现有的HTTP协议?该问题给出了四个答案:去问微软;WSDL2.0/SOAP1.2;WS-Transfer;根本没有。这个问题告诉我们HTTP并不是一个简单的数据传来传去的协议,而是一个聪明的会表现自己的协议,这也许是REST = Representational State Transfer的真正含义。实际上目前很多大公司已经采用了REST技术作为网络服务,如Google、Amazon等。在Java语言中重要的两个以SOAP技术开始的网络服务框架XFire和Axis也把REST作为自己的另一种选择。它们的新的项目分别是Apache CXF和Axis2。Java语言也制定关于REST网络服务规范:JAX-RS: Java API for RESTful Web Services (JSR 311)。相信还会出现更多与REST相关的激动人心的信息。
REST与AJAX技术
尽管AJAX技术的出现才不到两年时间,但是AJAX技术遵循了REST的一些重要原则。AJAX技术充分利用了HTTP来获取网络资源并且实现了HTTP没有的对于异步数据进行传输的功能。AJAX技术还使得软件更好地实现分布性功能,在一个企业内只要一个人下载了AJAX引擎,其它企业内部的人员,就可以共享该资源了。AJAX技术遵守REST准则的应用程序中简单和可伸缩的架构,凡是采用AJAX技术的页面简洁而又丰富,一个页面表现了丰富多彩的形态。AJAX技术还使用了一种不同于XML格式的JSON文件格式,这个意义在哪里呢?在REST软件架构下我们不能对于XML文件进行序列化处理,这样程序员必须要使用自己的XML绑定框架。而以序列化的JavaScript对象为基础的JSON已经获得了广泛认可,它被认为能以远比XML更好的方式来序列化和传输简单数据结构,而且它更简洁。这对REST是一个极大贡献和补充。当前的网络应用软件还违背了REST的“无状态服务器”约束。REST服务器只知道自己的状态。REST不关心客户端的状态,客户端的状态自己来管理,这是AJAX技术的应用之地。通过AJAX技术,可以发挥有状态网络客户机的优势。而REST的服务器关心的是从所有网络客户端发送到服务器操作的顺序。这样使得互联网这样一个巨大的网络得到有序的管理。
REST与Rails框架
Ruby on Rails框架(简称Rails或者Rails框架)是一个基于Ruby语言的越来越流行的网络应用软件开发框架。它提供了关于REST最好的支持,也是当今应用REST最成功的一个软件开发框架。Rails框架(从版本1.2.x起)成为了第一个引入REST作为核心思想的主流网络软件开发框架。在Rails框架的充分利用了REST软件架构之后,人们更加坚信REST的重要性和必要性。Rails利用REST软件架构思想对网络服务也提供了一流的支持。从最直观的角度看待REST,它是网络服务最理想的手段,但是Rails框架把REST带到了网络应用软件开发框架。这是一次飞跃,让REST的思想从网络服务的应用提升到了网络应用软件开发。利用REST思想的simply_restful插件已经成为了Rails框架的核心内容。
REST安全性
我们把现有基于SOAP的网络服务和基于REST/HTTP网络服务作个比喻,前者是一种传统的寄信方式,而后者是现代网络的电子邮件方式。要是是寄信和电子邮件都有病毒存在的话,传统的寄信被送到对方就很危险,而电子邮件是开发的,电子邮件供应商比如Google为我们检查了电子邮件是否有病毒。这里并不是说明SOAP网络服务消息包含义病毒,而是说明HTTP是无法处理SOAP信息包究竟好不好,需要额外的软件工具解决这一问题,包括防火墙也用不上和管不了。
REST/HTTP网络服务的信息包可以被防火墙理解和控制。你可以按照操作和链接进行过滤信息包,如你可以规定从外部来的只能读取(GET操作)自己服务器的资源。这样对于系统管理员而言使得软件管理更为简单。REST的安全性还可以利用传输安全协议SSL/TLS、基本和摘要式认证(Basic und Digest Authentication)。除了这些REST自身的安全性功能外,还可以利用像基于信息的Web Services Security(JSR 155)作为REST不错的补充。
Restlet第一步:
这篇文章让你在10分钟内尝试简单的Restlet框架。告诉你如何创建一个说“hello, world”的Resource。
1.我需要什么?
2.“hello, world”应用
3.在Servlet容器中运行
4.作为一个单独的Java应用运行
5.结尾
1.我需要什么?
我们假设你已经有了一个可以马上使用的开发环境,并且你已经安装了JRE1.5(或更高)。如果你还没有下载Restlet,请选择最新的Restlet Framework 1.0发行版。
2.“hello, world”程序
让我们从REST应用的核心---资源开始入手。下面的代码是这个程序涉及的唯一资源。拷贝/粘贴代码到“HelloWorldResource”类中。
Java代码
01packagefirstSteps;
02
03importorg.restlet.Context;
04importorg.restlet.data.MediaType;
05importorg.restlet.data.Request;
06importorg.restlet.data.Response;
07importorg.restlet.resource.Representation;
08importorg.restlet.resource.Resource;
09importorg.restlet.resource.StringRepresentation;
10importorg.restlet.resource.Variant;
11
12/**
13* Resource which has only one representation.
14*
15*/
16publicclassHelloWorldResourceextendsResource {
17
18publicHelloWorldResource(Context context, Request request,
19 Response response) {
20super(context, request, response);
21
22// This representation has only one type of representation.
23 getVariants().add(newVariant(MediaType.TEXT_PLAIN));
24 }
25
26/**
27* Returns a full representation for a given variant.
28*/
29 @Override
30publicRepresentation getRepresentation(Variant variant) {
31 Representation representation =newStringRepresentation(
32 "hello, world", MediaType.TEXT_PLAIN);
33returnrepresentation;
34 }
35 }
然后创建应用例子。我们创建名为“FirstStepsApplication”的类并拷贝/粘贴下面的代码:
01packagefirstSteps;
02
03importorg.restlet.Application;
04importorg.restlet.Context;
05importorg.restlet.Restlet;
06importorg.restlet.Router;
07
08publicclassFirstStepsApplicationextendsApplication {
09
10publicFirstStepsApplication(Context parentContext) {
11super(parentContext);
12 }
13
14/**
15* Creates a root Restlet that will receive all incoming calls.
16*/
17 @Override
18publicsynchronizedRestlet createRoot() {
19// Create a router Restlet that routes each call to a
20// new instance of HelloWorldResource.
21 Router router =newRouter(getContext());
22
23// Defines only one route
24 router.attachDefault(HelloWorldResource.class);
25
26returnrouter;
27 }
28 }
3.在Servlet容器中运行
你可能更熟悉Servlets,我们建议你在你喜欢的Servlet容器里运行Restlet应用。像往常一样创建一个新的Servlet应用,把下面列出的jar包放入lib目录。
org.restlet.jar
com.noelios.restlet.jar
com.noelios.restlet.ext.servlet_2.4.jar
然后按下面的配置修改“web.xml”配置文件:
01 <?xml version="1.0" encoding="UTF-8"?>
02<web-appid="WebApp_ID" version="2.4"
03 xmlns="http://java.sun.com/xml/ns/j2ee"
04 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
05 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
06http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
07<display-name>first steps servlet</display-name>
08<!-- Application class name -->
09<context-param>
10<param-name>org.restlet.application</param-name>
11<param-value>
12 firstSteps.FirstStepsApplication
13</param-value>
14</context-param>
15
16<!-- Restlet adapter -->
17<servlet>
18<servlet-name>RestletServlet</servlet-name>
19<servlet-class>
20 com.noelios.restlet.ext.servlet.ServerServlet
21</servlet-class>
22</servlet>
23
24<!-- Catch all requests -->
25<servlet-mapping>
26<servlet-name>RestletServlet</servlet-name>
27<url-pattern>/*</url-pattern>
28</servlet-mapping>
29</web-app>
最后,将程序打包成一个war文件,例如“firstStepsServlet.war”,并部署到你的Servlet容器里。启动Servlet容器后,打开你喜欢的浏览器,输入URL:“http://<你的服务器名>:<端口号>/firstStepsServlet”。服务器将很高兴地用“hello, world”欢迎你。
4.作为一个单独的Java应用运行
Restlet应用并不是只能运行在Servlet容器里,也可以使用下面几个Jar包所为一个独立应用运行:
org.restlet.jar
com.noelios.restlet.jar
com.noelios.restlet.ext.simple.jar
org.simpleframework.jar
如果你想要理解后面两个Jar包的意义,你可以参考连接器(
http://www.restlet.org/documentation/1.0/connectors)。
创建一个主类,拷贝/粘贴下面的代码。建立一个新的HTTP服务器监听端口8182并委托所有的请求给“FirstStepsApplication”。
代码:
01packagefirstSteps;
02importorg.restlet.Component;
03importorg.restlet.data.Protocol;
04
05publicclassFirstStepsMain {
06
07publicstaticvoidmain(String[] args) {
08try{
09// Create a new Component.
10 Component component =newComponent();
11// Add a new HTTP server listening on port 8182.
12 component.getServers().add(Protocol.HTTP, 8182);
13
14// Attach the sample application.
15 component.getDefaultHost().attach(
16newFirstStepsApplication(component.getContext()));
17
18// Start the component.
19 component.start();
20 }catch(Exception e) {
21// Something is wrong.
22 e.printStackTrace();
23 }
24 }
25 }
启动Main对象,打开你喜欢的浏览器,输入URL:“http://localhost:8182/”,服务器将高兴地用“hello, world”欢迎你。否则,确认Classpath正确且没有其他应用占用8182端口。
RestLet第二步:晋级篇
这里说明Resource如何处理GET,POST,PUT和DELETE方法。
1.引言
2.示例程序
3.实现Items Resource
4.实现Item Resource
5.实现Base Resource
6.运行应用
7.客户端应用
8.总结
1. 引言
在开始开发前,我们需要简单介绍一下Restlet框架的Resource概念。REST告诉我们,Resource根据URI进行辨认,同时能够有一种或多种表现(也被称作变量),用以响应方法调用。
在Restlet框架中,服务器连接器(server connectors)收到的调用最终由Resource的实例对象处理。一个Resource负责声明支持的表现方式列表(Variant对象的实例)和实现你想要支持的REST方法。
GET依赖可更改的“variants”列表和“getRepresentation(Variant)”方法。
POST依赖“allowPost”方法和“post(Representation)”方法。
DELETE依赖“allowPut”方法和“put(Representation)”方法。
DELETE依赖“allowDelete”方法和“delete()”方法。
还有,每一个到达的响应由一个专门的Resource实例处理,你不需要操心这些方法的线程安全问题。
我们假设你已经读过“第一步”,并且有了一些元件和应用的概念。
2.示例程序
一个Item列表用来管理创建,读取,更新,和删除活动,如一个典型的CRUD应用。一个Item包含名字和描述。在简短的分析后,我们定义了两个资源:
Items Resource代表所有可用Item的集合。
Item Resource代表一个单独的item。
现在,让我们定义用来标志item的Resource URIs。假设我们的应用运行在本机“localhost”并且监听8182端口:
http://localhost:8182/firstResource/items:“items”Resource URI。
http://localhost:8182/firstResource/items/{itemName}:“item”Resource URI,每个{itemName}代表一个Item的名字。
下一步,定义每个Resource允许访问的方法列表。
“items”Resource响应GET请求并以一个XML文档展示当前注册的所有Item列表。另外,Resource支持通过POST请求创建新的Item。提交的实体包含新的Item的名字和描述,这些是以格式化的Web表单方式提交的。如果Resource成功创建新Item,它返回一个“Success - resource created”状态(HTTP 201状态代码)并且告诉客户端新Resource在哪里可以找到(HTTP "Location" header)。否则,它返回一个“Client error”状态(HTTP 404状态代码)和一个简单的错误信息。
“item”Resource响应GET请求并以一个XML文档来展示该Resource的名字和描述。也可以通过PUT和DELETE请求更新和删除Resource。
在描述两个Resource对象前,首先编写应用的代码。为简化起见,注册的Item列表做为应用的一个属性简单地保存到内存里,并不保存到一个真实的数据库。不管怎样,我们假设你想邀请你的朋友们同时测试这个应用。因为我们只有一个“FirstResourceApplication”实例在运行,所以不得不考虑线程安全。这也就是为什么你会发现Map对象Items是不不可更改的,它是一个ConcurrentHashMap对象的实例。
代码:
01packagefirstResource;
02
03importjava.util.Map;
04importjava.util.concurrent.ConcurrentHashMap;
05
06importorg.restlet.Application;
07importorg.restlet.Context;
08importorg.restlet.Restlet;
09importorg.restlet.Router;
10
11publicclassFirstResourceApplicationextendsApplication {
12
13 /** The list of items is persisted in memory. */
14 privatefinalMap<String, Item> items;
15
16 publicFirstResourceApplication(Context parentContext) {
17 super(parentContext);
18 // We make sure that this attribute will support concurrent access.
19 items =newConcurrentHashMap<String, Item>();
20 }
21
22 /**
23 * Creates a root Restlet that will receive all incoming calls.
24 */
25 @Override
26 publicsynchronizedRestlet createRoot() {
27 // Create a router Restlet that defines routes.
28 Router router =newRouter(getContext());
29
30 // Defines a route for the resource "list of items"
31 router.attach("/items", ItemsResource.class);
32 // Defines a route for the resource "item"
33 router.attach("/items/{itemName}", ItemResource.class);
34
35 returnrouter;
36 }
37
38 /**
39 * Returns the list of registered items.
40 *
41 * @return the list of registered items.
42 */
43 publicMap<String, Item> getItems() {
44 returnitems;
45 }
46 }
2. 实现Items Resource
让我们开始编写Items Resource。如上文所述,它允许GET和POST请求。POST请求支持实现“post(Representation)”方法赋予处理消息实体的权限。此外,资源通过“allowPost”方法来确定是否开启POST支持。缺省情况下,资源是不可更改的,拒绝POST、PUT和DELETE方法并返回“Method not allowed”状态(HTTP 405状态代码)。
同样,通过实现“represent(Variant)”方法确定你可以接受GET请求并根据指定的Variant生成实体。在这个例子中,我们只生成“text/xml”这种形式。
代码:
001packagefirstResource;
002
003importjava.io.IOException;
004importjava.util.Collection;
005
006importorg.restlet.Context;
007importorg.restlet.data.Form;
008importorg.restlet.data.MediaType;
009importorg.restlet.data.Request;
010importorg.restlet.data.Response;
011importorg.restlet.data.Status;
012importorg.restlet.resource.DomRepresentation;
013importorg.restlet.resource.Representation;
014importorg.restlet.resource.StringRepresentation;
015importorg.restlet.resource.Variant;
016importorg.w3c.dom.Document;
017importorg.w3c.dom.Element;
018
019/**
020* Resource that manages a list of items.
021*
022*/
023publicclassItemsResourceextendsBaseResource {
024
025 /** List of items. */
026 Collection<Item> items;
027
028 publicItemsResource(Context context, Request request, Response response) {
029 super(context, request, response);
030
031 // Get the items directly from the "persistence layer".
032 items = getItems().values();
033
034 // Declare the kind of representations supported by this resource.
035 getVariants().add(newVariant(MediaType.TEXT_XML));
036 }
037
038 @Override
039 publicbooleanallowPost() {
040 returntrue;
041 }
042
043 /**
044 * Returns a listing of all registered items.
045 */
046 @Override
047 publicRepresentation getRepresentation(Variant variant) {
048 // Generate the right representation according to its media type.
049 if(MediaType.TEXT_XML.equals(variant.getMediaType())) {
050 try{
051 DomRepresentation representation =newDomRepresentation(
052 MediaType.TEXT_XML);
053 // Generate a DOM document representing the list of
054 // items.
055 Document d = representation.getDocument();
056 Element r = d.createElement("items");
057 d.appendChild(r);
058 for(Item item : items) {
059 Element eltItem = d.createElement("item");
060
061 Element eltName = d.createElement("name");
062 eltName.appendChild(d.createTextNode(item.getName()));
063 eltItem.appendChild(eltName);
064
065 Element eltDescription = d.createElement("description");
066 eltDescription.appendChild(d.createTextNode(item
067 .getDescription()));
068 eltItem.appendChild(eltDescription);
069
070 r.appendChild(eltItem);
071 }
072 d.normalizeDocument();
073
074 // Returns the XML representation of this document.
075 returnrepresentation;
076 }catch(IOException e) {
077 e.printStackTrace();
078 }
079 }
080
081 returnnull;
082 }
083
084 /**
085 * Handle POST requests: create a new item.
086 */
087 @Override
088 publicvoidpost(Representation entity) {
089 // Parse the given representation and retrieve pairs of
090 // "name=value" tokens.
091 Form. form. =newForm(entity);
092 String itemName = form.getFirstValue("name");
093 String itemDescription = form.getFirstValue("description");
094
095 // Check that the item is not already registered.
096 if(getItems().containsKey(itemName)) {
097 generateErrorRepresentation(
098 "Item " + itemName + " already exists.", "1", getResponse());
099 }else{
100 // Register the new item
101 getItems().put(itemName,newItem(itemName, itemDescription));
102
103 // Set the response's status and entity
104 getResponse().setStatus(Status.SUCCESS_CREATED);
105 Representation rep =newStringRepresentation("Item created",
106 MediaType.TEXT_PLAIN);
107 // Indicates where is located the new resource.
108 rep.setIdentifier(getRequest().getResourceRef().getIdentifier()
109 + "/" + itemName);
110 getResponse().setEntity(rep);
111 }
112 }
113
114 /**
115 * Generate an XML representation of an error response.
116 *
117 * @param errorMessage
118 * the error message.
119 * @param errorCode
120 * the error code.
121 */
122 privatevoidgenerateErrorRepresentation(String errorMessage,
123 String errorCode, Response response) {
124 // This is an error
转载地址:http://blog.ixpub.net/html/07/12399407-251280.html
本博客为学习交流用,凡未注明引用的均为本人作品,转载请注明出处,如有版权问题请及时通知。由于博客时间仓促,错误之处敬请谅解,有任何意见可给我留言,愿共同学习进步。