加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

RESTFul API 设计规范

发布时间:2020-12-14 02:11:58 所属栏目:百科 来源:网络整理
导读:0. API设计满足关键点 API应当基于 web 标准来设计 API应当对开发者友好并且便于在浏览器地址栏中浏览和探索 API应当是简单、直观和一致的,使它用起来方便和舒适 API应当是高效的,同时要维持和其他需求之间的平衡 1. API命名 1.1 根地址 好的RESTful API要

0. API设计满足关键点

  1. API应当基于 web 标准来设计
  2. API应当对开发者友好并且便于在浏览器地址栏中浏览和探索
  3. API应当是简单、直观和一致的,使它用起来方便和舒适
  4. API应当是高效的,同时要维持和其他需求之间的平衡

1. API命名

1.1 根地址

好的RESTful API要基于HTTPS来发布 API规模不大时,在域名后面增加 api 目录,如:https://www.trawe.cn/api/ API规模很大时,使用以api开头的二级域名,如:https://api.trawe.cn/

1.2 版本问题

  1. 新版本尽量对旧版本作兼容

  2. 版本信息放在URL中

https://api.trawe.cn/v1.2/users/123
  1. 协议报文中增加version字段
{
    version: "1.0",.... ....
}
  1. HTTP Header中增加版本信息
使用已的HTTPHeader:Accept Header:Accept: application/json+v1.2
自定义 Header:             X-Api-Version: 1.2

1.3 端点设计原则

1) 命名

  1. CRUD操作一律使用名词,不使用动词
  2. url一律使用小写字母
  3. url命名方式不使用 camel方式,采用 - 连接两个单词,如:app-setups,而不是appSetups
  4. 请求参数命名方式使用 下划线 “_” 连接两个单词(Javascript规范),如:user_name,而不是userName或user-name
  5. 在url中不要出现 get / add / delete / put / modify / update 等动词,使用HTTP Method来替代

2) 无论对于单个资源还是集合,名词都使用复数形式,这样便于风格的统一

GET  /tickets      # 获取 tickets 列表
GET  /tickets/12     # 获取一个单独的 ticket
POST /tickets        # 创建一个新的 ticket
PUT  /tickets/12         # 更新 ticket #12
PATCH /tickets/12     # 部分更新 ticket #12
DELETE /tickets/12   #  删除 ticket #12
GET /tickets/12/messages     #  获取ticket #12下的消息列表
GET /tickets/12/messages/5     #  获取ticket #12下的编号为5的消息
POST /tickets/12/messages     #  为ticket #12创建一个新消息
PUT /tickets/12/messages/5     #  更新ticket #12下的编号为5的消息
PATCH /tickets/12/messages/5     #  部分更新ticket #12下的编号为5的消息
DELETE /tickets/12/messages/5    #  删除ticket #12下的编号为5的消息

3) 对于非CRUD的操作 有很非CRUD服务,可以把这些服务看成资源,计算的结果是资源的presentation,按服务属性选择合适的HTTP方法。

1. 重新构造这个Action,使得它像一个资源的操作。
    这种方法在Action不包含参数的情况下可以奏效。例如一个有效的action可以映射成布尔类型field,并且可以通过PATCH更新资源。
2. 利用RESTful原则像处理子资源一样处理它。
    例如:Github的API让你通过PUT /gists/:id/star 来 star a gist ,而通过DELETE /gists/:id/star来进行 unstar 。
3. 有时候你实在是没有办法将Action映射到任何有意义的RESTful结构。
    例如:多资源搜索没办法真正地映射到任何一个资源接入点。这种情况,/search 将非常有意义,虽然它不是一个名词,但是这样做没有问题,只需要从API消费者的角度做正确的事,并确保所做的一切都用文档清晰记录下来了即可。

4) 查询过滤 过滤: 对每一个字段使用一个唯一查询参数,就可以实现过滤。 例如: 当通过 /tickets 终端来请求一个票据列表时,我们需要增加一些限定来查询那些在售的票。可以使用 GET /tickets?state=open 这样的请求来实现。这里“state”是一个实现了过滤功能的查询参数。

