12 September 2014

导语

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 希望主办方能够:

  1. 查看现有比赛细节
  2. 创建新的比赛
  3. 更新现有比赛
  4. 删除比赛

一个支持这些请求的 RESTful URI 应为 http://racing.acme.com/race。注意,在这种情况下,比赛是客户机要使用的资源。 用 HTTP GET 来调用 URI 会返回一个比赛列表(这时先不要考虑响应的格式)。 要添加新比赛,要用包含适当信息(例如,一个包含诸如名称、日期和距离等信息的 XML 文档)的 HTTP POST 来调用同一 URI。 要更新和删除现有比赛,则需要对特定比赛的实例进行操作。因此,可以给单个比赛赋予一个 URI:http://racing.acme.com/race/race_id。在这种情况下,race_id 表示任一比赛标识符的一个占位符(诸如 1 或者 600 米)。因此,查看一个现有比赛实例就是针对该 URI 执行一个 HTTP GET 请求;更新或者删除一个比赛分别为一个 PUT 或者 DELETE 请求。

Acme Racing 可能还希望公开有关某次比赛的参赛人员的数据。他们希望他们的服务支持:

  1. 获得有关特定比赛的全部参赛人员的数据。该数据还要包含已结束的比赛的赛跑时间和排名。
  2. 为特定比赛创建一个或多个参赛人员。
  3. 更新特定比赛的某一参赛人员的信息(如年龄)。
  4. 删除特定比赛的某一参赛人员。

和比赛一样,将 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 应用程序有一个相似点,就是它们都处在容器中,但实际上它们在两个方面是截然不同的。

  1. Restlet 不使用 HTTP 的直接概念或其状态显示,如 cookies 或者 session。
  2. Restlet 框架极其轻便。只需更新web.xml文件,并部署一个WAR就可以了

基类

基本上,一个用 Restlet 框架构建的 RESTful 应用程序的大部分都需要使用两个基类: ApplicationResource

  • 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()

获取 元素的 name 属性的值:

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-