今天我们来一起学习和了解rest的相关知识,做后台程序的或多说少都听说过甚至使用过rest技术,但很多同学可能都没有真正了解什么是rest,我通过查询资料,copy总结出了一篇文章,这里我们就一起来学习一下。
一、起源
REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。
Fielding是一个非常重要的人,他是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。
他这样介绍论文的写作目的:
“本文研究计算机科学两大前沿—-软件和网络—-的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。”
(This dissertation explores a junction on the frontiers of two research disciplines in computer science: software and networking. Software research has long been concerned with the categorization of software designs and the development of design methodologies, but has rarely been able to objectively evaluate the impact of various design choices on system behavior. Networking research, in contrast, is focused on the details of generic communication behavior between systems and improving the performance of particular communication techniques, often ignoring the fact that changing the interaction style of an application can have more impact on performance than the communication protocols used for that interaction. My work is motivated by the desire to understand and evaluate the architectural design of network-based application software through principled use of architectural constraints, thereby obtaining the functional, performance, and social properties desired of an architecture. )
二、名称
Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。我对这个词组的翻译是”表现层状态转化”。
如果一个架构符合REST原则,就称它为RESTful架构。
要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计。
三、资源(Resources)
REST的名称”表现层状态转化”中,省略了主语。”表现层”其实指的是”资源”(Resources)的”表现层”。
所谓”资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
所谓”上网”,就是与互联网上一系列的”资源”互动,调用它的URI。
四、表现层(Representation)
“资源”是一种信息实体,它可以有多种外在表现形式。我们把”资源”具体呈现出来的形式,叫做它的”表现层”(Representation)。
比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的”.html”后缀名是不必要的,因为这个后缀名表示格式,属于”表现层”范畴,而URI应该只代表”资源”的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对”表现层”的描述。
五、状态转化(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生”状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是”表现层状态转化”。
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
六、综述
综合上面的解释,我们总结一下什么是RESTful架构:
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现”表现层状态转化”。
以上是从别人博客引用 阮一峰-理解RESTful架构,此人是业界知名人士,博客质量很高,大家可以长期关注,我关注有很长时间了,此人以前是经济学博士,现在就职于阿里,任前端专家(我了解的八卦,呵呵)
总结:
根据前面的介绍大家应该基本对REST有了一定的了解,现在简单的举例子说明一下,其实rest就是让方法以及参数一起有一定的语义,如下
设计一个根据id获取用户的rest风格的api
http://www.jjshome.com/getuser/778123
根据删除某个类别
http://www.jjshome.com/delete/category/1
这个是csdn博客编辑的url,也有rest的风格
http://write.blog.csdn.net/postedit/49662031
Java平台能够像现在这样强大,很大程度上是因为有了社区的参与,java中有一个对规范进行管理的社区 JCP,所有的新技术都会在先向这里提交申请,JSR(Java规范请求,Java Specification Requests)正式规范文档,描述被提议加入到Java体系中的的规范和技术,然后通过委员会投票(JCP Executive Committee),当提议通过后JCP会提供一个参考实现。
JCP主要就是制定标准,然后提供一个参考实现,然后各大软件开发商就会按照规范提供自己的实现,将软件发布到互联网之后就会有这种对比,一般来讲最好用、最方便的实现就是我们的Java程序员都在使用的软件包。
比如说Servlet规范,我们通常用的是tomcat,因为Apache软件协会提供了tomcat的实现,实现了JSP/Servlet等一系列JavaEE规范,如JNDI、WebSocket等等,其实我们写的Servlet程序只是针对规范写的,并没有针对某个实现,也就是说我们只依赖了接口,谁实现了接口我们的程序就能在哪里运行,比如还有Jetty、JBoss、Weblogic等很多容器都实现了Servlet规范,所有我们的程序也能直接放到这些容器中去运行,包括我们这里讲的Java REST技术,也有标准,下面是相关的JSR定义
339是规范编号,JAX-RS是名称,后面是相关描述
JSR 339: JAX-RS 2.0: The Java API for RESTful Web Services
Java的rest常见实现有 Jersey ,CXF(Apache提供),JBoss的RestEasy, Dubbo的rest使用了RestEasy的实现
rest的使用我们这里主要描述规范中有定义的,不针对特定实现,看一个基本使用
@Path("/users")
public class UserResource {
@GET
@Path("getUser/{username}")
@Produces("text/xml")
public String getUser(@PathParam("username") String userName) {
...
}
}
然后是在web.xml中添加配置,然后只需要配置让rest框架加载这个类就可以了,这里提供的是resteasy的,其他框架也差不多
<context-param>
<param-name>resteasy.scanparam-name>
<param-value>trueparam-value>
context-param>
<listener>
<listener-class>
org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
listener-class>
listener>
<servlet>
<servlet-name>resteasy-servletservlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
servlet-class>
servlet>
<servlet-mapping>
<servlet-name>resteasy-servletservlet-name>
<url-pattern>/rest/*url-pattern>
servlet-mapping>
这就是rest对外提供服务所需要的代码,类似于Spring的component-scan。
写服务类很重要的一点就是获取http的请求参数,包括请求的header、cookie、post的数据等等,我们就来看各种获取参数的方法
@QueryParam用来获取GET请求的参数,如 http://www.jjshome.com/?username=testuser&age=26 ,@DefaultValue() 用于指定默认值
@Path("smooth")
@GET
public Response smooth(
@DefaultValue("2") @QueryParam("step") int step,
@DefaultValue("true") @QueryParam("min-m") boolean hasMin,
@DefaultValue("true") @QueryParam("max-m") boolean hasMax,
@DefaultValue("true") @QueryParam("last-m") boolean hasLast,
@DefaultValue("blue") @QueryParam("min-color") ColorParam minColor,
@DefaultValue("green") @QueryParam("max-color") ColorParam maxColor,
@DefaultValue("red") @QueryParam("last-color") ColorParam lastColor) {
...
}
@PathParam用于将url的一部分绑定到参数中,比如url:http://server:port/login/415546,可以通过以下方式获取
@GET
@Path("login/{zip}")
public String login(@PathParam("zip") String id) {
return "Id is " +id;
}
@HeaderParam用于获取http头信息,如获取浏览器信息
@GET
public String callService(@HeaderParam("User-Agent") String whichBrowser) {
...
}
获取Cookie的值
@GET
public String callService(@CookieParam("sessionid") String sessionid) {
...
}
@MatrixParam
@MatrixParam参数,这种参数平时用的不多,单rest提供了直接获取值得参数,看如下url:http://server:port/login;name=francesco;surname=marchioni
@GET
public String callService(@MatrixParam("name") String name,
@MatrixParam("surname") String surname) {
...
}
@BeanParam @FormParam
以上获取参数适合参数较少的方法获取参数,如果参数比较多全部都写在参数上,代码就很难看了,来看一个示例
@POST
public void post(@BeanParam MyBeanParam beanParam, String entity) {
final String pathParam = beanParam.getPathParam(); // contains injected path parameter "p"
...
}
把所有参数都放到一个对象里,下面是MyBeanParam的声明,可以在MyBeanParam中使用前面的每一种注解获取参数
public class MyBeanParam {
@PathParam("p")
private String pathParam;
@MatrixParam("m")
@Encoded
@DefaultValue("default")
private String matrixParam;
@HeaderParam("header")
private String headerParam;
private String queryParam;
public MyBeanParam(@QueryParam("q") String queryParam) {
this.queryParam = queryParam;
}
public String getPathParam() {
return pathParam;
}
...
}
@Context注解
可以用@Context属性来将这些对象注入到属性中
javax.ws.rs.core.HttpHeaders
javax.ws.rs.core.UriInfo
javax.ws.rs.core.Request
javax.servlet.HttpServletRequest
javax.servlet.HttpServletResponse
javax.servlet.ServletConfig
javax.servlet.ServletContext
javax.ws.rs.core.SecurityContext
过滤器
一共有两种过滤器
ContainerRequestFilter:方法调用之前调用
ContainerResponseFilter :方法调用之后调用
这里是一个权限验证的拦截器,实现ContainerRequestFilter
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
public class AuthorizationRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
final SecurityContext securityContext =
requestContext.getSecurityContext();
if (securityContext == null ||
!securityContext.isUserInRole("privileged")) {
requestContext.abortWith(Response
.status(Response.Status.UNAUTHORIZED)
.entity("User cannot access the resource.")
.build());
}
}
}
一个为所有Response添加http头信息的ContainerResponseFilter
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Response;
public class PoweredByResponseFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-Powered-By", "baidu webengin :-)");
}
}
现在SpringMVC非常流行,可能大家都使用了rest,类似于前面的那种方式,springMVC并没有实现JSR-303(java rest标准),只是提供了rest风格的使用方式,其实springMVC的rest也做的很好用了,只是没有那么多的接口,比如@PathVariable ,接口的全路径是org.springframework.web.bind.annotation.PathVariable,这些都是Spring自己提供的实现
关于Dubbo和Dubbox
dubbo很早就就没有维护了,版本只到2.5.3就停止了,团队也早就解散了,根据我查的一些资料来看,阿里内部并没有大规模的使用dubbo,而是继续使用之前的分布式技术,没有大规模的用dubbo并不是代表dubbo不好,因为大规模切换使用的技术成本是很大的,如果不是有安全问题或者新技术相对于老技术并没有显著的优势,企业是不会随便切换的,尤其是考虑到系统的稳定性。
dubbox是对dubbo的维护版本,也添加了一些新的特性,主要是由当当网的架构师沈理来维护,最主要的就是添加了rest的支持
沈理:目前在当当网的架构部和技术委员会担任架构师,主要负责当当网的SOA实施(即服务化)以及分布式服务框架的开发。
我的想法
最近在项目中也使用了dubbox提供的rest来进行webservice的开发,因为我的service是既要给dubbo调用,同时也要给http客户端调用,于是就设计了一个service,以不同的协议来暴露,分别是 dubbo和rest,如下
"acctRegisterService" class="com.xx.xx.AcctRegisterServiceImpl" />
interface="com.xx.xx.IAcctRegisterService" ref="acctRegisterService" protocol="dubbo" />
interface="com.jjshome.umc.api.client.acct.IAcctRegisterService" ref="acctRegisterService" protocol="rest" />
这样dubbo和http接口都可以同时调用,这样做确实减少了一些工作量,否则需要同时维护两个业务相同的service,如果这样使用,dubbox提供的rest还有点意义,接下来我们来看另一种场景
如果你提供的服务只需要提供http接口,如果使用dubbox提供的rest,我认为是没必要的
我们来看一下使用dubbox的rest的优缺点
缺点:如果要以dubbo形式暴露服务,必须实现接口
缺点:dubbox是基于resteasy的,不能换rest框架
缺点:使用的时候发现dubbox的rest还有很多未知问题,而且文档很少,(已有的文档都是rest框架提供的使用文档,而出现问题则都是dubbox和resteasy集成时的问题,基本上要靠看源码才能解决)
优点:可以同时提供dubbo服务和http服务
优点:可以通过dubbo的注册中心查看到部署的rest服务
总结:如果仅仅是需要暴露http接口,直接使用 rest框架更直接,更方便,如果是想同时暴露dubbo和rest服务,则可以考虑用dubbox提供的rest