RESTful API 介绍,设计

一:RESTful介绍

在互联网发展过程中,最开始是以html静态网页展示内容,url的表现形式一般为 http://www.example.com/getInfo.html;后来随着需求不断提高以及为了应对这种需求,出现了动态网页技术,在动态网页技术中http请求的url形式一般为 http://www.example.com/getInfo.java?id=1&name=tom ,或者是重写后的url形式http://www.example.com/getInfo/id/1/name/tom 等这种形式。其实这种url的形式是不是有点RESTful的样子了,但是当时还没有这种思想。

在2000年的时候,Roy Thomas Fielding博士在他的博士论文中提出了一种万维网的软件架构风格-REST(全称:Representational State Transfer表现层状态转换),目的是便于在不同软件/程序中(例如互联网)中互相传递信息。这种表现层状态转换是基于HTTP协议之上而确定的一组约束和属性,是一种设计提供万维网服务的软件架构风格。

题外话:他也是HTTP协议(1.0版和1.1版)的主要起草者之一、 Apache服务器软件的作者之一、Apache基金会的第一任主席。

符合或兼容于这种架构风格(简称为 REST 或 RESTful)的网络服务,允许客户端发出以统一资源标识符访问和操作网络资源的请求,而与预先定义好的无状态操作集一致化。因此表现层状态转换提供了在互联网络的计算系统之间,彼此资源可交互使用的协作性质。(维基百科)

二:怎么理解RESTful

上面的说明比较学术,其实我抓住几个重点词汇:网络资源,资源标识符号,无状态,表现层,状态转换。

网络资源:就是我们需要获取的数据,比如媒体,文本,图片,视频等等都可以看作是一种资源。

表现层:“资源”是一种信息实体,它可以有多种外在表现形式,比如上面网络资源说明。我们把“资源”具体呈现出来的形式,叫做它的“表现层(Representation)”

资源标识符号:定位资源用的。就像门牌号码可以定位到具体的房间。怎么定位到你需要的资源?就需要用一个资源标识符-URI(统一资源定位符)。每种资源对应一个特定的URI。要获取资源,访问URI就可以,URI是每一个资源的独一无二的地址。就像在网络世界中,IP地址可以定位到独一无二的一台服务器一样。

无状态:我们访问网站时,就是客户端和服务端的一次交互,这就涉及到数据和状态的变化。无状态就是每次访问时,服务端不会保留客户端的信息,每一次客户端访问服务端都是一次全新的访问。HTTP协议就是一种无状态协议。

状态转换:HTTP协议是一个无状态的协议,意思是说所有的状态都是保存在服务端。那么客户端要操作服务端,必须要通过某种手段,让服务端发生“状态转换”(State Transfer)。而这种转换是建立在表现层之上的,就是“表现层状态转换”。
HTTP协议是怎么做到的?就是通过HTTP协议的方法METHOD来操作:GET、PUT、DELETE、POST、PATCH。它们分别对应的四种基本操作时:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,PATCH用来更新部分资源。

三:RESTful 架构

  1. 有一种资源,每种资源有唯一的URI来标识。
  2. 对这种资源进行操作,实现状态转换。通常用HTTP的方法来操作。
  3. 谁来操作谁呢?客户端对服务度资源的操作

具备上面的这几种要素,就是RESTful架构。RESTful是一种设计风格。

四:RESTful API实践

API设计

RESTful API 是基于REST架构设计理念之下利用http协议来描述接口操作接口。
这种API设计主要描述几个部分:

  1. URL的设计,URL里面当然包含了资源定位符。
  2. Request参数
  3. Response 返回数据

URL Request

一个URL设计,需要简单,易懂,看到URL就能知道这个URL的意思,也就是自解释,自描述。
看看http url的设计,分为协议,域名,path,method,参数。
我们应该可以得到启发,它其实就是根据http协议而来。
比如获取雇员url:GET /users, 动作方法+path

  • 1.常用的动作方法:
动作 说明
GET 读取 Read
POST 新建 Create
PUT 更新 Update
PATCH 更新 Update ,一般为部分更新
DELETE 删除 Delete

比如 GET 操作

GET http://example.com/users 获取所有的员工信息

