嗨,老铁,欢迎来到我的博客!

如果觉得我的内容还不错的话,可以关注下我在 segmentfault.com 上的直播。我主要从事 PHP 和 Java 方面的开发,《深入 PHP 内核》作者之一。

[视频直播] PHP 进阶之路 - 亿级 pv 网站架构的技术细节与套路 直播中我将毫无保留的分享我这六年的全部工作经验和踩坑的故事,以及会穿插着一些面试中的 考点难点加分点

周梦康 发表于 2016-01-03 51956 次浏览 标签 : REST

一些常见的误解

不要以为 RESTful Api  就是设计得像便于 SEO 的伪静态,例如一个 Api 的 URL 类似于 http://xxx.com/blog/1 ,我们可以通过浏览器访问该 URL 而读取文章,但是这并不代表着它就是 RESTful Api 。

也不要认为URL 里有 queryString 就不是 RESTful Api ,例如 http://xxx.com/users/?page=10&page_size=30

更不要认为 HTTP + JSON 就是 RESTful ApI 了。

REST 和 HTTP/1.1 

Roy Fielding (Apache HTTP 服务器的核心开发者,Apache 软件基金会的合作创始人,HTTP/1.1 协议专家组的负责人)总结了一套理论框架,然后使用这套理论框架中的指导原则,来指导HTTP/1.1协议的设计方向。后来他在其的博士学位论文 Architectural Styles and the Design of Network-based Software Architectures 中更为系统、严谨地阐述了这套理论框架,并且使用这套理论框架推导出了一种新的架构风格,并且为这种架构风格取了一个令人轻松愉快的名字 REST。

想必通过这个你已经明白了 REST 和 HTTP/1.1 的密不可分的关系了吧。HTTP/1.1 的很多特性就是 REST 的特性,比如连接的无状态性。还有后面说的 REST 五大特性都和 HTTP/1.1 协议密不可分。

RESTful Api 与 SOAP Web API 在 URL 形式上的对比

从设计一个删除评论的 api 说起 

我们可以这样设计成类似于:

http://mengkang.net/?method=comment.del&id=x 

http://mengkang.net/comment/del/id/x 

或者其他形式的 url。

而 RESTful Api 则是:

[DELETE] http://mengkang.net/comments/1 


我们对比可以发现①和② URL 中,都有del的动作指示。

SOAP Web API采用RPC风格,它采用面向功能的架构,所以我们在设计SOAP Web API的时候首相考虑的是应高提供怎样的功能(或者操作)。

而 RESTful Api 是面向资源的架构。是查询、新增、修改、删除,都该资源无关。

所以我们在③ URL 中没有看到del的关键字,对比最为明显。

进一步认识 RESTful Api 

我们知道 URL 全称为“统一资源定位符(Uniform Resource Locator)”,用于描述 Web 资源所在的位置。RESTful Api 是以 HTTP 协议为强烈依托的,将类似于②这种以功能为主导的URL风格舍弃,还原 URL 的本质,它的宗旨就是一个 URL 就应该是一个资源,不能包含任何动作。

[POST]     http://mengkang.net/users   // 新增
[GET]      http://mengkang.net/users/1 // 查询
[PATCH]    http://mengkang.net/users/1 // 更新
[PUT]      http://mengkang.net/users/1 // 覆盖,全部更新
[DELETE]   http://mengkang.net/users/1 // 删除

PUTPATCH的功能都可以代表更新,但略有不同,PUT大多时候表示更新该资源的全部信息,而PATCH则更新部分信息。

PUTPOST又一些功能的重叠,都可以是新建一个资源,POST时,新建资源的地址是由服务器返回给客户端的。也就是说客户端在发送POST请求资源之前还无法预知该资源的地址,这在我们的 Api 开发中非常常见,新建一个帖子,新建一条评论,都如此。

