标题：restlet
基本概念
Restlet在术语上参照了Roy Fielding博士论文在讲解REST时采用的术语，如：资源（resource）、表示（representation）、连接器（connector）、组件（component）、媒体类型（media type）、语言（language），等等。这些术语你应该不会陌生。Restlet增加了一些专门的类（如Application、Filter、Finder、Router和Route），用以简化restlets的彼此结合，以及简化把收到的请求（incoming requests）映射为处理它们的资源。
  图12-1：Restlet的类层次结构
抽象类Uniform及其具体子类Restlet，是Restlet的核心概念。正如其名称所暗示的，Uniform暴露一个符合REST规定的统一接口（uniform interface）。虽然该接口是按HTTP统一接口定义的，但它也可用于其他协议（如FTP和SMTP）。
handle是一个重要的方法，它接受两个参数：Request和Response。正如你可以从图12-1中看到的，每个暴露于网上的调用处理者（call handler）（无论作为客户端还是服务端）都是Restlet的一个子类——也就是说，它是一个restlets——并遵守这个统一接口。由于有统一接口，restlets可以非常复杂的方式组合在一起。
Restlet支持的每一个协议都是通过handle方法暴露的。这就是说，HTTP（服务器和客户端）、HTTPS、SMTP，以及JDBC、文件系统，甚至类加载器（class loaders）都是通过调用handle方法来操作的。这减少了开发者需掌握的APIs的数量。
过滤、安全、数据转换及路由是“通过把Restlet的子类链起来”进行处理的。Filters可以在处理下个restlet调用之前或之后进行处理。Filters实例的工作方式与Rails过滤器差不多，只不过Filters实例跟其他Restlet类一样响应handle方法，而不是具有一个专门的API。
一个Router restlet有许多附属的Restlet对象，它把每个收到的协议调用（incoming protocol call）路由给适当的Restlet处理器。路由（routing）通常是根据目标URI进行的。跟Rails不同的是，Restlet没有对资源层次结构（resource hierarchy）作URI规则限定，所以可以随意设置想要的URI，只要对Routers作相应编程就行了。
除了这一常见用途，Router还可用于其他用途。你可以用一个Router在多台远程机器之间以动态负载均衡的方式来转发调用。即使在这种复杂的情况下，它也一样响应Restlet的统一接口，并且可成为一个更大路由系统中的一个组件。VirtualHost类（Router的一个子类）使我们可以在同一台物理主机上运行多个具有不同域名的应用。在过去，你要引入一个前端Web服务器（如Apache的httpd）才能实现此功能；而用Restlet的话，它只是另一个响应统一接口的Router实现。
一个Application对象能够管理一组restlets，并提供常见的服务，比方说对压缩的请求进行透明解码，或者利用method查询参数在重载的POST（overloaded POST）之上实现PUT和DELETE请求。最后，Component对象可以包含并编配（orchestrate）一组Connectors、VirtualHosts及Applications（作为独立Java应用运行的，或者嵌入在一个更大系统（如J2EE环境）中的）。
在第6章，我向你介绍了“把一个问题划分为一组响应HTTP统一接口的资源”的步骤。在第7章，为了处理Ruby on Rails的简单化假设（simplifying assumptions），我对该步骤作了相应的调整。因为Restlet没有做简单化假设（simplifying assumptions），所以我们无须对此步骤进行修改。它可以实现任何REST式系统。如果你刚好想实现一个REST式面向资源的Web服务，可以按愿意的方式来组织和实现这些资源。Restlet确实提供了一些便于创建面向资源的应用的类。其中特别值得一提的是Resource类，它可作为你所有应用资源的基础。
我在本书中一直用URI模板作为一组URIs的简化表达（见第9章）。Restlet用URI模板来进行URI与资源的映射。假如用Restlet来实现第7章那个社会性书签服务的话，它也许要指定一个代表特定书签的URI：
/users/{username}/bookmarks/{URI}你可以在把Resource子类附加到Router上时使用这种语法。假如你不相信真会这么好的话，可以等到下一节，那时我会实际实现部分书签服务。
编写Restlet客户端
在示例2-1中，你看到的是一个从Yahoo!搜索服务获取XML搜索结果的Ruby客户端。示例12-3是一个用Java参照Restlet 1.0实现的具有同样功能的客户端。要确保把以下JAR包写在你的classpath中，才能成功编译并运行接下来的例子：
org.restlet.jar（Restlet API） com.noelios.restlet.jar （Noelios Restlet Engine核心） com.noelios.restlet.ext.net.jar （基于JDK的HttpURLConnection的HTTP客户端连接器） 这些JAR包可以在Restlet发布包中的lib目录里找到。要确保你的Java环境支持Java SE 5.0（或更高）版本。如果你确实需要的话，可以用Retrotranslator（http://retrotranslator. sourceforge.net/）轻易地把Restlet代码反移植（backport）到J2SE 4.0版上去。
示例12-3：Yahoo!搜索服务的一个Restlet客户端
// YahooSearch.javaimport org.restlet.Client;import org.restlet.data.Protocol;import org.restlet.data.Reference;import org.restlet.data.Response;import org.restlet.resource.DomRepresentation;import org.w3c.dom.Node;/**  * 用返回XML的Yahoo！搜索服务来搜索Web  */public class YahooSearch {    static final String BASE_URI =     "http://api.search.yahoo.com/WebSearchService/V1/webSearch";    public static void main(String[] args) throws Exception {        if (args.length != 1) {            System.err.println("You need to pass a term to search");        } else {            // 获取一个资源，即一个包含搜索结果的XML文档            String term = Reference.encode(args[0]);            String uri = BASE_URI + "?appid=restbook&query=" + term;            Response response = new Client(Protocol.HTTP).get(uri);            DomRepresentation document = response.getEntityAsDom();            // 用XPath找出数据结构中重要部分            String expr = "/ResultSet/Result/Title";            for (Node node : document.getNodes(expr)) {                  System.out.println(node.getTextContent());            }        }    }} 跟示例2-1一样，你可以在执行这个类时把一个搜索关键字作为命令行参数传给它。比如像下面这样：
$ java YahooSearch xslt     XSL Transformations (XSLT)      The Extensible Stylesheet Language Family (XSL)     XSLT Tutorial          ...该示例证明了“用Restlet从Web服务获取XML数据，并用标准工具处理它”是极其简单的事。Yahoo!资源的URI是用一个常量和用户提供的搜索关键字构造而成的。客户端连接器（client connector）是用HTTP协议来初始化的。XML文档是通过get方法获得的，该方法对应于HTTP统一接口的GET方法。当调用返回时，程序将得到一个DOM表示。跟前面的Ruby例子一样，XPath是对XML进行查询的最简单方式。
跟前面的Ruby例子一样，这个程序也忽略了XML文档里的XML名称空间（namespaces）。Yahoo!为整个文档采用名称空间urn:yahoo:srch，但我是直接引用标签的，比方说，我用ResultSet，而不是urn:yahoo:srch:ResultSet。前面的Ruby例子忽略名称空间，是因为Ruby的默认XML解析器不支持名称空间。Java的XML解析器支持名称空间，而且Restlet API令正确处理名称空间变得更加容易。虽然对上面那个简单例子来说，它们区别不大，但支持名称空间可以避免一些因名称空间而导致的微妙的问题。
当然，若一直用urn:yahoo:srch:ResultSet，是比较烦人的。Restlet API可以容易地把一个简短前缀跟一个名称空间进行关联，然后就可以在XPath表达式中使用这个简短前缀而不是整个名称空间了。示例12-4对示例12-3后半部分代码作了改动，它使用了带名称空间的Xpath，这样就不会把来自Yahoo!的ResultSet标签与来自其他名称空间的标签搞混了。
示例12-4：支持名称空间的文档处理代码
DomRepresentation document = response.getEntityAsDom();            // 把该名称空间与前缀‘y’关联起来            document.setNamespaceAware(true);            document.putNamespace("y", "urn:yahoo:srch");            // 用XPath找出数据结构中重要部分            String expr = "/y:ResultSet/y:Result/y:Title/text()";            for (Node node : document.getNodes(expr)) {                  System.out.println(node.getTextContent());            }示例2-15是Yahoo!搜索服务的另一个Ruby客户端。它请求的是JSON格式（而不是XML格式）的搜索数据。示例12-5是一个与之功能等价的Restlet客户端。它通过Restlet里的另两个JAR文件获取JSON支持：
org.restlet.ext.json_2.0.jar（用于JSON的Restlet扩展） org.json_2.0/org.json.jar（JSON官方程序库） 示例12-5：Yahoo!的JSON搜索服务的一个Restlet客户端
// YahooSearchJSON.javaimport org.json.JSONArray;import org.json.JSONObject;import org.restlet.Client;import org.restlet.data.Protocol;import org.restlet.data.Reference;import org.restlet.data.Response;import org.restlet.ext.json.JsonRepresentation;/**  * 用返回JSON的Yahoo！搜索服务来搜索Web  */public class YahooSearchJSON {    static final String BASE_URI =    "http://api.search.yahoo.com/WebSearchService/V1/webSearch";    public static void main(String[] args) throws Exception {        if (args.length != 1) {            System.err.println("You need to pass a term to search");        } else {            // 获取一个资源，即一个包含搜索结果的JSON文档            String term = Reference.encode(args[0]);            String uri = BASE_URI + "?appid=restbook&output=json&query=" + term;            Response response = new Client(Protocol.HTTP).get(uri);            JSONObject json = new JsonRepresentation(response.getEntity())                     .toJsonObject();            // 在JSON文档中寻找并显示标题            JSONObject resultSet = json.getJSONObject("ResultSet");            JSONArray results = resultSet.getJSONArray("Result");            for (int i = 0; i < results.length(); i++) {                 System.out.println(results.getJSONObject(i).getString("Title"));            }        }    }}当你为Yahoo！的Web服务编写客户端时，可以选择表示格式（representation format）。Restlet核心API支持XML，另外还可以通过扩展支持JSON。正如你所预料的那样，这两个例子的区别仅仅在于对响应的处理上。JsonRepresentation类可以把响应实体主体（response entity-body）转换成一个JSONObject实例（而Ruby的JSON库是把JSON数据结构转换成一个本地数据结构）。该数据结构只能进行人工遍历，因为目前JSON中还没有类似XPath的查询语言。
编写Restlet服务
接下来的例子会稍微复杂一些。我将向你展示如何设计并实现一个服务端应用。在第7章，我用Ruby on Rails实现了一个书签管理应用，现在我用Restlet来重新实现其部分功能。为了简单起见，该应用只支持对用户及其书签进行安全的（safe）操作。
Java包结构是这样的：
org restlet example     book   rest    ch7       -Application       -ApplicationTest       -Bookmark       -BookmarkResource       -BookmarksResource       -User       -UserResource也就是说，Bookmark等类都在org.restlet.example.book.rest.ch7包里。
我不打算在此展示完整的代码。如果需要，你可以去本书的官方网站（http://www.oreilly. com/catalog/9780596529260），那里提供了本书的所有示例程序代码。你也可以在restlet.org（http://www.restlet.org）上找到本例的完整代码。如果你已经下载了Restlet的话，那么也可以在src/org/restlet.example/org/restlet/example/book/rest目录里找到本节的示例代码。
我将从一些简单的代码开始。示例12-6是Application.main方法，它用来建立Web服务器，并开始处理请求。
示例12-6：Application.main方法：建立Web服务器
public static void main(String... args) throws Exception {    // 用HTTP服务器连接器创建一个组件    Component comp = new Component();    comp.getServers().add(Protocol.HTTP, 3000);    // 把应用附加到默认主机上，并启动    comp.getDefaultHost().attach("/v1", new Application());    comp.start();}
资源与URI设计
由于Restlet未对资源设计作特别的限制，所以你完全可以根据ROA的设计原则来进行资源类（resource classes）及URIs的设计。在第7章，我要围绕“Rails的基于控制器的架构”来进行设计；而这里，我不需要围绕Restlet架构来进行设计。图12-2 展示了URI是如何经由Router映射到资源，再映射到下层restlet类的。
  图12-2：社会性书签应用的Restlet架构
为了理解如何用Java代码实现这些映射，我们来看一下Application类及它的createRoot 方法（见示例12-7）。它跟示例7-3所示的Rails routes.rb文件在功能上是等价的。
示例12-7：Application.createRoot方法：实现URI模板到restlet的映射
public Restlet createRoot() {    Router router = new Router(getContext());    // 为用户资源增加路由    router.attach("/users/{username}", UserResource.class);    // 为用户的书签资源增加路由    router.attach("/users/{username}/bookmarks", BookmarksResource.class);    // 为书签资源增加路由    Route uriRoute = router.attach("/users/{username}/bookmarks/{URI}",                                       BookmarkResource.class);    uriRoute.getTemplate().getVariables()      .put("URI", new Variable(Variable.TYPE_URI_ALL));}在我创建一个Application对象（比如像示例12-6中的那样）时，这段代码便会运行。它会在资源类UserResource与URI模板“/users/(username)”之间建立起清晰而直观的映射关系。Router先拿请求的目标URI跟URI模板（URI templates）进行比较，然后把请求转发给一个新建的相应的资源类实例。模板变量的值被存放在请求的属性地图（attributes map）里（跟Rails例子中的params地图类似），以便于在Resource代码中使用。这既有效，又易于理解；当你事隔很久再回顾代码时，这很有帮助。
请求处理和表示
假定一个客户端向URI http://localhost:3000/v1/users/jerome发出GET请求。我有一个监听本地主机3000端口的Component对象，和一个隶属于 /v1 的Application对象。该Application有一个Router和一组Route对象，这些Route对象正等待着跟各个URI模板匹配的请求。 URI路径片段“/users/jerome”跟模板“/users/{username}”相匹配，而该模板的Route是与UserResource类（大致等价于Rails UsersController类）相关联的。
Restlet通过初始化一个新的UserResource对象，并调用它的handleGet方法来处理该请求。示例12-8是UserResource类的构造方法。
示例12-8：UserResource类的构造方法
/** * 构造方法 * * @param context *           上级上下文 * @param request *           要处理的请求 * @param response *           要返回的响应 */ public UserResource(Context context, Request request, Response response) {    super(context, request, response);    this.userName = (String) request.getAttributes().get("username");    ChallengeResponse cr = request.getChallengeResponse();    this.login = (cr != null) ? cr.getIdentifier() : null;    this.password = (cr != null) ? cr.getSecret() : null;    this.user = findUser();    if (user != null) {        getVariants().add(new Variant(MediaType.TEXT_PLAIN));    }}至此，这个架构已经建立了一个Request对象，它包含了我所需要的关于请求的所有信息。username属性来自URI，认证证书来自请求的Authorization报头。我还调用findUser方法来根据认证证书在数据库中查找用户（为节省篇幅，我就不在此展示findUser方法的代码了）。这些工作在第7章都是由Rails过滤器完成的。
在框架把一个UserResource实例化后，它会对资源对象调用适当的handle方法。HTTP统一接口中的每一个方法，都有一个对应handle方法。 在这个例子中，Restlet架构最后的任务是调用UserResource.handleGet。
由于我没有定义UserResource.handleGet这个方法，所以它将具有继承Resource. handleGet方法的行为。HandleGet的默认行为是找到最符合客户端要求的资源的表示。客户端通过内容协商（content-negotiation）来表达它的要求。Restlet通过Accept报头的值来决定返回哪个表示。由于这里只有一个表示格式，所以客户端的要求不起作用。这是由getVariants和getRepresentation方法处理的。由于在上述构造方法中把text/ plain定义为唯一支持的表示格式，所以我的getRepresentation方法的实现是很简单的（见示例12-9）。
示例12-9：UserResoure.getRepresentation：构造一个用户的表示
@Overridepublic Representation getRepresentation(Variant variant) {    Representation result = null;    if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) {        // 创建一个文本表示        StingBuilder sb=new StringBuilder();        sb.append("------------\n");        sb.append("User details\n");        sb.append("------------\n\n");        sb.append("Name:  ").append(this.user.getFullName()).append('\n');        sb.append("Email: ").append(this.user.getEmail()).append('\n');        result = new StringRepresentation(sb);    }    return result;}虽然这只是一个资源的一个方法，但其他资源，以及UserResource的其他HTTP方法的工作原理都差不多，比如：对用户的PUT请求将被路由给UserResource.handlePut，等等。正如我前面所提到的，这里的代码只是社会性书签应用所有代码的一部分；所以，如果你有兴趣进一步学习的话，可以去下载一个完整的示例代码来阅读。
现在，你应该了解Restlet架构是如何把收到的（incoming）HTTP请求路由给特定的Resource类，然后再路由给该类的特定方法了。你也应该知道如何由资源状态来构造表示（representations）了。一般，只要关注Application和Router代码一次就行，因为一个Router可用于你的所有资源。
编译、运行与测试
Application类实现了运行社会性书签服务的HTTP服务器。你需要在classpath中加入以下JAR文件：
org.restlet.jar com.noelios.restlet.jar com.noelios.restlet.ext.net.jar org.simpleframework_3.1/org.simpleframework.jar com.noelios.restlet.ext.simple_3.1.jar com.db4o_6.1/com.db4o.jar 这些JAR包可以在Restlet发布包中的lib目录里找到。有两点需要注意：第一，Web服务器的实际工作是由一个非常紧凑的、基于Simple框架的HTTP服务器连接器来处理的；第二，我们是用强大的db4o对象数据库（而不是关系数据库）来存储领域对象（用户和书签）的。在编译好所有示例文件后，运行org.restlet.example.book.rest.ch7. Application，它将作为服务器的端点（endpoint）。
ApplicationTest类为服务提供了一个客户端接口。它采用上节描述的Restlet客户端类来添加和删除用户和书签。它是通过HTTP统一接口进行工作的：用PUT请求创建用户和书签，用DELETE请求删除用户和书签。
在命令行下运行ApplicationTest类，你将得到以下消息：
Usage  depends  on  the  number  of  arguments:-  Deletes  a  user                     :  userName,  password-  Deletes  a  bookmark         :  userName,  password,  URI-  Adds  a  new  user               :  userName,  password,  "full  name",  email-  Adds  a  new  bookmark   :  userName,  password,  URI,  shortDescription, longDescription,  restrict[true  /  false]你可以用这个程序来添加一些用户，并增加一些书签。然后，你就可以在Web浏览器中通过访问适当的URI（如http://localhost:3000/v1/users/jerome等）来浏览用户书签的HTML表示了。