gembin

          OSGi, Eclipse Equinox, ECF, Virgo, Gemini, Apache Felix, Karaf, Aires, Camel, Eclipse RCP

          HBase, Hadoop, ZooKeeper, Cassandra

          Flex4, AS3, Swiz framework, GraniteDS, BlazeDS etc.

          There is nothing that software can't fix. Unfortunately, there is also nothing that software can't completely fuck up. That gap is called talent.

          About Me

           

          Restlet創建面向資源的服務

          Restlet項目(http://www.restlet.org)為“建立REST概念與Java類之間的映射”提供了一個輕量級而全面的框架。它可用于實現任何種類的REST式系統,而不僅僅是REST式Web服務;而且,事實證明它自從2005年誕生之時起,就是一個可靠的軟件。
          estlet項目受到Servlet API、JSP(Java Server Pages)、HttpURLConnection及Struts等Web開發技術的影響。該項目的主要目標是:在提供同等功能的同時,盡量遵守Roy Fielding博士論文中所闡述的REST的目標。它的另一個主要目標是:提出一個既適于客戶端應用又適于服務端的應用的、統一的Web視圖。

          Restlet的思想是:HTTP客戶端與HTTP服務器之間的差別,對架構來說無所謂。一個軟件應可以既充當Web客戶端又充當Web服務器,而無須采用兩套完全不同的APIs。

          Restlet包括Restlet API和Noelios Restlet Engine(NRE)兩部分,NRE是對Restlet API的一種參考實現。這種劃分,使得不同實現可以具有同樣的API。NRE包括若干HTTP服務器連接器(HTTP server connector),它們都是基于Mortbay的Jetty、Codehaus的AsyncWeb,以及Simple框架這些流行的HTTP Java開源項目的。它甚至提供一個適配器(adapter),使你可以在標準Servlet容器(如Apache Tomcat)內部署一個Restlet應用。

          Restlet還提供兩個HTTP客戶端連接器(HTTP client connector)。它們一個是基于官方的HttpURLConnection類,一個是基于Apache的HTTP客戶端庫。還有一個連接器允許你容 易地按REST風格通過XML文檔來處理JDBC源(source);此外,一個基于JavaMail API的SMTP連接器允許你發送內容為XML的Email。

          Restlet API包括一些能夠創建基于字符串、文件、流(stream)、通道(channel)及XML文檔的表示(representation),它支持 SAX、DOM及XSLT。使用FreeMaker或Apache Velocity模板引擎,你可以很容易地創建基于JSP式模板的表示(representations)。你甚至可以像普通Web服務器那樣,用一個支 持內容協商(content negotiation)的Directory類來返回靜態文件與目錄。

          簡單性(simplicity)和靈活性(flexibility)是貫穿整個框架的設計原則。Restlet API旨在把HTTP、URI及REST的概念抽象成一系列類(classes),同時又不把低層信息(如原始HTTP報頭)完全隱藏起來。

          基本概念

          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.java
          import 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.java
          import 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:構造一個用戶的表示

          @Override
          public 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表示了。

          小結

          Restlet項目在2007年初發布了1.0正式版。它只用了12個多月的開發時間。目前,該項目具有一個繁榮的開發與用戶群體。Restlet 郵件列表很友好,不論是新手,還是有經驗的開發者,它都歡迎。作為該項目的創建者,Noelios咨詢公司是主要的開發力量,他們也提供專業的支持計劃與 培訓。

          在本書編寫之時,1.0版處于維護中,新的1.1版已經開始開發了。該項目計劃將來把Restlet API提交給JCP(Java Community Process)。還有一個用于REST式Web服務的高層API,它已由Sun公司提交給JCP(JSR311)。這個高層API使得“把Java領域 對象暴露為REST式資源”更加容易。這將是對Restlet API(尤其是其Resource類)的一個很好的補充。Noelios咨詢公司是最初的專家組成員,他們將根據標準的進展來對Restlet引擎作相應 的更新。


          posted on 2008-07-24 11:53 gembin 閱讀(1526) 評論(0)  編輯  收藏 所屬分類: SOA

          導航

          統計

          常用鏈接

          留言簿(6)

          隨筆分類(440)

          隨筆檔案(378)

          文章檔案(6)

          新聞檔案(1)

          相冊

          收藏夾(9)

          Adobe

          Android

          AS3

          Blog-Links

          Build

          Design Pattern

          Eclipse

          Favorite Links

          Flickr

          Game Dev

          HBase

          Identity Management

          IT resources

          JEE

          Language

          OpenID

          OSGi

          SOA

          Version Control

          最新隨筆

          搜索

          積分與排名

          最新評論

          閱讀排行榜

          評論排行榜

          free counters
          主站蜘蛛池模板: 泗洪县| 卢氏县| 新密市| 绩溪县| 清远市| 汕尾市| 竹溪县| 如皋市| 花莲市| 宁都县| 万盛区| 彭泽县| 中西区| 始兴县| 治多县| 洛浦县| 绥滨县| 永城市| 班戈县| 林芝县| 两当县| 山阴县| 明星| 吴旗县| 乌海市| 庐江县| 定远县| 灵武市| 双桥区| 宝应县| 宣化县| 连平县| 曲沃县| 丰都县| 平度市| 奇台县| 宣城市| 丽江市| 睢宁县| 万州区| 甘肃省|