而资源的地址是客户端预先知道的情况则比较少。也有人如此设计而使用PUT,比如需要对 github 上的某人的项目 star ,则可能会设计成:http://github.com/xxx/zhoumengkang/projectname/star 这里把“对这个项目已经点赞过”看成了一个资源,那么就可以用PUT,因为要删除这个资源时,还是访问这个 URL。

关于这些功能上有交集的地方,可能后面会有一些更加标准的规范或者协议吧。

最后说下HEADOPTIONSHEAD请求的是资源的元数据,比如一张照片,的元数据则可能包含了,照片拍摄的设备,地点,时间等。服务器一般将对应资源的元数据置于响应的报头集合返回给客户端,这样的响应一般不具有主体部分。OPTIONS则是发送一种“探测”请求以确定针对某个目标地址的请求必须具有怎样的约束(比如应该采用怎样的HTTP方法以及自定义的请求报头),然后根据其约束发送真正的请求。

关于过滤筛选,排序和 token 等 query string 是支持使用,和唯一资源的概念并不冲突。

我所理解的无状态性和唯一资源对 Api Url 的设计指导

举一个实际的例子,用户黑名单的 CURD。我们可是设计成

[GET] 		/blacklist
[PUT]		/blacklist/{id}	#把 id 加入到当前授权用户的黑名单中
[DELETE] 	/blacklist/{id}

如上的 api 设计中,首先如上的 url 设计中,通过授权码中获取登录用户 uid。则是有状态的了,其次因为所有的用户访问的资源都是同一个地址,这与唯一资源的概念是相违背的。如果是无状态的,那么就与唯一资源的概念相吻合了。(这只是 restful 所建议的,实际是否需要完全遵守视情况而定)

[GET] 		/user/{uid}/blacklist
[PUT]		/user/{uid}/blacklist/{id}	#把 id 加入到当前授权用户的黑名单中
[DELETE] 	/user/{uid}/blacklist/{id}

REST的五大特性

  1. 资源(Resource)

  2. 资源的表述(Representation)

  3. 状态转移(State Transfer)

  4. 统一接口(Uniform Interface)

  5. 超文本驱动(Hypertext Driven)

资源的概念是 RESTful 里面最重要的一个概念,很容易被我们忽视和误解,所以就重点阐述了这一特性。

而其他特性,我们日常开发应该都是遵守的,就不再展开说了,需要了解的可以看我的这篇笔记 REST的五个特性

Server 端代码的基本设计思想

按照 RESTful Api 的规范,严格来说根据endpoint找到在 server 端映射成一个资源对象,例如通过 http://mengkang.net/users/1 找到UserResource对象

而在这个对象里对应着该资源支持的 Http 协议的方法。如果一个对象支持7种方式的请求,则类似于:

public class UserResource {

    private User user;

    public UserResource() {

    }

    public UserResource(int id) {
        //从数据查询处该用户的数据
        String name = "xxx";
        short age = 23;
        user = new User(id,name,age);
    }

    /**
     * 获取单个用户
     * 从 new UserResource(int) 开始
     * @return
     */
    public User get(){
        return user;
    }

    /**
     * 覆盖用户的全部信息
     * 从 new UserResource(int) 开始
     * @return
     */
    public boolean put(){
        return true;
    }

    /**
     * 只更新用户的部分信息
     * 从 new UserResource(int) 开始
     * @return
     */
    public boolean patch(){
        return true;
    }

    /**
     * 创建一个 UserResource
     * @return
     */
    public int post(){
        return 1;
    }

    /**
     * 删除用户
     * @return
     */
    public boolean delete(){
        return true;
    }

}

如果有关注的 api 那么对于该用户的 star 操作则应新建一个UserStarResource资源对象,因为每个资源最多只有这7中方法,构造方法除外,而不能有其他的相关的动作方法。

假如我们规定 URL 为 http://mengkang.net/user/1/star 表示是访问者对 id 为 1 的这个用户的 star 状态的一个资源。(当前访问者的信息通过 query string 传递 auth token 的形式获取)

public class UserStarResource {

