Exposing Services in Dubbo, the RESTful Way: Basics to Advanced
A rundown of how to expose services with RESTful architecture using built-in Dubbo features that have appeared over the last year or more
This article is part of the Alibaba Open Source series.
REST architecture is one of the fundamental concepts that governs how services work on the World Wide Web. Specifically, it defines universal best practices for developing web apps on a conceptual level. These best practices are designed to apply independently of HTTP, but also to guide its proper implementation.
With Dubbo having been developed to support RPC, and with the growing popularity of microservices and increasing demand for multilingual interoperability, the capability to expose RESTful services in the Dubbo framework has become a must. Thankfully, support for REST architecture in Dubbo has existed since the release of version 2.6.0 in January 2018. In this version, Dubbo formally incorporated the main features of DubboX, one of these being RESTEasy 3.0.19.Final. RESTEasy offers fully REST support with all the capabilities of the JAXRS 2.0 specification.
This article provides a step-by-step guide on one method of supporting REST architecture in Dubbo. Firstly, however, it is worth providing some further background on REST architecture to fully contextualize the idea of exposing RESTful services.
A brief history of REST
First put forward by Roy Thomas Fielding in his doctoral thesis, “The Architectural Style and Web-based Software Architecture Design” in 2000, REST stands for REpresentational State Transfer. The term alludes to the way Fielding felt web applications should operate:
· By means of resources, not services, and by transfer of representations of resources, not resources themselves.
· By means of unified operations for accessing and manipulating resources. The returned result of an operation represents the transition of the resource state.
REST is a concept, not a specific software framework. It is an approach to application architecture that defines a series of constraints, namely:
· Resources have unique identifiers
· Resources are related
· There is a standard access method
· There are multiple representations of resources
· Interactions are stateless.
If an application’s architecture meets these constraints, it can be described as a RESTful architecture. A good example of such an application is a website with a simple static HTML page, e.g.:
· Visiting http://acme.com/accounts returns a page containing a list of all accounts;
· Selecting one of the links http://acme.com/accounts/1/ returns a page with account details of user 1;
· And so on.
Crawler software works best on the most RESTful websites. Why? Firstly, because on a RESTful website, once you know the home page address, you can discover all related web pages on the website. Secondly, and more importantly, because to access the site’s resources you only need standard HTTP, not some sort of site-specific portal.
REST and HTTP
RESTful architecture was not conceived of to be limited to HTTP. However, REST largely emerged as a result of Fielding’s process of justifying Web principles and HTTP syntax to developers. So, it’s safe to say that RESTful HTTP — the term for a HTTP-based RESTful architectural style — is REST’s most iconic incarnation. It is also the most natural and widely used mode of implementing RESTful architecture.
To revisit our acme.com example, here’s how the website might provide RESTful services using standardized HTTP operations.
Returns all account information
Creates a new account
GET http://acme.com/accounts/1
Returns the information of account 1
DELETE http://acme.com/accounts/1
Deletes account 1
PUT http://acme.com/accounts/1
Updates account 1 information
The idea is to use the standard methods of the HTTP protocol i.e. POST, DELETE, PUT, GET to express the CRUD operation of a resource, as well as using the URL to represent the unique identifier of a resource. The error code for resource access is also multiplexed with the status code of the HTTP protocol. The returned result is usually represented by json or XML. If access to the associated resource is changed (in other words, there is a state transfer at the presentation layer), this type of RESTful application can be further called “hypermedia as the engine of application state (HATEOAS)” application.
REST and Dubbo
There are two main approaches to exposing a service in Dubbo:
1. Directly expose it using Spring REST or another REST framework.
2. Expose it via Dubbo’s built-in REST capabilities.
Both have their benefits and drawbacks. The first approach works better with the service discovery component in the micro-service system, while the second benefits from seamless compatibility with service discovery and governance capabilities in the Dubbo system.
The examples in this article adopt the second approach.
Exposing Services in Dubbo: Basics
In the following examples, we show how to quickly expose and invoke a REST service via the most traditional Spring XML configuration. The underlying server uses netty and the service registration discovery is based on Zookeeper.
The following steps are covered:
1. Maven dependency
2. Defining a service interface
3. Implementing the service interface
4. Assembling the service
5. Service provider startup class
6. Starting the server
7. Assembling the caller
8. Invoking the call
(Note: All instances discussed in this section can be obtained here: https://github.com/beiwei30/dubbo-rest-samples/tree/master/basic)
1. Maven dependency
First, you need to introduce the dependency of Dubbo all-in-one and the necessary dependencies related to RESTEasy in your project. Because Zookeeper is used for service discovery in this example, it is also necessary to introduce dependencies related to the ZooKeeper client. For ease of use, third-party dependencies can be introduced via the BOM file dubbo-dependencies-bom provided by the framework.
<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>
2. Defining a service interface
A service interface UserService is defined, which has two functions:
· To get the details of the specified User
· To register a new 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);
}
By modifying the service interface with the JaxRS standard annotation on the interface, we specify the form of access to the service under REST:
1. @Path (“users”) defines that UserService is accessed via ‘/users’;
2. We define @Consumers and @Produces at the class level to specify the parameters and return types which are XML and JSON. After defining at the class level, you don’t have to define it further at the method level.
3. The accepted HTTP method is defined via @GET at the getUser method, and the parameter which is defined via @Path is from the path in the URL. ‘GET /users/1’ is equivalent to invoking ‘getUser(1)’.
4. The accepted HTTP method is defined via @POST as POST at registerUser method. We create a User by POST-ing the JSON or XML-formatted User data to ‘/users/register’.
In Dubbo, REST-related annotations can be defined on both interfaces and implementations. This is a trade-off in its design, as defining annotation on the implementation class guarantees the purity of the interface. Otherwise, a Dubbo caller who does not need to be invoked via REST relies on the library of JaxRS, while Dubbo callers who do need to be called via REST have to deal with the details of the REST call by themselves.
Annotation is defined on the interface. The framework automatically handles the details of REST calls and is well integrated with Dubbo’s service discovery and governance capabilities. In this instance, the JaxRS annotation is defined on the interface.
3. Implementing the service interface
For the sake of brevity, the implementation of the interface presented here simply returns an example of the type that interface requires. In a real system, the logic is likely to be more complicated.
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();
}
}
4. Assembling the service
As above, this instance demonstrates how to configure and expose Dubbo services via traditional Spring XML. It should be pointed out that we have shown how to expose two different protocols at the same time here: One is REST, the other is native Dubbo protocol.
<?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 -->
To summarize:
1. The name of the application is defined as rest-provider.
2. The service registry is defined via Zookeeper, and URL as “zookeeper://127.0.0.1:2181”.
3. The service is exposed in a RESTful way on port 8080. The underlying transport uses netty.
4. The service is exposed in the way of native Dubbo on the default port 20880. The underlying transport framework is netty.
5. The Spring bean (i.e. UserServiceImpl) of “userService” is exposed as a UserService service. The supported protocols include both REST and Dubbo.
6. The UserServiceImpl is registered as the Spring bean of “userService”.
5. Service provider startup class
The Spring XML that was configured just now can be loaded simply via ClassPathXmlApplicationContext. Once “rest-provider.xml” is configured, the Dubbo server-side can be started.
public class RestProvider {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/rest-provider.xml");
context.start();
System.in.read();
}
}
6. Starting the server
Since this example relies on ZooKeeper for service registration discovery, you need to start a ZooKeeper server before starting RestProvider. Then, you can run RestProvider directly. From the following output log, we can know that UserService exposes the same service in two ways:
· REST: rest://192.168.2.132:8080/org.apache.dubbo.samples.rest.api.UserService
· Dubbo: dubbo://192.168.2.132:20880/org.apache.dubbo.samples.rest.api.UserServic
...
[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
...
It can also be verified via zkCli to access Zookeeper server.
An array [dubbo: //…, rest: //…] is returned under the path of dubbo/org.apache.dubbo.samples.rest.api.UserService/providers’. The first element of the array starts with “dubbo” and the second one starts with “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]
You can simply verify the REST service which was exposed just now on the command line with ‘curl’:
$ 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
7. Assembling the caller
Relying only on the interface of the service, the Dubbo caller can be invoked after assembling Dubbo Consumer via the following method:
<?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>
The protocol configured by ‘userService’ is “rest”, which will invoke the server via the REST protocol.
It is worth pointing out that the specified protocol=”rest” shown here is not required under normal circumstances. The reason why we needed to display it here is that the server in our example exposes multiple protocols at the same time. Rest is specified here to ensure that the caller uses the REST protocol.
8. Invoking the call
The Sping XML which you just configured can now be loaded via ClassPathXmlApplicationContext and used to configure “rest-consumer.xml”. Once this is done, it can be used to initiate the call of REST service of UserService provided by RestProvider.
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));
}
}
The calls of ‘getUser’ and ‘registerUser’ are shown here and the output is as follows:
>>> User{id=1, name='username-1'}
>>> 2
Advanced
This section introduces some more advanced functions. The following topics are covered:
· Using annotation in REST
· Running the protocol on a different server
· Using an external Servlet container
· Adding Swagger support
Using annotation in REST
If you use annotations in Dubbo instead of Spring XML to expose and reference services, it makes no difference to the REST protocol. Here is a look at the differences between the two, with a comparison to the above Spring XML configuration-based example.
(Note: All instances discussed in this section can be obtained here: https://github.com/beiwei30/dubbo-rest-samples/tree/master/annotation)
- Using Java Configuration to configure the protocol, registry, and application of the services provider.
@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;
}
}
1. Specify the package name that needs to scan the Dubbo service via @EnableDubbo. In this instance, UserServiceImpl is under “org.apache.dubbo.samples.rest.impl”.
2. Specify the service provider to expose the service in REST by providing a Spring Bean of ProtocolConfig.
3. Specify the service registration mechanism used by the service provider by providing a Spring Bean of RegistryConfig.
2. Using @Service to declare Dubbo services.
@Service // #1
public class UserServiceImpl implements UserService {
...
}
@Service or @Service (protocol = “rest”) is used to modify “UserServiceImpl” to declare a Dubbo service, where protocol = “rest” is not required, because only one instance of ProtocolConfig is configured via Java Configuration. In this instance, Dubbo will automatically assemble the protocol into the service.
3. Service provider startup class
By initializing an AnnotationConfigApplicationContext instance via ProviderConfiguration, you can completely get rid of the Spring XML configuration file and just assemble a Dubbo service provider with annotations.
public class RestProvider {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();
}
}
4. Using Java Configuration to configure the registry and application of the services user.
@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;
}
}
1. The package name that needs to scan the Dubbo service reference @Reference is specified via @EnableDubbo. In this instance, the UserService’s reference is under “org.apache.dubbo.samples.rest.comp”
2. The package name of the Spring Bean that needs to be scanned is specified via @ComponentScan. In this instance, the class UserService itself that contains UserServiceComponent’s reference should be a Spring Bean for easy invocation, so the package name specified here is also “org.apache.dubbo.samples.rest.comp”.
3. Service discovery mechanism used by the service consumer is specified by providing a Spring Bean of RegistryConfig.
The definition of the Spring Bean of the UserServiceComponent mentioned here is as follows:
@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);
}
}
Best practice here is to let this Spring Bean also inherit the UserService interface, so that it can also be interface-based programming when called.
5. Service caller startup class
By using ConsumerConfiguration to initialize an AnnotationConfigApplicationContext instance, you can completely get rid of the Spring XML configuration file and fully assemble a Dubbo service consumer with annotations. Then you can initiate a remote call by looking up the Spring Bean of UserServiceComponent-type.
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));
}
}
Running the protocol on a different server
Currently, the REST protocol can run on five different servers in Dubbo, namely:
· “netty”: rest server which is directly based on the netty framework, can be configured via <dubbo:protocol name=”rest” server=”netty”/>
· “tomcat”: rest server which is based on the embedded tomcat, can be configured via <dubbo:protocol name=”rest” server=”tomcat”/>
· “jetty”: default option, rest server which is based on the embedded jetty, can be configured via <dubbo:protocol name=”rest” server=”jetty”/>
· “sunhttp”: the JDK’s built-in Sun HTTP server is used as the rest server; it is configured via <dubbo:protocol name=”rest” server=”sunhttp”/>, and only recommended for use in development environments
· “servlet”: the servlet container of the external application server is used as the rest server. In this circumstance, in addition to configuring <dubbo:protocol name=”rest” server=”servlet”/>, you need to do extra configuration in web.xml.
Since the above example shows “netty” as the rest server, the following demonstrates the use of the rest server using embedded tomcat.
(Note: All instances discussed in this section can be obtained here: https://github.com/beiwei30/dubbo-rest-samples/tree/master/tomcat)
- Increasing the related dependency of 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. Configuring protocol and using tomcat as REST server;
<dubbo:protocol name="rest" port="8080" server="tomcat"/>
After starting the service provider, log information related to built-in Tomcat will appear in the following output:
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"]
Using an external Servlet container
You can also use an external servlet container to start Dubbo’s REST service.
(Note: All instances discussed in this section can be obtained here: https://github.com/beiwei30/dubbo-rest-samples/tree/master/servlet)
1. Modifying pom.xml to change packaging method
Because you are using an external servlet container, you need to change the packaging method to “war”.
<packaging>war</packaging>
2. Modifying rest-provider.xml
Configuring “server” as “servlet” means that an external servlet container will be used. “contextpath” is configured as “”, because when using an external servlet container, Dubbo’s REST support needs to know what the contextpath of the hosted webapp is.
Here we are going to deploy the application via the root context path, so configure it as “”.
<dubbo:protocol name="rest" port="8080" server="servlet" contextpath=""/>
3. Configuring 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>
1. Configure Dubbo and Spring-related ContextListener by turning on Dubbo HTTP support and assembling the Dubbo service via rest-provider.xml.
2. Configure the DiapatcherServlet, which is required by Dubbo HTTP.
After doing this, you no longer need the RestProvider to start the Dubbo service, so you can remove it from the project. Correspondingly, Dubbo’s service will now start as the servlet container starts. Once started, you can access the exposed REST service by something like “http://localhost:8080/api/users/1".
It should be noted that this example assumes that the service provider’s WAR package is deployed on the root context path, so when the application is launched via the IDE-configured tomcat server, you need to specify the Application Context as “/”.
Adding Swagger support
Here we discuss how to expose Swagger OpenApi and inherit Swagger UI based on the above example of using an external servlet container.
(Note: All instances discussed in this section can be obtained here: https://github.com/beiwei30/dubbo-rest-samples/tree/master/servlet)
1. Exposing Swagger OpenApi
The related dependencies of swagger are added to access the description of the REST service via “http://localhost:8080/openapi.json".
<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 is modified to increase the configuration of 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>
After restarting the application, you can access the exposed openapi contract via “http://localhost:8080/openapi.json" or “http://localhost:8080/openapi.yaml".
The following is a representation in yaml format:
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. Integrating Swagger UI
The swagger-ui dependency is increased continually in pom.xml. The version here is webjars, which is more concise from the perspective of integration. For the working mechanism of webjars, please see webjars official website.
<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>
Add an HTML file to the root contents of the project’s webapp/WEB-INF with the file content shown below.
The HTML file name can be anything you like. If the file were named “swagger-ui.html”, you would access the swagger UI by visiting “http://localhost:8080/swagger-ui.html". For the convenience of this demonstration, this example is named “index.html”, so when we enter “http://localhost:8080", we can easily get the page of 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>
When you restart the server again and access” http://localhost:8080", you will see the display of the swagger UI page:
The Swagger UI makes it easy for you to browse the documentation of the REST service provided by the current server. It can even be called directly for service testing. To take ‘/api/users/{id}’ as an example, the test results are shown below:
Going Further with REST and Dubbo
If you are interested in finding out more about some of the topics brought up in this article, here are some links to get you started:
1. http://en.wikipedia.org/wiki/Roy_Fielding
2. http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
3. https://martinfowler.com/articles/richardsonMaturityModel.html
4. https://github.com/dangdangdotcom/dubbox
5. https://www.webjars.org/documentation#servlet3
Additionally, there is a good demonstration on further advanced uses of injecting extensions of Dubbo REST here: https://github.com/beiwei30/dubbo-rest-samples/tree/master/extensions.
There are also plenty of other Dubbo features worth exploring, including internationalization support, Dubbo REST’s future plans, and advanced Swagger support. We hope to have the chance to cover some of these topics in future articles.
Alibaba Tech
First hand and in-depth information about Alibaba’s latest technology → Facebook: “Alibaba Tech”. Twitter: “AlibabaTech”.