对于常用的查询,有以下两种处理:

  1. 可以单独将查询包装为一个独立的API 如:GET /trades?status=closed&sort=created,desc 可以包装GET /trades/recently-closed
  2. 查询结果标签化 将经常使用的、复杂的查询标签化,降低维护成本。如:GET /trades?status=closed&sort=created,desc 可以标签化为 GET /trades#recently-closed

排序: 跟过滤类似,使用排序参数字段来描述排序的规则。 为适应复杂排序需求,让排序参数采取逗号分隔的字段列表的形式,每一个字段前都可能有一个负号来表示按降序排序。 例如:

排序字段前面的 +表示升序   -表示降序  默认为升序
GET /tickets?sort=-priority  # 获取票据列表,按优先级字段降序排序
GET /tickets?sort=-priority,created_at  # 获取票据列表,按“priority”字段降序排序。在一个特定的优先级内,较早的票排在前面。

5) 减少层级深度 /在url中表达层级,用于按实体关联关系进行对象导航,一般根据id导航。 过深的导航容易导致url膨胀,不易维护,如 GET /zoos/1/areas/3/animals/4,尽量使用查询参数代替路径中的实体导航,如GET /animals?zoo=1&area=3

6) 限制返回哪些字段 使用一个字段查询参数,它包含一个 用逗号隔开的字段列表。例如,下列请求获得的信息将刚刚足够展示一个在售票的有序列表: GET /tickets?fields=id,subject,customer_name,updated_at&state=open&sort=-updated_at

7) 关于返回结果 通常情况下只返回JSON格式结果。 返回结果支持gzip压缩:进行gzip压缩的数据可节省50%以上的带宽 对于需要返回不同格式资源的情况:

  1. 在HTTP Header中指定格式 如:Accept:application/xml;q=0.6,application/atom+xml;q=1.0
  2. URL后缀增加扩展名: 如:/users/1.xml

8) 分页 常用的分页字段如下:

limit=10:指定返回记录的数量
offset=10:指定返回记录的开始位置。
page=2&per_page=100:指定第几页,以及每页的记录数。

9) 缓存 ETag: 当产生一个请求时,包含一个HTTP 头,ETag会在里面置入一个和表达内容对应的哈希值或校验值。这个值应当跟随表达内容的变化而变化。现在,如果一个入站HTTP请求包含了一个If-None-Match头和一个匹配的ETag值,API应当返回一个304未修改状态码,而不是返回请求的资源。 Last-Modified: 基本上像ETag那样工作,不同的是它使用时间戳。在响应头中,Last-Modified包含了一个RFC 1123格式的时间戳,它使用If-Modified-Since来进行验证。注意,HTTP规范已经有了 3 种不同的可接受的日期格式 ,服务器应当准备好接收其中的任何一种。

2. HTTP方法

常用:

GET (选择):从服务器上获取一个具体的资源或者一个资源列表。
POST (创建): 在服务器上创建一个新的资源。
PUT (更新):以整体的方式更新服务器上的一个资源。
PATCH (更新):只更新服务器上一个资源的一个属性。
DELETE (删除):删除服务器上的一个资源。

不常用:

HEAD : 获取一个资源的元数据,如数据的哈希值或最后的更新时间。
OPTIONS:获取客户端能对资源做什么操作的信息。

3. 数据报文

  1. 统一报文格式
请求使用JSON格式时:
{
    method: 'testMethod',// 请求方法名称
    version: '1.0',// 接口版本
    token: '0X十六进制',//  Token
    sign_type: 'MD5',//  签名算法
    sign: '0X十六进制',//  签名
    timestamp: '12345678'    // 请求的时间戳
    
}

响应:
{
    code: 0,// 返回码。 -1:失败  0:成功  其它:具体业务代码
    message: '处理成功',// 返回码描述信息
    sign: '0X十六进制',// 签名
    ...                                   // 业务数据
}

响应数据不作多余包装,如下为错误示例:

{
    code: 0,data: {userName: "用户名"} // 这里面的data没有业务含意,仅仅是为了包装,所以应该去掉
}

需要进行包装的情况:

  1. 使用JSONP进行跨域请求
  2. 当客户端没有能力处理HTTP头信息时
  1. 对于日期类型的字段处理
  1. 一种方式为:转为一定格式的字符串,如 yyyy-MM-dd HH:mm:ss.SSS
  2. 另一种方式为:转为长整型数字时间戳

