Restlet框架构建RESTful Web服务
导语
REST
是一种思维方式,而非协议或标准。它是设计基于命名资源而非消息的松耦合应用程序 — 通常指面向 Web 的应用程序 — 的一种风格。
构建 RESTful
应用程序的最困难的部分在于确定要公开哪些资源。解决了这个问题之后,再使用开源 Restlet
框架构建 RESTful Web 服务就是小菜一碟了
何为REST
REST
是设计基于命名资源 — 例如,以 Uniform Resource Locators(URL)、Uniform Resource Identifiers(URI)和 Uniform Resource Names(URN)的形式 — 而非消息的松耦合 Web 应用程序的一种风格。
REST 巧妙地借助已经验证过的成功的 Web 基础设施 — HTTP
。换句话说,REST 利用了 HTTP 协议的某些方面,例如 GET 和 POST 请求。这些请求可以很好地映射到标准业务应用程序需求,诸如创建、读取、更新和删除(CRUD)
CRUD | HTTP |
---|---|
创建 | POST |
读取 | GET |
更新 | PUT |
删除 | DELETE |
REST优点
构建 RESTful 系统并不难,且这样的系统具有高度的可伸缩性,同时与底层数据松散耦合;这样的系统还可以很好地利用缓存。
Web 上所有的东西(页面、图像等)本质上都是资源。 而 REST 正是基于命名资源而非消息的,这就限制了底层技术的曝光,从而给应用程序设计中的松耦合提供了便利条件。例如,下面的 URL 在不暗示任何底层技术的情况下,公开了资源:http://thediscoblog.com/2008/03/20/unambiguously-analyzing-metrics/。
该 URL 表示一个资源 — 一篇名为 “Unambiguously analyzing metrics” 的文章。请求该资源就会调用 HTTP GET 命令。注意该 URL 是基于名词的。基于动词的版本 http://thediscoblog.com/2008/03/20/getArticle?name=unambiguously-analyzing-metrics会违反 REST 原则,因为它以 getArticle 的形式嵌套了一条消息。
REST 的魅力在于任何东西都可以成为资源,且表示方法也可以不同。在前面的例子中,资源为一个 HTML 文件,因此,其响应可能是 HTML 格式的。但是资源也可以是一个 XML 文档、序列化的对象或者 JSON 表示。其实,这些都无关紧要。重要的是资源被命名了,并且与它通信不会影响其状态。不影响状态是很重要的,因为无状态的交互有利于可伸缩性。
REST的价值在那里
引用达芬奇的一句名言 “简洁就是终极复杂”。万维网的实现非常简单,并且无可置否地获得了成功。REST 正是利用了 Web 的简单性,并因此造就了高度可伸缩的、松散耦合的系统,而且事实证明,这样的系统很容易构建。
正如您所看到的,构建 RESTful 应用程序最难的部分在于确定要公开的资源。解决了这个问题之后,再使用开源 Restlet 框架构建 RESTful Web 服务就是小菜一碟了。
构建一个RESTful API
设想这样一个在线应用程序,它管理赛跑比赛,参赛人员要跑完不同的路程。应用程序管理赛跑以及与其相关的参赛人员。它会报告某个选手的时间(跑完全程所用的时间)和排名(参赛人员以第几名跑完全程)。赛事筹办公司 Acme Racing 要求您构建一个 RESTful Web 服务,主办方可以用它来为特定比赛安排新的赛事和选手,并且可以为某次特定比赛提供官方记录。 Acme Racing 已经有了一个遗留的胖客户机应用程序,它支持类似的请求,并利用了一个简单的数据库和一个域模型。因此,剩下的工作就只有公开这个功能了。记住 REST 的魅力就在于它与底层应用程序的隐式松散耦合。因此,您目前的工作并非是去操心数据模型或与其相关联的技术 — 而是去构造一个支持公司需求的 RESTful API。
Acme Races 希望主办方能够:
- 查看现有比赛细节
- 创建新的比赛
- 更新现有比赛
- 删除比赛
一个支持这些请求的 RESTful URI 应为 http://racing.acme.com/race。注意,在这种情况下,比赛是客户机要使用的资源。 用 HTTP
GET
来调用 URI 会返回一个比赛列表(这时先不要考虑响应的格式)。 要添加新比赛,要用包含适当信息(例如,一个包含诸如名称、日期和距离等信息的 XML 文档)的 HTTPPOST
来调用同一 URI。 要更新和删除现有比赛,则需要对特定比赛的实例进行操作。因此,可以给单个比赛赋予一个 URI:http://racing.acme.com/race/race_id。在这种情况下,race_id 表示任一比赛标识符的一个占位符(诸如 1 或者 600 米)。因此,查看一个现有比赛实例就是针对该 URI 执行一个 HTTPGET
请求;更新或者删除一个比赛分别为一个PUT
或者DELETE
请求。
Acme Racing 可能还希望公开有关某次比赛的参赛人员的数据。他们希望他们的服务支持:
- 获得有关特定比赛的全部参赛人员的数据。该数据还要包含已结束的比赛的赛跑时间和排名。
- 为特定比赛创建一个或多个参赛人员。
- 更新特定比赛的某一参赛人员的信息(如年龄)。
- 删除特定比赛的某一参赛人员。
和比赛一样,将 RESTful URI 应用于与比赛相关联的参赛人员同样是一个逻辑行为。例如,查看特定比赛的全部参赛人员可以通过对 http://racing.acme.com/race/race_id/runner 的 GET 请求来实现。 要获得一个比赛的某个参赛人员的个人信息,可以编址为 http://racing.acme.com/race/race_id/runner/runner_id。 向一个比赛添加参赛人员就是一个对 http://racing.acme.com/race/race_id/runner 的 POST 请求。更新或删除特定参赛人员则分别是对 http://racing.acme.com/race/race_id/runner/runner_id 的 PUT 和 DELETE 请求。
因此,这些 URI(每一个 URI 都支持四个标准 HTTP 请求的其中一些或者全部)就满足了 Acme Racing 的需求:
- /race
- /race/race_id
- /race/race_id/runner
- /race/race_id/runner/runner_id
一个 URI 可以映射到不止一个 HTTP 动词(例如,将一个 HTTP GET 应用到 /race 将返回数据;使用 POST 和适当的数据在服务器上创建数据)。不过,有些 HTTP 命令不能实现。例如,/race 可能不支持 DELETE 命令(Acme Racing 不会删除所有的比赛);而/race/race_id 可能支持 DELETE 命令,因为移除一个比赛的某个特定实例是一个业务需求。
通信的媒介XML
接下来构造一系列的 XML 文档来表示 RESTful 比赛 Web 服务将会支持的资源。
- GET 到 /races/1 的请求的响应:
<race name="Mclean 1/2 Marathon" date="2008-05-12" distance="13.1" id="1">
<uri>/races/1</uri>
<description/>
</race>
注意这个URI请求就会返回id=1的race的信息。
- 创建一个新比赛的POST请求:
<race name="Limerick 2008 Half" date="2008-05-12" distance="13.4">
<description>erin go braugh and have a good time!</description>
</race>
注意这里就没有id和uri了。
- GET到 /race/9/runner 的请求的响应:
<race name="Limerick 200 Half" date="2008-05-12" distance="13.4" id="9">
<uri>races/9</uri>
<description>erin go braugh and have a good time!</description>
<runners>
<runner first_name="Linda" last_name="Smith" age="25" id="21">
<uri>/races/9/runner/21</uri>
</runner>
<runner first_name="Andrew" last_name="Glover" age="22" id="20">
<uri>/races/9/runner/20</uri>
</runner>
</runners>
</race>
注意这个URI请求就会返回id=9的race的所有runner列表。
- GET到 /race/1/runner/1 的请求的响应:
<race name="Mclean 1/2 Marathon" date="2008-05-12" distance="13.1" id="1">
<uri>/races1</uri>
<description />
<runner first_name="Andrew" last_name="Glover" age="32" id="1">
<uri>/races/1/runner/1</uri>
<result time="100.04" place="45" />
</runner>
</race>
注意这个URI请求就会返回id=1的race的id=1的runner信息。
Restlet框架
Restlet
应用程序与 servlet
应用程序有一个相似点,就是它们都处在容器中,但实际上它们在两个方面是截然不同的。
- Restlet 不使用 HTTP 的直接概念或其状态显示,如 cookies 或者 session。
- Restlet 框架极其轻便。只需更新
web.xml
文件,并部署一个WAR
就可以了
基类
基本上,一个用 Restlet 框架构建的 RESTful 应用程序的大部分都需要使用两个基类: Application
和 Resource
。
- Application 实例将 URI 映射到 Resource 实例。
- Resource 实例处理基本的 CRUD 命令,当然,这些命令都要映射到 GET、POST、PUT 和 DELETE。
Application子类
通过 Router
将URI 映射到 Resource
子类。
public class RaceApplication extends Application{
public RaceApplication(Context context) {
super(context);
}
public Restlet createRoot() {
Router router = new Router(this.getContext());
router.attach("/race", RacesResource.class);
router.attach("/race/{race_id}", RaceResource.class);
router.attach("/race/{race_id}/runner", RaceRunnersResource.class);
router.attach("/race/{race_id}/runner/{runner_id}", RaceRunnerResource.class);
return router;
}
}
Resource子类
Resource
基类定义了get/post/put/delete的模板方法,子类需要覆盖实现。
具体来说,在 GET 的情况下构建XML,在 POST、PUT 或者 DELETE 的情况下操作XML。
Groovy生成和操作XML
这里将使用Groovy来生成 XML 并完成操作 XML 文档这个沉闷的工作。
比如对于这个XML文件:
<acme-races>
<race name="Alaska 200 below" date="Thu Jan 01" distance="3.2" id="20">
<uri>/races/20</uri>
<description>Enjoy the cold!</description>
</race>
</acme-races>
创建上面的XML:
def writer = new StringWriter()
def builder = new MarkupBuilder(writer)
builder."acme-races"() {
race(name: "Alaska 200 below", date: "Thu Jan 01", distance: "3.2", id: "20") {
uri("/races/20")
description("Enjoy the cold!")
}
}
println writer.toString()
获取
def root = new XmlSlurper().parseText(raceXML)
def name = root.race.@name.text()
获得描述
root.race.description.text()
Groovy代码
直接用Groovy写class即可,然后在其他Java类中可以直接import进这个class,就和普通Java类是一样的用法。 因为本质上Groovy的源代码.groovy是可以被编译为字节码再执行的,就是说本质上和Java源代码是一致的。
数据层
这层就是WEB传统的由spring和hibernate管理的数据持久层。和Restlet松耦合。
比如Race的API:
Collection<Race> findAll();
Race findById(long id);
Race findByName(String name);
void create(Race race);
void update(Race race);
void remove(Race race);
部署
需要将Restlet部署进servlet容器中:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>RESTful racing</display-name>
<context-param>
<param-name>org.restlet.application</param-name>
<param-value>RaceApplication</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>/*</url-pattern>
</servlet-mapping>
</web-app>
代码
这篇文章是在IBM看到的,原文翻译的不好,我这里又整理了的。
最后附上代码下载
-EOF-