    public boolean get(){
        return true;
    }

    public boolean post(){
        return true;
    }
    public boolean delete(){
        return true;
    }
}

REST 状态码

理论上来说我们应该以 Http response status code 作为客户端的标准,而不是在 Http body 体里面定义。这样客户端的能够更快速的获取服务端的响应状态码。

但是由于国内某些网络商会劫持状态码非200的请求,跳转到他们的广告地址。所以大家还是考虑国内的实际情况。

REST 架构风格约束

  1. 客户-服务器(Client-Server)通信只能由客户端单方面发起,表现为请求-响应的形式。

  2. 无状态(Stateless)通信的会话状态(Session State)应该全部由客户端负责维护。

  3. 缓存(Cache)响应内容可以在通信链的某处被缓存,以改善网络效率。

  4. 统一接口(Uniform Interface)通信链的组件之间通过统一的接口相互通信,以提高交互的可见性。

  5. 分层系统(Layered System)通过限制组件的行为(即,每个组件只能“看到”与其交互的紧邻层),将架构分解为若干等级的层。

文字比较学院派,仔细一想,想必也发现我们实际工作都已经遵守这五条架构风格了。


短短的一篇文章无法涵盖所有内容,这里推荐下 Github Api, 可以作为大家设计 RSETful Api 的最佳范例。

一个基于 netty 的轻量级的 RESTful Api Server https://github.com/zhoumengkang/netty-restful-server 

更多更详细的信息可以阅读下面两篇文章

http://www.cnblogs.com/artech/p/restful-web-api-02.html

http://www.infoq.com/cn/articles/understanding-restful-style/

书籍推荐

RESTful Web Services Cookbook

嗨,老铁,欢迎来到我的博客!

如果觉得我的内容还不错的话,可以关注下我在 segmentfault.com 上的直播。我主要从事 PHP 和 Java 方面的开发,《深入 PHP 内核》作者之一。

[视频直播] PHP 进阶之路 - 亿级 pv 网站架构的技术细节与套路 直播中我将毫无保留的分享我这六年的全部工作经验和踩坑的故事,以及会穿插着一些面试中的 考点难点加分点

评论列表