GET http://example.com/users/23 获取员工id=23的信息

  • 2.其他操作:
方法/资源 http://example.com/users http://example.com/users/23
GET 获取所有的用户信息 获取用户id为23的信息
POST 在所有用户信息中创建/追加一个新的用户 在id=23号用户新增信息
PUT 更新该组用户信息 更新id=23号的用户信息
DELETE 删除所有的用户信息 删除id=23号用户信息
PATCH 更新所有用户部分信息 更新id=23号用户信息

常见错误:
比如新建一个用户:POST http://example.com/users/22 ,这种形式是错误的,应该用
POST http://example.com/users

  • 3. 用复数形式描叙资源

上面的 example.com/users,users复数形式,而且是名词。因为常见的操作是一个集合。所以一般建议用复数形式。不推荐使用 example.com/user/1这种形式,而是推荐使用example.com/users/1 复数这种形式。

错误的表示形式:

GET http://example.com/getusers - 不要使用这种带有get动词的形式

getusers 前面带一个get是多余的,因为http请求的动作就是GET,不用写多余的动作了。如果这样写的话,很容易造成大家理解api不统一。有可能前端和后端人员各自为政,用不同的动词来操作url,造成大家开发理解的不统一。

类似的还有:

  • /createUsers
  • /updateUserInfo
  • /deleteUser?id=2

统一标准:前后端人员统一用http规定的几个动作来进行url相关的操作。不需要在url后面加多余的动作名词。统一用一个标准,这样前后端人员理解起来也能统一思想,便于大家协作开发。

  • 4. url的层级

在操作资源的时候,最简单的操作是CURD,但是资源之间相互关联也很常见,资源嵌套也很常见,比如查找id为10的用户所有文章,这个URL如何设计?
一种设计:

GET /users/10/articles

获取article的id=3文章

GET /users/10/articles/3

有人推荐这种方案,这种形式的 URI 一定程度上描述了 user 与 article 之间的一对多关系,但同时,我们就不太能够分清当前端点返回的数据到底是 user 类型还是 article 类型。
这种设计语义不明确,也不利于扩展。

另一种设计,除了第一级,其他级别都用查询字符串表达:

GET /articles?user_id=10
推荐使用这种方案。
比如用户发布文章:GET /atticles?user_id=10&published=true

  • 5. 域名的设计

比如我们用独立的api域名:api.example.com
或者在域名后面加 api,这种形式 example.com/api/users
这样有利于以后我们迁移,扩展。

  • 6. 版本号
    考虑到系统的变化,迭代,和兼容性,在API中引入版本号。

是否在URL中加入版本号,有一个争论。
有的认为不要在URL中加入版本号,可以在请求头信息中加入。因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URI。

Accept: application/example.com+json; version=3

有的则认为在URL中放入版本号,操作简单。在api变更时候,更容易操作些:

GET api.example.com/v1/users

根据个人来选择,一般推荐在url中写上版本号。

筛选过滤复杂参数-复杂业务场景

对可选的、复杂的参数,使用查询字符串querystring(?)

比如说有一些复杂的业务场景,比如对获取的资源在进行条件筛选,还有排序,分页等等
对于这些场景,RESTful api又怎么设计? 用querystring

  • 过滤

比如某一个用户已经发表的文章
GET /users/10/posts?state=published
GET /users/10/posts?published=true

  • 分页

比如用户分页
GET /users?page=1&page_size=10

在加条件,某一个用户已经发表的文章太多,需要分页
GET /users/10/posts?published=true&page=2&page_size=10

  • 多字段,不同排序

针对多个字段,不同的排序:
搜索用户,并按照注册时间升序、活跃度降序
GET /users?q=key&sort=create_time_asc,liveness_desc

  • 显示某些字段

GET /users?fields=id,title,desc;

  • 搜索分页排序

github的搜索某一用户分页排序的api
https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
查询的条件q,如果有需要还有分页page,per_page,排序sort,order。

github某个用户的repo,类型,分页,排序
https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",

github的api查看地址:https://api.github.com/

一些非资源的特殊请求

有时API调用并不涉及资源(如计算,翻译或转换)。例:

GET /translate?from=de_DE&to=en_US&text=Hallo
GET /calculate?para2=23&para2=432

在这种情况下,API响应不会返回任何资源。而是执行一个操作并将结果返回给客户端。因此,您应该在URL中使用动词而不是名词,来清楚的区分资源请求和非资源请求。

返回格式和状态码统一

比如返回一个统一json格式:

{
   code: 200,               //返回的状态码
   message: "200 OK",       //返回信息简单说明
   data: {                  //返回的数据,如果返回时错误,那么具体内容就是错误信息
       
   }
}

状态码信息

1xx:相关信息
2xx:操作成功
3xx:重定向
4xx:客户端错误
5xx:服务器错误

2xx 状态码

200 表示操作成功,但是不同的方法可以返回更精确的状态码

GET: 200 OK
POST: 201 Created
PUT: 200 OK
PATCH: 200 OK
DELETE: 204 No Content

使用状态码 202 有时候会比 使用状态啊吗 201 是更好的选择,状态码 202 的意思是:服务端已接收到了请求,但是还没有创建任何资源,但结果一切正常

我分享两种特别适合使用 202 Accepted 状态码的业务场景:

  • 如果资源是经过位于将来一系列处理流程之后才创建的,比如当某项作业完成时
  • 如果资源已经存在,但这是理想状态,因此不应该被识别为一个错误时
3xx 状态码

重定向,需要进一步的操作以完成请求

301 Moved Permanently:永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 Found:已找到。请求的资源已经在其他临时位置找到,并应该在响应头中返回该位置。只有GET和HEAD请求应该重定向到该位置。
304 Not Modified:未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源

4xx 状态码

4xx 状态码表示客户端的错误,主要有以下几种:

400 Bad Request:服务器不理解客户端的请求,未做任何处理
401 Unauthorized:用户未提供身份验证凭据,或者没有通过身份验证
403 Forbidden:用户通过了身份验证,但是不具有访问资源所需的权限
404 Not Found:所请求的资源不存在,或不可用
405 Method Not Allowed: 该http方法不被允许。
406 Not Acceptable :GET, 用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 gone : 这个url对应的资源现在不可用。
415 Unsupported Media Type:客户端要求的返回格式不支持。比如,API 只能返回 JSON 格式,但是客户端要求返回 XML 格式
429 Too Many Requests : 请求太多 -由于速率限制而拒绝请求时

5xx 状态码

5xx 状态码表示服务端错误

500 Internal Server Error:客户端请求有效,服务器处理时发生了意外
502 Bad Gateway:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应
503 Service Unavailable:服务器无法处理请求,一般用于网站维护状态
505 HTTP Version Not Supported: 服务器不支持,或者拒绝支持在请求中使用的HTTP版本

Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

{
  "link": {
      "rel":   "collection https://www.example.com/users",
      "href":  "https://api.example.com/users",
      "title": "List of users",
      "type":  "application/vnd.yourformat+json"
    }
}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

// https://api.github.com/

{
      "current_user_url": "https://api.github.com/user",
      "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
      "authorizations_url": "https://api.github.com/authorizations",
      "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}",
      "commit_search_url": "https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}",
      "emails_url": "https://api.github.com/user/emails",
      "emojis_url": "https://api.github.com/emojis",
  
        ... ...
  }

想获取当前用户信息,访问 https://api.github.com/user,会返回

// https://api.github.com/user

{
   "message": "Requires authentication",
   "documentation_url": "https://developer.github.com/v3/users/#get-the-authenticated-user"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

上面介绍的,其实是martin fowler 划分RESTful api4个等级最高的一个等级:
他画的api 4个等级图:
image.png

(from: https://martinfowler.com/articles/richardsonMaturityModel.html)

总结URL上的一些设计技巧

  • 使用 / 来表示资源层级关系
  • 使用 ? 来过滤资源
  • 使用 ,; 来表示资源同级层关系
  • 使用 _- 来表示更友好的URL形式

一些设计也可以参考github的v3: https://developer.github.com/v3/
或者一些其他大厂twitter, https://developer.twitter.com/en
google等等

参考

posted @ 2019-11-23 15:45  九卷  阅读(2356)  评论(0编辑  收藏  举报