4. 安全

  1. 使用Https传输数据
  2. 使用Token(如JWT)来标识用户状态并设置失效时间
  3. Token失效后客户端自动重新登录获取新的Token
  4. 发送请求时对参数按ASCII排序计算签名(Hash算法或对称加密算法,可以所有接口统一密钥,也可以一个接口一个密钥),接收到请求后先验证签名
  5. 每个端(Android、iOS、微信服务号、Web网站)生成一个AppKey
  6. 不设置密码,登录时使用手机+验证码方式登录

5. 文档

  1. 文档须提供从请求到响应整个循环的示例;
  2. 请求应该是可粘贴的例子,要么是可以贴到浏览器的链接,要么是可以贴到终端里的curl示例 ;
  3. 一旦发布一个公开的API,必须承诺 在没有通知的前提下,不会更改API的功能
  4. 对于外部可见API的更新,文档必须包含任何将废弃的API的时间表和详情;

6. HTTP状态代码

HTTP定义了一套可以从API返回的有意义的状态代码。 这些代码能够用来帮助API使用者对不同的响应做出相应处理。

200 OK (成功)  -  对一次成功的GET,PUT,PATCH 或 DELETE的响应。也能够用于一次未产生创建活动的POST
201 Created (已创建)  -  对一次导致创建活动的POST的响应。 同时结合使用一个位置头信息指向新资源的位置
204 No Content (没有内容) - 对一次没有返回主体信息(像一次DELETE请求)的请求的响应
304 Not Modified (未修改) - 当使用HTTP缓存头信息时使用304
400 Bad Request (错误的请求) - 请求是畸形的,比如无法解析请求体
401 Unauthorized (未授权) - 当没有提供或提供了无效认证细节时。如果从浏览器使用API,也可以用来触发弹出一次认证请求
403 Forbidden (禁止访问) - 当认证成功但是认证用户无权访问该资源时
404 Not Found (未找到) - 当一个不存在的资源被请求时
405 Method Not Allowed (方法被禁止) - 当一个对认证用户禁止的HTTP方法被请求时
410 Gone (已删除) - 表示资源在终端不再可用。当访问老版本API时,作为一个通用响应很有用
415 Unsupported Media Type (不支持的媒体类型) - 如果请求中包含了不正确的内容类型
422 Unprocessable Entity (无法处理的实体) - 出现验证错误时使用
429 Too Many Requests (请求过多) - 当请求由于访问速率限制而被拒绝时

7. 错误处理

原则

  1. 不要发生了错误但给2xx响应,客户端可能会缓存成功的http请求;
  2. 正确设置http状态码,不要自定义;
  3. Response body 提供 1) 错误的代码(日志/问题追查);2) 错误的描述文本(展示给用户)。

API 可能抛出两类异常:业务异常和非业务异常。 业务异常由自己的业务代码抛出,表示一个用例的前置条件不满足、业务规则冲突等,比如参数校验不通过、权限校验失败。 非业务类异常表示不在预期内的问题,通常由类库、框架抛出,或由于自己的代码逻辑错误导致,比如数据库连接失败、空指针异常、除0错误等等。

业务类异常必须提供2种信息:

  1. 如果抛出该类异常,HTTP 响应状态码应该设成什么;
  2. 异常的文本描述;

在Controller层使用统一的异常拦截器:

  1. 设置 HTTP 响应状态码:对业务类异常,用它指定的 HTTP code;对非业务类异常,统一500;
  2. Response Body 的错误码:异常类名
  3. Response Body 的错误描述:
  4. 对业务类异常,用它指定的错误文本;
  5. 对非业务类异常,线上可以统一文案如“服务器端错误,请稍后再试”;
  6. 开发或测试环境中用异常的 stacktrace,服务器端提供该行为的开关。

8. 超媒体API

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

{"link": {
  "rel":   "collection https://www.example.com/zoos","href":  "https://api.example.com/zoos","title": "List of zoos","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的网址列表。

{
  "current_user_url": "https://api.github.com/user","authorizations_url": "https://api.github.com/authorizations",// ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

{
  "message": "Requires authentication","documentation_url": "https://developer.github.com/v3"
}

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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读