回复 holys 2016-01-04 18:33:18
请问: 如何设计一个 发短信给手机,然后校验短信 的RESTful API(两个)。请举例说明 endpoint 以及 method
回复 康哥 2016-01-04 21:13:35
回复holys: 你好同学,说下我的理解,发送短信时是新建验证短信这个资源,可以是[POST] http://mengkang.net/sms/15907244568这样的形式,也可以使用PUT方式,因为该资源的地址是客户端预先就知道的,看文中我也说到了这个情况的举例。
验证的,则可以使用[GET] http://mengkang.net/sms/15907244568 ,Request Parameters 为 code=123456 返回值为是否发送成功。这和我上面举的那个 star 的例子比较类似。
回复 康哥 2016-01-05 10:51:20
今天发现一篇介绍的更加系统详尽的文章,分享给大家 http://mccxj.github.io/blog/20130530_introduce-to-rest.html
回复 Allen 2016-01-06 09:57:42
@康哥 如你文中所讲,Restful通过7个HTTP 谓词GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS 表示动作,而且不应该通过API的URI部分表达动作,那对于一个资源来说,上述7个动作以外的动作应该怎样表达?比如电商系统的下达销售订单这个请求,API应该怎么设计?是salesOrder/booking/{id}这样?还是通过Post请求将订单状态更新。如果是后者相当于将后台业务实现暴露到前端来处理,是不是必须在领域模型SalesOrder之上设计一个用于发布服务的SalesOrderResource通过它的状态改变来转换成SalesOrder的动作?这里我比较迷惑也是学习RESTFulApi一直以来的一个困惑,能否麻烦你帮我解释一下?谢谢!
回复 康哥 2016-01-06 10:55:29
回复Allen: 你好同学,不可能存在这7个动作之外的动作,所有的动作都可以抽象为 CURD,我估计你客户端的开发吧?你说的下达销售订单,我想知道这个订单之前存在吗?如果不存在,而且需要用一个主键id来索引,那么这就是创建,通过[POST] http://xxx.com/salesOrder/booking/来生成订单资源。下次需要更新订单的状态,那么就是PATCH[PATCH] http://xxx.com/salesOrder/booking/:id
后面第二个问题没太听明白,大概说下,关于权限控制,应该在 api 封装做判断的。比如你改变非自己的订单状态,那么后端肯定会做权限判断呀。
回复 Allen 2016-01-06 11:47:29
回复康哥: 非常感谢你的回复。我了解你的意思了。我说的下达订单可以理解成类似京东网站上提交订单这个动作。订单是在购物车页面点击“去结算”已经生成了,此时应该使用[POST] http://xxx.com/salesOrder 创建一个新订单,在“结算”页面的提交订单动作就应该是更新订单状态,[PATCH] http://xxx.com/salesOrder/{id}消息内容通过http body传递。“下达订单”这个动作并不只是修改一下订单的状态,还要考虑出库,记账等一列后续处理,这些处理应该不能在前端做。但也不应该SalesOrder实体类的setter上,由谁来承载这个动作呢?如果是这个SalesOrderResource.Post()来处理,需要大量的判断,不太合适,按照标准SalesOrderResource 不应该出现7个方法以外的方法,换句话说,没有一个单独的叫做Booing(SalesOrder)的方法来承载这段逻辑的话,这段逻辑应该放在哪里比较合适呢?
回复 康哥 2016-01-06 14:44:42
回复Allen: 同学你好,我大概明白你的意思了,RESTful 里的资源还是对客户端来说是一个资源,也就是 Api。RESTful 也是需要遵循分层系统(Layered System)通过限制组件的行为,将架构分解为若干等级的层。不是所有的操作都是在资源对象里完成的,资源对象不是服务的终点,只是 CS 的连接处。
即客户端只能创建订单,货币的交易和出库行为则是下面一层的工作。实际已经是好几个服务或者系统了。一个订单衍生出的出库货币交易系统。多个系统或者服务之间的分布式调用,还是保持原来的方式,比如 RPC。不知道对你有帮助没。
回复 康哥 2016-01-08 12:27:39
来自HeroKu的HTTP API 设计指南(中文版) http://www.kancloud.cn/thinkphp/http-api-design/31119
规范、清晰
回复 德克斯特的实验室 2016-01-08 17:04:33
请问分页RESTful API应该怎么设计呢?类似的多参数API呢?
回复 康哥 2016-01-08 17:44:04
回复德克斯特的实验室: hi,我在博客中只提了一句,抱歉,这属于资源返回信息的过滤,还是正常使用query string的,比如 github 的这个 api:https://developer.github.com/v3/repos/#list-your-repositories
比如资源的排序(sort),分页(page或者offset+limit)都和资源的概念不冲突。
回复 Allen 2016-01-15 10:45:43
回复德克斯特的实验室: 之前了解过OData协议,也是一种基于RESTFul的扩展协议,里面提供了skip,take api用于分页查询.
回复 Aodirary 2016-07-05 22:17:28
Hello, I learned a lot from your side, however, I still have question about creating RESTful API, can I friend you on wechat to talk about more? thanks
回复 康哥 2016-08-17 21:28:26
回复Aodirary: 我英语不6~
回复 ma-spring 2018-03-14 20:19:12
学习了,测试REST API工具也有很多比如Wisdom RESTClient 可以生成精美的测试报告和API文档
Wisdom RESTClient
https://github.com/Wisdom-Projects/rest-client

谢谢作者的分享!赞一个!
回复 周梦康 2018-03-15 09:37:29
回复ma-spring: 哦,我一般用ide 里自带的 test restful web srvice,感谢分享~
回复 周梦康 2018-03-15 09:38:54
回复ma-spring: 哦,看了下哈,和 postman 比较像