目录
- 概述
- REST的优点
- 应用场景
- 快速入门
- 标准Java REST API:JAX-RS简介
- REST服务提供端详解
- HTTP POST/GET的实现
- Annotation放在接口类还是实现类
- JSON、XML等多数据格式的支持
- 中文字符支持
- XML数据格式的额外要求
- 定制序列化
- 配置REST Server的实现
- 获取上下文(Context)信息
- 配置端口号和Context Path
- 配置线程数和IO线程数
- 配置长连接
- 配置最大的HTTP连接数
- 配置每个消费端的超时时间和HTTP连接数
- GZIP数据压缩
- 用Annotation取代部分Spring XML配置
- 添加自定义的Filter、Interceptor等
- 添加自定义的Exception处理
- 配置HTTP日志输出
- 输入参数的校验
- 是否应该透明发布REST服务
- REST服务消费端详解
- 场景1:非dubbo的消费端调用dubbo的REST服务
- 场景2:dubbo消费端调用dubbo的REST服务
- 场景3:dubbo的消费端调用非dubbo的REST服务
- Dubbo中JAX-RS的限制
- REST常见问题解答(REST FAQ)
- Dubbo REST的服务能和Dubbo注册中心、监控中心集成吗?
- Dubbo REST中如何实现负载均衡和容错(failover)?
- JAX-RS中重载的方法能够映射到同一URL地址吗?
- JAX-RS中作POST的方法能够接收多个参数吗?
- Dubbo当前体系可能的不足之处(与REST相关的)
- RpcContext的侵入性
- Protocol配置的局限性
- XML命名不符合spring规范
- REST最佳实践
- 性能基准测试
- 测试环境
- 测试脚本
- 测试结果
- 扩展讨论
- REST与Thrift、Protobuf等的对比
- REST与传统WebServices的对比
- JAX-RS与Spring MVC的对比
- 未来
概述
dubbo支持多种远程调用方式,例如dubbo RPC(二进制序列化 + tcp协议)、http invoker(二进制序列化 + http协议,至少在开源版本没发现对文本序列化的支持)、hessian(二进制序列化 + http协议)、WebServices (文本序列化 + http协议)等等,但缺乏对当今特别流行的REST风格远程调用(文本序列化 + http协议)的支持。
有鉴于此,我们基于标准的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的简写),为dubbo提供了接近透明的REST调用支持。由于完全兼容Java标准API,所以为dubbo开发的所有REST服务,未来脱离dubbo或者任何特定的REST底层实现一般也可以正常运行。
特别值得指出的是,我们并不需要完全严格遵守REST的原始定义和架构风格。即使著名的Twitter REST API也会根据情况做适度调整,而不是机械的遵守原始的REST风格。
附注:我们将这个功能称之为REST风格的远程调用,即RESTful Remoting(抽象的远程处理或者调用),而不是叫RESTful RPC(具体的远程“过程”调用),是因为REST和RPC本身可以被认为是两种不同的风格。在dubbo的REST实现中,可以说有两个面向,其一是提供或消费正常的REST服务,其二是将REST作为dubbo RPC体系中一种协议实现,而RESTful Remoting同时涵盖了这个面向。
REST的优点
以下摘自维基百科:
- 可更高效利用缓存来提高响应速度
- 通讯本身的无状态性可以让不同的服务器的处理一系列请求中的不同请求,提高服务器的扩展性
- 浏览器即可作为客户端,简化软件需求
- 相对于其他叠加在HTTP协议之上的机制,REST的软件依赖性更小
- 不需要额外的资源发现机制
- 在软件技术演进中的长期的兼容性更好
这里我还想特别补充REST的显著优点:基于简单的文本格式消息和通用的HTTP协议,使它具备极广的适用性,几乎所有语言和平台都对它提供支持,同时其学习和使用的门槛也较低。
应用场景
正是由于REST在适用性方面的优点,所以在dubbo中支持REST,可以为当今多数主流的远程调用场景都带来(显著)好处:
-
显著简化企业内部的异构系统之间的(跨语言)调用。此处主要针对这种场景:dubbo的系统做服务提供端,其他语言的系统(也包括某些不基于dubbo的java系统)做服务消费端,两者通过HTTP和文本消息进行通信。即使相比Thrift、ProtoBuf等二进制跨语言调用方案,REST也有自己独特的优势(详见后面讨论)
-
显著简化对外Open API(开放平台)的开发。既可以用dubbo来开发专门的Open API应用,也可以将原内部使用的dubbo service直接“透明”发布为对外的Open REST API(当然dubbo本身未来最好可以较透明的提供诸如权限控制、频次控制、计费等诸多功能)
-
显著简化手机(平板)APP或者PC桌面客户端开发。类似于2,既可以用dubbo来开发专门针对无线或者桌面的服务器端,也可以将原内部使用的dubbo service直接”透明“的暴露给手机APP或桌面程序。当然在有些项目中,手机或桌面程序也可以直接访问以上场景2中所述的Open API。
-
显著简化浏览器AJAX应用的开发。类似于2,既可以用dubbo来开发专门的AJAX服务器端,也可以将原内部使用的dubbo service直接”透明“的暴露给浏览器中JavaScript。当然,很多AJAX应用更适合与web框架协同工作,所以直接访问dubbo service在很多web项目中未必是一种非常优雅的架构。
-
为企业内部的dubbo系统之间(即服务提供端和消费端都是基于dubbo的系统)提供一种基于文本的、易读的远程调用方式。
-
一定程度简化dubbo系统对其它异构系统的调用。可以用类似dubbo的简便方式“透明”的调用非dubbo系统提供的REST服务(不管服务提供端是在企业内部还是外部)
需要指出的是,我认为1~3是dubbo的REST调用最有价值的三种应用场景,并且我们为dubbo添加REST调用,其最主要到目的也是面向服务的提供端,即开发REST服务来提供给非dubbo的(异构)消费端。
借用Java过去最流行的宣传语,为dubbo添加REST调用后,可以实现服务的”一次编写,到处访问“,理论上可以面向全世界开放,从而真正实现比较理想化的面向服务架构(SOA)。
当然,传统的WebServices(WSDL/SOAP)也基本同样能满足以上场景(除了场景4)的要求(甚至还能满足那些需要企业级特性的场景),但由于其复杂性等问题,现在已经越来越少被实际采用了。
快速入门
在dubbo中开发一个REST风格的服务会比较简单,下面以一个注册用户的简单服务为例说明。
这个服务要实现的功能是提供如下URL(注:这个URL不是完全符合REST的风格,但是更简单实用):
http://localhost:8080/users/register
而任何客户端都可以将包含用户信息的JSON字符串POST到以上URL来完成用户注册。
首先,开发服务的接口:
public class UserService {
void registerUser(User user);
}
然后,开发服务的实现:
@Path("users")
public class UserServiceImpl implements UserService {
@POST
@Path("register")
@Consumes({MediaType.APPLICATION_JSON})
public void registerUser(User user) {
// save the user...
}
}
上面的服务实现代码非常简单,但是由于REST服务是要被发布到特定HTTP URL,供任意语言客户端甚至浏览器来访问,所以这里要额外添加了几个JAX-RS的标准annotation来做相关的配置:
@Path("users"):指定访问UserService的URL相对路径是/users,即http://localhost:8080/users
@Path("register"):指定访问registerUser()方法的URL相对路径是/register,再结合上一个@Path为UserService指定的路径,则调用UserService.register()的完整路径为http://localhost:8080/users/register
@POST:指定访问registerUser()用HTTP POST方法
@Consumes({MediaType.APPLICATION_JSON}):指定registerUser()接收JSON格式的数据。REST框架会自动将JSON数据反序列化为User对象
最后,在spring配置文件中添加此服务,即完成所有服务开发工作:
<!-- 用rest协议在8080端口暴露服务 -->
<dubbo:protocol name="rest" port="8080"/>
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="xxx.UserService" ref="userService"/>
<!-- 和本地bean一样实现服务 -->
<bean id="userService" class="xxx.UserServiceImpl" />
标准Java REST API:JAX-RS简介
JAX-RS是标准的Java REST API,得到了业界的广泛支持和应用,其著名的开源实现就有很多,包括Oracle的Jersey,RedHat的RestEasy,Apache的CXF和Wink,以及restlet等等。另外,所有支持JavaEE 6.0以上规范的商用JavaEE应用服务器都对JAX-RS提供了支持。因此,JAX-RS是一种已经非常成熟的解决方案,并且采用它没有任何所谓vendor lock-in的问题。
JAX-RS在网上的资料非常丰富,例如下面的入门教程:
- Oracle官方的tutorial:http://docs.oracle.com/javaee/7/tutorial/doc/jaxrs.htm
- IBM developerWorks中国站文章:http://www.ibm.com/developerworks/cn/java/j-lo-jaxrs/
更多的资料请自行google或者百度一下。就学习JAX-RS来说,一般主要掌握其各种annotation的用法即可。
注意:dubbo是基于JAX-RS 2.0版本的,有时候需要注意一下资料或REST实现所涉及的版本。
REST服务提供端详解
下面我们扩充“快速入门”中的UserService,进一步展示在dubbo中REST服务提供端的开发要点。
HTTP POST/GET的实现
REST服务中虽然建议使用HTTP协议中四种标准方法POST、DELETE、PUT、GET来分别实现常见的“增删改查”,但实际中,我们一般情况直接用POST来实现“增改”,GET来实现“删查”即可(DELETE和PUT甚至会被一些防火墙阻挡)。
前面已经简单演示了POST的实现,在此,我们为UserService添加一个获取注册用户资料的功能,来演示GET的实现。
这个功能就是要实现客户端通过访问如下不同URL来获取不同ID的用户资料:
http://localhost:8080/users/1001
http://localhost:8080/users/1002
http://localhost:8080/users/1003
当然,也可以通过其他形式的URL来访问不同ID的用户资料,例如:
http://localhost:8080/users/load?id=1001
JAX-RS本身可以支持所有这些形式。但是上面那种在URL路径中包含查询参数的形式(http://localhost:8080/users/1001) 更符合REST的一般习惯,所以更推荐大家来使用。下面我们就为UserService添加一个getUser()方法来实现这种形式的URL访问:
@GET
@Path("{id : \\d+}")
@Produces({MediaType.APPLICATION_JSON})
public User getUser(@PathParam("id") Long id) {
// ...
}
@GET:指定用HTTP GET方法访问
@Path("{id : \d+}"):根据上面的功能需求,访问getUser()的URL应当是“http://localhost:8080/users/ + 任意数字",并且这个数字要被做为参数传入getUser()方法。 这里的annotation配置中,@Path中间的{id: xxx}指定URL相对路径中包含了名为id参数,而它的值也将被自动传递给下面用@PathParam("id")修饰的方法参数id。{id:后面紧跟的\d+是一个正则表达式,指定了id参数必须是数字。
@Produces({MediaType.APPLICATION_JSON}):指定getUser()输出JSON格式的数据。框架会自动将User对象序列化为JSON数据。
Annotation放在接口类还是实现类
在Dubbo中开发REST服务主要都是通过JAX-RS的annotation来完成配置的,在上面的示例中,我们都是将annotation放在服务的实现类中。但其实,我们完全也可以将annotation放到服务的接口上,这两种方式是完全等价的,例如:
@Path("users")
public interface UserService {
@GET
@Path("{id : \\d+}")
@Produces({MediaType.APPLICATION_JSON})
User getUser(@PathParam("id") Long id);
}
在一般应用中,我们建议将annotation放到服务实现类,这样annotation和java实现代码位置更接近,更便于开发和维护。另外更重要的是,我们一般倾向于避免对接口的污染,保持接口的纯净性和广泛适用性。
但是,如后文所述,如果我们要用dubbo直接开发的消费端来访问此服务,则annotation必须放到接口上。
如果接口和实现类都同时添加了annotation,则实现类的annotation配置会生效,接口上的annotation被直接忽略。
JSON、XML等多数据格式的支持
在dubbo中开发的REST服务可以同时支持传输多种格式的数据,以给客户端提供最大的灵活性。其中我们目前对最常用的JSON和XML格式特别添加了额外的功能。
比如,我们要让上例中的getUser()方法支持分别返回JSON和XML格式的数据,只需要在annotation中同时包含两种格式即可: