《Web API 的设计与开发》读书笔记

@JChehe 2017-07-24 06:19:33发表于 JChehe/blog

Web API Checklist

  1. URI 是否短小且容易输入
  2. URI 是否能让人一眼看懂
  3. URI 是否只有小写字母组成
  4. URI 是否容易修改?
  5. URI 是否反映了服务器端的架构
  6. URI 规则是否统一
  7. 有没有使用合适的 HTTP 方法
  8. URI 里用到的单词所表示的意思是否和大部分 API 相同
  9. URI 里用到的名词是否采用了复数形式
  10. URI 里有没有空格符及需要编码的字符
  11. URI 里的单词和单词之间有没有使用连接符(-)
  12. 分页的设计是否恰当
  13. 登录有没有使用 OAuth 2.0
  14. 响应数据格式有没有使用 JSON 作为默认格式
  15. 是否支持通过查询参数来指定数据格式
  16. 是否支持不必要的 JSONP
  17. 响应数据的内容能不能从客户端指定
  18. 响应数据中是否存在必要的封装
  19. 响应数据的结构有没有尽量做到扁平化
  20. 响应数据有没有用对象来描述,而不是用数组
  21. 响应数据的名称所选用的单词的意思是否和大部分 API 相同
  22. 响应数据的名称有没有用尽可能少的单词来描述
  23. 响应数据的名称由多个单词连接而成时,连接方法在整个 API 里是否一致
  24. 响应数据的名称有没有使用奇怪的缩写形式
  25. 响应数据的名称的单复数形式是否和数据内容相一致
  26. 出错时响应数据中是否包含有助于客户端剖析原因的信息
  27. 出错时有没有返回 HTML 数据
  28. 有没有返回合适的状态码
  29. 服务器端在维护时有没有返回 503 状态码
  30. 有没有返回合适的媒体类型
  31. 必要时能不能支持 CORS
  32. 有没有返回 Cache-Control、ETag、Last-Modified、Vary 等首部以便客户端采用合适的缓存策略
  33. 不想缓存的数据有没有添加 Cache-Control: no-cache 首部信息
  34. 有没有对 API 进行版本管理
  35. API 版本的命名有没有遵循语义化版本控制规范
  36. 有没有在 URI 里嵌入主版本编号,并且能够让人一目了然
  37. 有没有考虑 API 终止提供时的相关事项
  38. 有没有在文档里明确注明 API 的最低提供期限
  39. 有没有使用 HTTPS 来提供 API
  40. 有没有认真执行 JSON 转义
  41. 能不能识别 X-Requested-With 首部,让浏览器无法通过 Script 元素读取 JSON 数据
  42. 通过浏览器访问的 API 有没有使用 XSRF token
  43. API 在接收参数时有没有仔细检查非法的参数(负数等)
  44. 有没有做到即使重复发送,数据也不会多次更新
  45. 有没有在响应小心里添加各种增强安全性的首部
  46. 有没有实施访问限速
  47. 对预想的用例来说限速的次数有没有设置得过少

每个点的详细说明

  1. URI 是否短小且容易输入
    在表示的信息量相同的情况下,使用短小、简单的表述方式更易于理解和记忆,并能减少输入时的错误。

    案例

    http://api.example.com/service/api/search
    =>
    http://api.example.com/search
    
  2. URI 是否能让人一眼看懂
    即使没有其他提示,也能理解其用途。

  • 不轻易使用缩写形式
  • 使用 API 里常用的英语单词(如检索,search 比 find)
  • 避免拼写错误
  1. 没有大小写混用的 URI
  • 建议全部用小写
  • 对于大小写不匹配的 API,返回 404(HTTP协议规定:除了 schema 和主机名外,其他信息都需要区分字母的大小写)
  1. 修改方便的 URI
  • 能将某个 URI 非常容易地修改为另一个 URI

  • 服务器端的处理均在服务器内部完成,而无需用户费心

    案例

    // 直观地看到 12345 是 item 的 ID,因此便于修改
    http://api.example.com/v1/items/12345
    
    // 极端案例
    1~3000     http://api.example.com/v1/items/alpah/:id
    4000~6000  http://api.example.com/v1/items/beta/:id
    
  1. URI 是否反映了服务器端的架构

  2. 规则统一的 URI
    指 URI 所用的词汇和结构等。

    案例:①查询字符串与URI路径 ②单复数

    http://api.example.com/friends?id=100
    http://api.example.com/friend/100/message
    =>
    http://api.example.com/friends/100
    http://api.example.com/friends/100/message
    
  3. 有没有使用合适的 HTTP 方法

    方法名 说明
    GET 获取资源
    POST 新增资源
    PUT 更新已有资源
    DELETE 删除资源
    PATCH 更新部分资源
    HEAD 获取资源的元信息

    GET

    表示获取信息。一般不会修改服务器上的已有资源(当然,已读/未读、最后访问日期等资源会因为 GET 操作而自我更新,属于例外)。

    POST

    用于向服务器注册新建的资源,如新用户注册、发布新的博文等。

    PUT

    用于更新资源。PUT 会用发送的资源完全替换原有的资源信息。如果只是更新资源的某部分数据,可以使用 PATCH

    DELETE

    用于删除指定的资源。

    PATCH

    用于更新原有资源中的部分信息。

  4. 使用连接符来连接多个单词
    为什么是连字符(-)而不是下划线等呢?因为 URI 的主机名(域名)允许使用连字符而禁止使用下划线,且不区分大小写。其次点字符具有特殊含义。
    其实最好的方法是尽量避免在 URI 中使用多个单词。比如,不用 popular_users,而用 users/popular,或者用查询字符串的方式。

  5. 分页的设计是否恰当
    ① 获取数据量和获取位置的查询参数: 通过 per_page=50&page=3limit=50&offset=100 组合。page 一般从1开始计数,offet 则从 0 开始计数。但这种使用相对位置的方法存在以下几个问题:(1) 性能问题(为了获取第2302条开始的数据,也需要从首条数据开发计数) (2) 如果数据更新的频率很高,会导致当前获取的数据出现一定的偏差(如数据重复)。
    ② 使用绝对位置来获取数据。如“某个 ID 之前”或“某个日期之前”等条件。

  6. 登录有没有使用 OAuth 2.0

  7. 响应数据格式有没有使用 JSON 作为默认格式
    越来越多公司只支持 JSON,而不支持 XML。

  8. 是否支持通过查询参数来指定数据格式
    若希望支持或者必须支持其他数据格式,则通过查询参数(推荐键值为 format)来指定。

  9. 是否支持不必要的 JSONP
    通过查询参数 callback 指定回调函数名字,另外由于 JSONP 是 JavaScript 而不是 JSON, Content-type 不是 application/json,而使用 application/javascript。
    使用 JSONP 最大的问题在于服务器返回错误时无法正确应对。当返回错误的状态码(400等)时,script 元素就会终止脚本的载入。换言之,如果在处理 JSONP 时发生错误,返回了 4、5 字头的状态码,客户端方面就完全无法知晓当前发生了什么。
    于是在使用 JSONP 时,即便发生了错误,也要求服务器依旧返回 200 这样的状态码,并在响应体里显示具体的错误内容。
    案例——将原本置于首部的状态码等信息放到消息体里进行处理。

{
   status_code: 404,
   error_message: 'User Not Found'
}

  1. 响应数据的内容能不能从客户端指定
    ① 通过字段名指定返回内容,若省略则返回所有信息,或在所有信息里选择使用频率最高的组合来返回。
    ② 预先准备几个项目的组合,让用户在必要时指定这些组合的名称即可。
http://api.example.com/v1/users/12345?fields=name,age
  1. 响应数据中是否存在必要的封装
  2. 响应数据的结构有没有尽量做到扁平化
    尽可能地做到数据扁平化,但遇到使用层级结构有绝对优势的情况时,也可以考虑使用层级结构。
  3. 响应数据有没有用对象来描述,而不是用数组
    推荐使用对象来封装数据的方式,原因如下:
    ① 更容易理解响应数据表示什么
    ② 响应数据通过对象的封装实现了结构统一
    ③ 可以避免安全方面的问题
    对于第三点,会造成 JSON 注入的安全隐患。
    JSON 注入是指在使用 script 元素加载 JSON,来在浏览器里加载其他服务的 API 所提供的 JSON 文件,从而非法获得其中的信息。当 JSON 是正确 JavaScript 语法的序列化(数组)时,这类问题才会触发。但在使用对象封装数据时,因为根节点的 {} 部分在 JavaScript 语言里会被解释器识别为语法块(block),因此其中单独的部分并不符合正确的语法(user: {})。
  4. 响应数据的名称所选用的单词的意思是否和大部分 API 相同
  5. 响应数据的名称有没有用尽可能少的单词来描述
  6. 响应数据的名称由多个单词连接而成时,连接方法在整个 API 里是否一致
    JSON 使用驼峰法。
  7. 响应数据的名称有没有使用奇怪的缩写形式
  8. 响应数据的名称的单复数形式是否和数据内容相一致
    当返回序列时要使用复数形式来命名,除此之外则使用单数形式。
  9. 出错时响应数据中是否包含有助于客户端剖析原因的信息
    使用在响应体里存放出错信息的方法是比较合适的选择。另外,将出错信息以序列的形式返回,可以说当多个错误同时出现时,这个一个非常合适的方法。例如当参数出现两处错误时,就可以将这两处错误分别予以描述,这对开发人员而言是非常友好的。
{
   "errors": [
       {
           "messages": "Bad Authentication data",
           "code": 215
       }
   ]
}

另外,对于“详细的错误代码”是指 API 提供者针对各个错误自定义的代码。这些代码的清单应该和 API 一起以联机文档的方式提供。对于这个代码,建议和 HTTP 状态码一样,如用4位数表示,1字头表示通用错误,2字头表示用户信息错误等。
另外,有时会在错误的提示信息里同时包含面向非开发人员的信息和面向开发人员的信息。

{
   "errors": {
       "developerMessage": "面向开发人员的信息",
       "userMessage": "面向用户的信息",
       "code": 2013,
       "info": "http://docs.example.com/api/v1/authentication"
   }
}
  1. 出错时有没有返回 HTML 数据
    虽说发生了错误,客户端依然在访问 API,所以仍然期待服务器返回 JSON 或 XML 等数据格式。尤其在通过 Accept 请求首部或扩展名等制定了接收格式。
  2. 有没有返回合适的状态码

通过首位数字即可了解状态的大概含义

状态码 含义
1字头 消息
2字头 成功
3字头 重定向
4字头 客户端原因引起的错误
5字头 服务器端原因引起的错误

主要的 HTTP 状态码

状态码 名称 说明
200 OK 请求成功
201 Created 请求成功,新的资源已创建。这也就是 POST 的场景。在数据库的数据表里添加了新的项目等场景中,都可以返回 201。
202 Accepted 请求成功。在异步处理客户端请求时,它用来表示服务器端已接受了来自客户端的请求,但处理尚未结束。在文件转换、处理远程通知,如(Apple Push Notification)这类很耗时的场景中,如果等所有处理都结束后才向客户端返回响应消息,就会花费相当长的时间。这时所采用的方法是服务器端向客户端返回一次响应消息,随后立即开始异步处理。202 状态码就被用于告知客户端:服务器端已开始处理请求,但整个处理过程尚未结束。
204 No Conent 没有内容。当响应信息为空时会返回该状态码
300 Multiple Choices 存在多个资源
301 Moved Permanently 资源被永久转移
302 Found 请求的资源被暂时转移
303 See Other 引用它处
304 Not Modified 自上一次访问后没有发生更新
307 Teamporary Redirect 请求的资源被暂时转移
400 Bad Request 请求不正确
401 Unauthorized 需要认证
403 Forbidden 禁止访问
404 Not Found 没有找到指定的资源
405 Method Not Allowed 无法使用指定的方法
406 Not Acceptable 同 Accept 相关联的首部里含有无法处理的内容。API 不支持客户端指定的数据格式时服务器端所返回的状态码。比如只支持 JSON 和 XML 输出的 API 被指定返回 YAML 的数据格式时,服务器端就会返回 406 状态码。HTTP 协议一般通过 Accept 请求首部来指定数据格式,但 API 里有时会用其他方式来指定。
408 Request Timeout 请求在规定时间内没有处理结束。当客户端发送请求至服务器所需的时间过长时,就会触发服务器端的超时处理。
409 Conflict 资源存在冲突。在使用邮箱地址及 FackBook ID 等信息进行新用户注册时,如果该邮箱地址或 ID 已被其他用户注册,就会引起冲突。
410 Gone 指定的资源已不存在。与 404 同表示资源不存在。但 410 还进一步表示该资源曾经存在但目前已经消失了。但这会让客户端知道服务器端知道不必要的信息。
413 Request Entity Too Large 请求消息体太大
414 Request-URI Too Long 请求的 URI 太长
415 Unsupported Media Type 不支持所指定的媒体类型
429 Too Many Requests 请求次数过多
500 Internal Server Error 服务器端发生错误
503 Service Unavailable 服务器暂时停止运行
  1. 服务器端在维护时有没有返回 503 状态码
  2. 有没有返回合适的媒体类型
  3. 必要时能不能支持 CORS
  4. 有没有返回 Cache-Control、ETag、Last-Modified、Vary 等首部以便客户端采用合适的缓存策略
  5. 不想缓存的数据有没有添加 Cache-Control: no-cache 首部信息
  6. 有没有对 API 进行版本管理
  7. API 版本的命名有没有遵循语义化版本控制规范
  8. 有没有在 URI 里嵌入主版本编号,并且能够让人一目了然
  9. 有没有考虑 API 终止提供时的相关事项
  10. 有没有在文档里明确注明 API 的最低提供期限
  11. 有没有使用 HTTPS 来提供 API
  12. 有没有认真执行 JSON 转义
  13. 能不能识别 X-Requested-With 首部,让浏览器无法通过 Script 元素读取 JSON 数据
  14. 通过浏览器访问的 API 有没有使用 XSRF token
  15. API 在接收参数时有没有仔细检查非法的参数(负数等)
  16. 有没有做到即使重复发送,数据也不会多次更新
  17. 有没有在响应小心里添加各种增强安全性的首部
  18. 有没有实施访问限速
  19. 对预想的用例来说限速的次数有没有设置得过少