REST 是 Roy Thomas Fielding [[1]](#fn1) 在 2000 年他的博士論文 [[2]](#fn2) 「架構風格以及基於網絡的軟件架構設計」 中提出來的一個概念。REST 是 RESTransfer 的縮寫,翻譯過來就是 「表現層狀態轉化」。REST 就是 Roy 在這篇論文中提出的面向互聯網的軟件所應當具有的架構風格。css
按照 REpresentational State Transfer 的字面意思,能夠把應用當作是一個虛擬的狀態機,軟件提供的不是服務而是一系列的資源統一的操做**來訪問,而返回的結果表明了資源狀態的一次躍遷。REST 是一種架構風格,若是一個軟件架構符合 REST 風格,就能夠稱之爲 RESTful 架構。這個架構應當具有如下一些設計上的約束:資源具備惟一標示、資源之間有關聯關係、使用標準的方式來訪問、資源有多種表現形式、無狀態交互。html
舉例來講,一個簡單的靜態 HTML 頁面的網站就很好的符合了 RESTful 架構風格。訪問 http://acme.com/accounts 返回一個包含全部帳號的頁面,選取其中一個連接 http://acme.com/accounts/1 又會返回包含用戶 1 的詳細信息。爬蟲軟件在這種場景下工做的很好,當知道了某個網站的首頁地址後,能夠自舉發現這個網站上全部關聯的網頁。更重要的是,這種訪問形式不依賴網站提供的任何客戶端,而是僅僅經過 HTTP 標準的訪問方式完成的。能夠說,HTML 這種超媒體文檔的組織形式就是資源的表現層狀態遷移的一種形式。java
對於一個提供服務的動態網站來講,能夠按照相似的思路將其 RESTful 化:nginx
其中的思路是利用 HTTP 協議的標準方法 POST、DELETE、PUT、GET 來表達對於一個資源的增刪改查 (CRUD) 操做,利用 URL 來表示一個資源的惟一標識。對資源訪問的錯誤碼也複用 HTTP 協議的狀態碼。返回結果一般由 json 或 XML 來表示,若是其中包換了對關聯資源的訪問方式 (所謂的表現層狀態遷移) ,這種類型的 RESTful 應用能夠進一步的稱之爲 hypermedia as the engine of application state (HATEOAS) 應用 [[3]](#fn3)。git
source: https://www.nginx.com/wp-content/uploads/2016/04/micro-image.pnggithub
這裏須要注意的是,按照 Roy 的定義,RESTful 架構風格與 HTTP 協議並無什麼強關聯關係。可是,基於 HTTP 的 RESTful 架構風格是實現起來最天然,也是目前使用最普遍的一種實現,咱們稱之爲 RESTful HTTP。一樣的,在下文中將會專一在 HTTP 的場景下介紹如何在 Dubbo 框架中將服務暴露成 Restful 架構。web
隨着微服務的流行以及多語言互操做訴求日益增多,在 Dubbo 中暴露 REST 服務變成了一個不容忽視的訴求。爲了在 Dubbo 中暴露 REST 服務,一般有兩種作法,一種是直接依賴 Sprng REST 或者其餘 REST 框架來直接暴露,另外一種是經過 Dubbo 框架內置的 REST 能力暴露。兩種作法各有優缺點,主要體如今前者與微服務體系中的服務發現組件可以更好的工做,然後者能夠無縫的享受到 Dubbo 體系中的服務發現以及服務治理的能力。本文關注的是如何使用後者來暴露 REST 服務。spring
自 2.6.0
開始,Dubbo 合併了當當網捐獻的 DubboX [[4]](#fn4) 中的主要特性,其中就包括了基於 RESTeasy 3.0.19.Final
的 REST 支持,具有 JAXRS 2.0 規範中全部的能力。apache
在如下的例子中,展現瞭如何經過最傳統的 Spring XML 配置的方式來快速的暴露和調用一個 REST 服務。其中底層的 server 使用的是 netty,服務註冊發現基於 Zookeeper。編程
注:本章節討論的示例能夠經過 https://github.com/beiwei30/dubbo-rest-samples/tree/master/basic 來得到
首先須要在項目中引入 dubbo all-in-one 的依賴以及 RESTEasy 相關的必要依賴。由於在本例中使用 Zookeeper 做爲服務發現,還須要引入 Zookeeper client 相關的依賴。爲了方便使用,第三方的依賴能夠經過框架提供的 BOM 文件 dubbo-dependencies-bom
來引入。
<properties> <dubbo.version>2.6.5</dubbo.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo-dependencies-bom</artifactId> <version>${dubbo.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.version}</version> </dependency> <!-- REST support dependencies --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-netty4</artifactId> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson-provider</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxb-provider</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> <!-- zookeeper client dependency --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> </dependency> </dependencies>
定義一個服務接口 UserService
,該接口提供兩個功能,一個是獲取指定 User 的詳細信息,另外一個是新註冊一個用戶。
@Path("users") // #1 @Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) // #2 @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) public interface UserService { @GET // #3 @Path("{id: \\d+}") User getUser(@PathParam("id") Long id); @POST // #4 @Path("register") Long registerUser(User user); }
經過在接口上用 JaxRS 標準的 annotation 來修飾,咱們規定了該服務在 REST 下的訪問形式:
@Path("users")
定義了 UserService 經過 '/users' 來訪問@Consumers
和 @Produces
來規定參數以及返回值的類型爲 XML 和 JSON。在類級別上定義以後,就能夠不用在方法級別上進一步定義了@GET
定義了接受的 HTTP 方法爲 GET,經過 @Path
來規定參數是來自於 URL 中的 path。'GET /users/1' 等同於調用 'getUser(1)'@POST
定義了接受的 HTTP 方法爲 POST,經過將 JSON 或 XML 格式的 User 數據 POST 到 '/users/register' 上來建立一個 User在 Dubbo 中,將 REST 相關的 annotation 定義在接口或者實現上都是能夠的。這個在設計上是個權衡問題。Annotation 定義在實現類上能夠保證接口的純淨,不然對於不須要經過 REST 方式調用的 Dubbo 調用方來講將須要強制依賴 JaxRS 的庫,可是同時,對於須要經過 REST 方式調用的 Dubbo 調用方來講,就須要本身來處理 REST 調用相關的細節了。Annotation 定義在接口上,框架會自動處理掉 REST 調用相關的細節,並和 Dubbo 的服務發現以及服務治理功能可以很好的結合起來。在本例中採用了在接口上定義 JaxRS annotation 的形式。
爲了簡潔,這裏給出的接口的實現只是簡單的返回了接口須要的類型的示例,在真實的系統中,邏輯可能會比較複雜。
public class UserServiceImpl implements UserService { private final AtomicLong id = new AtomicLong(); public User getUser(Long id) { return new User(id, "username-" + id); } public Long registerUser(User user) { return id.incrementAndGet(); } }
如上所述,本例展現的是如何經過傳統的 Spring XML 的方式來裝配並暴露 Dubbo 服務。須要指出的是,這裏展現瞭如何同時暴露兩種不一樣的協議,一種是 REST,另外一種是原生的 Dubbo 協議。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <dubbo:application name="rest-provider"/> <!-- #1 --> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <!-- #2 --> <dubbo:protocol name="rest" port="8080" server="netty"/> <!-- #3 --> <dubbo:protocol name="dubbo" server="netty4"/> <!-- #4 --> <dubbo:service interface="org.apache.dubbo.samples.rest.api.UserService" protocol="rest,dubbo" ref="userService"/> <!-- #5 --> <bean id="userService" class="org.apache.dubbo.samples.rest.impl.UserServiceImpl"/> </beans> <!-- #6 -->
rest-provider
簡單的經過 ClassPathXmlApplicationContext 來加載剛剛配置的 Spring XML 配置 'rest-provider.xml' 便可啓動 Dubbo 服務端
public class RestProvider { public static void main(String[] args) throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/rest-provider.xml"); context.start(); System.in.read(); } }
因爲本例依賴 Zookeeper 作服務註冊發現,在啓動 RestProvider 以前,須要先啓動一個 Zookeeper 服務器。以後就能夠直接運行 RestProvider 了。經過如下的輸出日誌,咱們能夠知道 UserService 以兩種方式對外暴露了同一個服務,其中:
... [01/01/19 07:18:56:056 CST] main INFO config.AbstractConfig: [DUBBO] Export dubbo service org.apache.dubbo.samples.rest.api.UserService to url rest://192.168.2.132:8080/org.apache.dubbo.samples.rest.api.UserService?anyhost=true&application=rest-provider&bean.name=org.apache.dubbo.samples.rest.api.UserService&bind.ip=192.168.2.132&bind.port=8080&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.samples.rest.api.UserService&methods=getUser,registerUser&pid=27386&server=netty&side=provider×tamp=1546341536194, dubbo version: 2.6.5, current host: 192.168.2.132 ... [01/01/19 07:18:57:057 CST] main INFO config.AbstractConfig: [DUBBO] Export dubbo service org.apache.dubbo.samples.rest.api.UserService to url dubbo://192.168.2.132:20880/org.apache.dubbo.samples.rest.api.UserService?anyhost=true&application=rest-provider&bean.name=org.apache.dubbo.samples.rest.api.UserService&bind.ip=192.168.2.132&bind.port=20880&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.samples.rest.api.UserService&methods=getUser,registerUser&pid=27386&server=netty4&side=provider×tamp=1546341537392, dubbo version: 2.6.5, current host: 192.168.2.132 ...
也能夠經過 zkCli 訪問 Zookeeper 服務器來驗證。'/dubbo/org.apache.dubbo.samples.rest.api.UserService/providers' 路徑下返回了一個數組 [dubbo://..., rest:.//...]。數組的第一個元素是 ’dubbo‘ 打頭的,而第二個元素是 'rest' 打頭的。
[zk: localhost:2181(CONNECTED) 10] ls /dubbo/org.apache.dubbo.samples.rest.api.UserService/providers [dubbo%3A%2F%2F192.168.2.132%3A20880%2Forg.apache.dubbo.samples.rest.api.UserService%3Fanyhost%3Dtrue%26application%3Drest-provider%26bean.name%3Dorg.apache.dubbo.samples.rest.api.UserService%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.rest.api.UserService%26methods%3DgetUser%2CregisterUser%26pid%3D27386%26server%3Dnetty4%26side%3Dprovider%26timestamp%3D1546341537392, rest%3A%2F%2F192.168.2.132%3A8080%2Forg.apache.dubbo.samples.rest.api.UserService%3Fanyhost%3Dtrue%26application%3Drest-provider%26bean.name%3Dorg.apache.dubbo.samples.rest.api.UserService%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.rest.api.UserService%26methods%3DgetUser%2CregisterUser%26pid%3D27386%26server%3Dnetty%26side%3Dprovider%26timestamp%3D1546341536194]
能夠簡單的經過 'curl' 在命令行驗證剛纔暴露出來的 REST 服務:
$ curl http://localhost:8080/users/1 {"id":1,"name":"username-1"} $ curl -X POST -H "Content-Type: application/json" -d '{"id":1,"name":"Larry Page"}' http://localhost:8080/users/register 1
Dubbo 調用方只須要依賴服務的接口,經過如下方式裝配好 Dubbo Consumer,便可發起調用。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <dubbo:application name="rest-consumer"/> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <dubbo:reference id="userService" interface="org.apache.dubbo.samples.rest.api.UserService" protocol="rest"/> <!-- #1 --> </beans>
須要特別指出的是,這裏顯示的指定 protocol="rest" 在一般狀況下不是必須的。這裏須要顯示指定的緣由是咱們例子中服務端同時暴露了多種協議,這裏指定使用 rest 是爲了確保調用方走 REST 協議。
簡單的經過 ClassPathXmlApplicationContext 來加載剛剛配置的 Spring XML 配置 'rest-consumer.xml' 便可發起對 RestProvider 所提供的 UserService 的 REST 服務的調用。
public class RestConsumer { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/rest-consumer.xml"); context.start(); UserService userService = context.getBean("userService", UserService.class); System.out.println(">>> " + userService.getUser(1L)); User user = new User(2L, "Larry Page"); System.out.println(">>> " + userService.registerUser(user)); } }
這裏分別展現了對 'getUser' 和 'registerUser' 的調用,輸出結果以下:
>>> User{id=1, name='username-1'} >>> 2
在 Dubbo 中使用 annotation 而不是 Spring XML 來暴露和引用服務,對於 REST 協議來講並無什麼不一樣。有關如何使用 annotation 更詳細的用法,請參閱《在 Dubbo 中使用註解》章節。這裏主要展現一下與上面基於 Spring XML 配置的例子不一樣之處。
注:本章節討論的示例能夠經過 https://github.com/beiwei30/dubbo-rest-samples/tree/master/annotation 來得到
1. 使用 Java Configuration 來配置服務提供方的 protocol、registry、application
@Configuration @EnableDubbo(scanBasePackages = "org.apache.dubbo.samples.rest.impl") // #1 static class ProviderConfiguration { @Bean // #2 public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("rest"); protocolConfig.setPort(8080); protocolConfig.setServer("netty"); return protocolConfig; } @Bean // #3 public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("localhost"); registryConfig.setPort(2181); return registryConfig; } @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("rest-provider"); return applicationConfig; } }
@EnableDubbo
來指定須要掃描 Dubbo 服務的包名,在本例中,UserServiceImpl 在 "org.apache.dubbo.samples.rest.impl" 下2. 使用 Service 來申明 Dubbo 服務
@Service // #1 public class UserServiceImpl implements UserService { ... }
@Service
或者 @Service(protocol = "rest")
修飾 "UserServiceImpl" 來申明一個 Dubbo 服務,這裏 protocol = "rest"
不是必須提供的,緣由是經過 Java Configuration 只配置了一個 ProtocolConfig 的示例,在這種狀況下,Dubbo 會自動裝配該協議到服務中3. 服務提供方啓動類
經過使用 ProviderConfiguration
來初始化一個 AnnotationConfigApplicationContext
實例,就能夠徹底擺脫 Spring XML 的配置文件,徹底藉助 annotation 來裝配好一個 Dubbo 的服務提供方。
public class RestProvider { public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class); context.start(); System.in.read(); } }
4. 使用 Java Configuration 來配置服務消費方的 registry、application
@Configuration @EnableDubbo(scanBasePackages = "org.apache.dubbo.samples.rest.comp") // #1 @ComponentScan({"org.apache.dubbo.samples.rest.comp"}) // #2 static class ConsumerConfiguration { @Bean // #3 public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("localhost"); registryConfig.setPort(2181); return registryConfig; } @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("rest-consumer"); return applicationConfig; } }
@EnableDubbo
來指定須要掃描 Dubbo 服務引用 @Reference
的包名。在本例中,UserService 的引用在 "org.apache.dubbo.samples.rest.comp" 下@ComponentScan
來指定須要掃描的 Spring Bean 的包名。在本例中,包含 UserService 引用的類 UserServiceComponent 自己須要是一個 Spring Bean,以方便調用,因此,這裏指定的包名也是 "org.apache.dubbo.samples.rest.comp"這裏提到的 UserServiceComponent 的 Spring Bean 定義以下:
@Component public class UserServiceComponent implements UserService { // #1 @Reference private UserService userService; @Override public User getUser(Long id) { return userService.getUser(id); } @Override public Long registerUser(User user) { return userService.registerUser(user); } }
UserService
接口,這樣在調用的時候也能夠面向接口編程5. 服務調用方啓動類
經過使用 ConsumerConfiguration
來初始化一個 AnnotationConfigApplicationContext
實例,就能夠徹底擺脫 Spring XML 的配置文件,徹底藉助 annotation 來裝配好一個 Dubbo 的服務消費方。而後就能夠經過查找 UserServiceComponent
類型的 Spring Bean 來發起遠程調用。
public class RestConsumer { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); context.start(); UserService userService = context.getBean(UserServiceComponent.class); System.out.println(">>> " + userService.getUser(1L)); User user = new User(2L, "Larry Page"); System.out.println(">>> " + userService.registerUser(user)); } }
目前 REST 協議在 Dubbo 中能夠跑在五種不一樣的 server 上,分別是:
<dubbo:protocol name="rest" server="netty"/>
來配置<dubbo:protocol name="rest" server="tomcat"/>
來配置<dubbo:protocol name="rest" server="jetty"/>
來配置<dubbo:protocol name="rest" server="sunhttp"/>
來配置,僅推薦在開發環境中使用<dubbo:protocol name="rest" server="servlet"/>
以外,還須要在 web.xml 中作額外的配置因爲以上的例子展現了 "netty" 做爲 rest server,下面演示一下使用嵌入式 tomcat 的 rest server 的用法。
注:本章節討論的示例能夠經過 https://github.com/beiwei30/dubbo-rest-samples/tree/master/tomcat 來得到
1. 增長 Tomcat 相關的依賴
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-logging-juli</artifactId> </dependency>
2. 配置 protocol 使用 tomcat 做爲 REST server
<dubbo:protocol name="rest" port="8080" server="tomcat"/>
啓動服務提供方以後,在如下的輸出將會出現與嵌入式 Tomcat 相關的日誌信息:
Jan 01, 2019 10:15:12 PM org.apache.catalina.core.StandardContext setPath WARNING: A context path must either be an empty string or start with a '/' and do not end with a '/'. The path [/] does not meet these criteria and has been changed to [] Jan 01, 2019 10:15:13 PM org.apache.coyote.AbstractProtocol init INFO: Initializing ProtocolHandler ["http-nio-8080"] Jan 01, 2019 10:15:13 PM org.apache.tomcat.util.net.NioSelectorPool getSharedSelector INFO: Using a shared selector for servlet write/read Jan 01, 2019 10:15:13 PM org.apache.catalina.core.StandardService startInternal INFO: Starting service [Tomcat] Jan 01, 2019 10:15:13 PM org.apache.catalina.core.StandardEngine startInternal INFO: Starting Servlet Engine: Apache Tomcat/8.5.31 Jan 01, 2019 10:15:13 PM org.apache.coyote.AbstractProtocol start INFO: Starting ProtocolHandler ["http-nio-8080"]
進一步的,還可使用外部的 servlet 容器來啓動 Dubbo 的 REST 服務。
注:本章節討論的示例能夠經過 https://github.com/beiwei30/dubbo-rest-samples/tree/master/servlet 來得到
1. 修改 pom.xml 改變打包方式
由於使用的是外部的 servlet 容器,須要將打包方式修改成 "war"
<packaging>war</packaging>
2. 修改 rest-provider.xml
配置 "server" 爲 "servlet" 表示將使用外部的 servlet 容器。並配置 "contextpath" 爲 "",緣由是在使用外部 servlet 容器時,Dubbo 的 REST 支持須要知道被託管的 webapp 的 contextpath 是什麼。這裏咱們計劃經過 root context path 來部署應用,因此配置其爲 ""。
<dubbo:protocol name="rest" port="8080" server="servlet" contextpath=""/>
3. 配置 WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <context-param> <!-- #1 --> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/classes/spring/rest-provider.xml</param-value> </context-param> <listener> <listener-class>com.alibaba.dubbo.remoting.http.servlet.BootstrapListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <!-- #2 --> <servlet-name>dispatcher</servlet-name> <servlet-class>com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/api/*</url-pattern> </servlet-mapping>
這樣作以後,再也不須要 RestProvider 來啓動 Dubbo 服務,能夠將其從工程中刪掉。對應的,如今 Dubbo 的服務將會隨着 Servlet 容器的啓動而啓動。啓動完畢以後,能夠經過相似 "http://localhost:8080/api/users/1" 來訪問暴露出的 REST 服務。須要注意的是,這個例子裏假定了服務提供方的 WAR 包部署在 root context path 上,因此當該應用經過 IDE 配置的 tomcat server 啓動時,須要指定 Application Context 爲 "/"。
在上面使用外部 Servlet 容器的例子的基礎上,討論如何暴露 Swagger OpenApi 以及如何繼承 Swagger UI。
注:本章節討論的示例能夠經過 https://github.com/beiwei30/dubbo-rest-samples/tree/master/servlet 來得到
1. 暴露 Swagger OpenApi
增長 swagger 相關依賴,以便經過 "http://localhost:8080/openapi.json" 來訪問 REST 服務的描述
<properties> <swagger.version>2.0.6</swagger.version> </properties> <dependencies> <dependency> <groupId>io.swagger.core.v3</groupId> <artifactId>swagger-jaxrs2</artifactId> <version>${swagger.version}</version> </dependency> <dependency> <groupId>io.swagger.core.v3</groupId> <artifactId>swagger-jaxrs2-servlet-initializer</artifactId> <version>${swagger.version}</version> </dependency> </dependencies>
修改 WEB-INF/web.xml,增長 openapi servlet 的配置
<web-app> ... <servlet> <!-- #3 --> <servlet-name>openapi</servlet-name> <servlet-class>io.swagger.v3.jaxrs2.integration.OpenApiServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>openapi</servlet-name> <url-pattern>/openapi.json</url-pattern> <url-pattern>/openapi.yaml</url-pattern> </servlet-mapping> </web-app>
從新啓動應用以後,能夠經過訪問 "http://localhost:8080/openapi.json" 或者 "http://localhost:8080/openapi.yaml" 來訪問暴露出的 openapi 的契約,如下是 yaml 格式的表述:
openapi: 3.0.1 paths: /api/users/{id}: get: operationId: getUser parameters: - name: id in: path required: true schema: type: integer format: int64 responses: default: description: default response content: application/json: schema: $ref: '#/components/schemas/User' text/xml: schema: $ref: '#/components/schemas/User' /api/users/register: post: operationId: registerUser requestBody: description: a user to register content: application/json: schema: $ref: '#/components/schemas/User' text/xml: schema: $ref: '#/components/schemas/User' responses: default: description: default response content: application/json: schema: type: integer format: int64 text/xml: schema: type: integer format: int64 components: schemas: User: type: object properties: id: type: integer format: int64 name: type: string
2. 集成 Swagger UI
在 pom.xml 中繼續增長 swagger-ui 的依賴,這裏使用的是 webjars 的版本,從集成的角度來講更加簡潔。webjars 的工做機制能夠參閱 webjars 官網 [[5]](#fn5)
<properties> <swagger.webjar.version>3.20.3</swagger.webjar.version> </properties> <dependencies> <dependency> <groupId>org.webjars</groupId> <artifactId>swagger-ui</artifactId> <version>${swagger.webjar.version}</version> </dependency> </dependencies>
在工程的 webapp/WEB-INF 根目錄下增長一個 HTML 文件,內容以下。HTML 文件名能夠爲任何名字,沒有硬性要求,若是該文件被命名爲 "swagger-ui.html",那麼你能夠經過訪問 「http://localhost:8080/swagger-ui.html" 來訪問 swagger UI。本例爲了演示方便起見,將其命名爲 "index.html",這樣當訪問 "http://localhost:8080" 時,就能夠很方便的獲得 swagger UI 的頁面。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>API UI</title> <link rel="stylesheet" type="text/css" href="webjars/swagger-ui/3.20.3/swagger-ui.css" > <link rel="icon" type="image/png" href="webjars/swagger-ui/3.20.3/favicon-32x32.png" sizes="32x32" /> <link rel="icon" type="image/png" href="webjars/swagger-ui/3.20.3/favicon-16x16.png" sizes="16x16" /> <style> html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; } *, *:before, *:after { box-sizing: inherit; } body { margin:0; background: #fafafa; } </style> </head> <body> <div id="swagger-ui"></div> <script src="webjars/swagger-ui/3.20.3/swagger-ui-bundle.js"> </script> <script src="webjars/swagger-ui/3.20.3/swagger-ui-standalone-preset.js"> </script> <script> window.onload = function () { window.ui = SwaggerUIBundle({ url: "openapi.json", dom_id: '#swagger-ui', deepLinking: true, presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], plugins: [ SwaggerUIBundle.plugins.DownloadUrl ], layout: "StandaloneLayout" }); }; </script> </body> </html>
再次重啓服務器,並訪問 "http://localhost:8080" 時,將會看到 swagger UI 頁面的展現:
經過 Swagger UI 能夠很方便的瀏覽當前服務器提供的 REST 服務的文檔信息,甚至能夠直接調用來作服務測試。以 '/api/users/{id}' 爲例,測試結果以下圖所示:
本文主要關注了在 Dubbo 中支持 REST 協議的狀況。首先探索了 REST 概念的起源,澄清了 REST 是一種適合互聯網的軟件架構風格,進一步的說明了 REST 風格的架構能夠與 HTTP 協議無關,可是 HTTP 協議的確是 REST 風格架構的最經常使用甚至是最佳的組合和搭檔。而後討論瞭如何在 Dubbo 中開發 REST HTTP 的幾種典型用法,其中包括了經過不一樣的配置,如傳統的 Spring XML,徹底經過 annotation 來配置兩種典型的用法,本文中沒有涉及到的還有純 API 編程方式,Spring Boot 配置方式也是徹底能夠的,由於篇幅緣由沒有說起;還討論瞭如何經過不一樣的 REST server 來暴露 REST HTTP 服務,包括了 embedded tomcat,netty,以及外置的 servlet 容器等幾種用法。最後,在外置的 servlet 容器的基礎上,進一步的討論瞭如何經過 Swagger 暴露 openAPI 以及集成 Swagger UI 的方法。
本文沒有涉及的內容包含但不限於國際化支持、Dubbo REST 更高階的注入擴展的用法、以及 Dubbo REST 支持將來的規劃。其中 Dubbo REST 擴展的支持能夠參考 https://github.com/beiwei30/dubbo-rest-samples/tree/master/extensions 中的演示。之後有機會會開專門的篇幅來探討更高級的 Swagger 的支持、以及對將來的展望。
http://en.wikipedia.org/wiki/Roy_Fielding ↩︎
http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm ↩︎
https://martinfowler.com/articles/richardsonMaturityModel.html ↩︎
https://github.com/dangdangdotcom/dubbox ↩︎
https://www.webjars.org/documentation#servlet3 ↩︎
原文連接 更多技術乾貨 請關注阿里云云棲社區微信號 :yunqiinsight