Web API 的设计和开发

  1. 什么是 Web API
    1. 软件组件的外部接口;为了从外部调用软件的功能,需要指定调用该功能应该遵守的规则,这些规则,即是 API;
    2. API 的重要性:通过使用API,避免大包大揽,将一些非核心功能开放给第三方来开发,通过分工建立生态,建立更加强大的生命力;
    3. 面向外部大量开发者群体的 API 和面向内部少量开发群体的 API,在设计思想上会有所区别,前者侧重普适通用性,后者侧重上手,二者对于“优美”的定义不同;
  2. 响应数据的格式
    1. JSONP
      1. 目的:规避同源策略的限制
      2. 用法:将响应数据作为 script 进行引用;
      3. 原理:因为 Script 的加载不受同源策略约束;
      4. 建议:能不支持尽量不支持,原因:安全问题,可以会被攻击;
    2. 数据内部结构
      1. 原则:尽量减少 API 请求次数,一次性返回所需要的数据;
      2. 让用户选择响应的内容;指定单个字段太麻烦的话,可以考虑使用小组及组合或嵌套的策略,将所需要的字段根据场景做进一步的抽象;
      3. 封装可以让响应格式统一,但有点浪费 HTTP 协议本身自带的封装效果;
      4. 数据尽量扁平化,除非层级有绝对优势才考虑使用层级;
      5. 列表建议做 { } 封装,原因:封装后不符 js 语法,可以避免攻击;
      6. 列表的响应,可以添加 hasNext 字段,来表示是否还有后续内容;
    3. 数据的格式
      1. JSON:驼峰命名法
      2. 性别:GENDER 是社会意义的,SEX 是生物意义的;
      3. 日期的格式: RFC3339,2015-10-12T11:30:22+09:00
      4. 大数据:由于JS处理64bit长度的局限性,可以转成字符串传输;
    4. 响应数据的格式
      1. 出错信息用状态码表示,遵循HTTP的规范,即2开头表示成功,4开头表示客户端请求有误,5开头表示服务器内部处理有误(原因:更加易懂);
      2. 向客户端返回详细的错误信息,以便其了解原因,方便调试
      3. 可以考虑使用 devMsg 和 userMsg 来区分显示给用户和开发人员的消息内容;
      4. 另外还可以加上 info 字段放上文档链接,方便人员查询错误代码的具体意义;
      5. 有时需要返回意义不明确的信息,例如登录错误(涉及安全),好友屏蔽(涉及隐私)等;开发环境和生产环境也可以做区分,开发环境返回真实的信息,生产环境返回模糊的信息;
  3. 尽量利用 HTTP 协议
    1. 尽量使用通用规范的好处:让他人的学习成本更低,易于理解;
    2. 状态码:2、3、4、5,每个数字下面,都对应有几十种细分,非常丰富,所以应尽量使用它们来表示想要的状态;
    3. 202:表示服务端已经开始处理,但还没有弄好;此时可以在响应消息的 Retry-after 字段里面,放上时间(目测这个功能可以用来在客户端显示进度?)
    4. Etag:一种缓存校验机制;缓存的目的是提高通信效率,但可能存在的问题是如何获取最新版本,此时通过给数据的版本加上唯一标识符,即可以知悉资源是否更新;如果标识符发生变化,则表示需要获取最新版本;
    5. 3开头,其中的强制重定向,可以用来避免客户端在原页面重复进行操作,当请求处理成功后,返回给客户端一个新的页面,这在付款、下单、下载资源等场景非常有用;
    6. 4开头:认证错,授权错,资源不存在,资源冲突,方法错,格式不支持,曾经存在,超时,太长太大,超次数
    7. 5开头:内部错,存在不可用;
  4. 缓存与HTTP规范
    1. 涉及方:客户端缓存、客户端代理服务器缓存、服务端代理服务器缓存;
    2. 过期模型:通过在首部指定过期时间(绝对时间,相对时间等),可以有效减少请求次数;
    3. 校验模型:通过判断资源是否更新(虽然没有减少请求次数,但适用于大数据交互的场景);通过 Entity Tag 来实现,即 ETAG,在首部加上这个信息用于判断;根据场景可分为强验证和弱验证两种;
    4. 启发式过期:服务器端没有指定具体的过期时间,客户端(浏览器)自己判断;比如首部中有 last modified 字段,假设为1年前,则客户端可以猜测缓存有效期可以设置为1年左右;如果 last modified 是昨天,则缓存有效期可以设置为半天;
    5. 不希望缓存: no-cache(其实有缓存,只是每次都需要验证),no-store(真的没有缓存)
    6. vary 字段:用来增加资源的唯一识别,除了 URI 外,再加上 vary 批定的字段,只有这些字段的值全部一致时,才读取缓存数据,如果不一致,则重新到服务器进行请求;
    7. cache-control 支持多种参数,其中有一种 stale-while-revalidate,假设该字段值设置为1000,则表示1000到期后,缓存仍然有效,并且,此时会异步去服务器请求最新的数据;这样的好处是即提高了响应速度,也能保证数据及时更新;
    8. 媒体类型:由于有时客户端采用的第三方库,会根据返回的媒体类型进行不同的操作,因为返回正确的媒体类型很重要,避免客户端处理出错;
    9. x- 开头的媒体类型,表示该类型尚未在官方组织正式注册;但新的 RFC 规范已经废止通过这种方式来自定义媒体类型,而采用了“注册树”的办法;
    10. x- 也常用于表示私有首部;
  5. API 的设计要方便更改
    1. 常用做法:在 URI 中嵌入主版本号
    2. 最大程度的减少 API 的更新;如果必须更新,注意向下兼容;
    3. 发布新版 API 后,如果需要停止对老版本的支持,则建议正常先保留老版本半年;
  6. 安全性与可靠性
    1. 安全问题
      1. 劫持服务端与客户端之间的通讯信息;
        1. 应对措施:使用 HTTPS 进行加密;缺点:会增加一些访问的时间,性能有所下降;
      2. 攻击服务器获取服务端的数据;
    2. 攻击类型
      1. XSS:在传输的数据中,注入恶意的代码(例如 js),然后触发它被执行;例如先伪造一个 URL,吸引用户点击,之后传输包含恶意代码的网页给用户,用户端的浏览器显示该网页时,触发恶意代码被执行;该攻击有3种类型,其中一些只危害用户端,另2种会危害服务器端
        1. 防范:对用户的输入进行检查,确保其中不包含恶意的代码
      2. XSRF:跨站点请求伪造;站点 B 伪造成用户的身份登录站点 A 进行操作;
        1. 前提:用户登录了 站点 A,同时,用户打开了站点 B
        2. 原理:当用户点击 B 时,执行恶意代码,获取了站点 A 给用户的 Cookie,凭借这个 Cookie,站点 B 假装自己是用户,向站点 A 发起了非法的请求;
        3. 防范:除了 Cookie 外,增加其他验证用户身份的方式;比如:
          1. 只能使用 POST 方法进行数据更新的操作(原因:因为 GET 方法缺少验证)
          2. 使用 TOKEN 验证;
          3. 使用 一些只能人工识别的验证方式,例如识别图片上面的字符
      3. 来自怀有恶意的用户的攻击
        1. 修改参数达到有利自己的目的(防范:仔细检查参数的有效性)
        2. 多次发送(防范:增加状态的判断,例如通过增加虚拟的订单和收据,然后根据单据的状态变化,来判断是否允许再次操作)
    3. 同安全相关的 HTTP 首部
      1. X-XSS-Protection: 1
        1. 开户后,浏览器可以检测和防御 XSS 攻击(Chrome 和 Safari 此功能是默认开启的,且无法禁用)
      2. X-Frame-Options: Deny
        1. 攻击者在用户的页面上添加透明的 iFrame 元素,当用户点击时,执行了恶意代码(即点击劫持)
      3. Content-Security-Policy: default-src ‘none’
        1. 用于指定读取的HTML内元素指向的资源范围(例如:IMG, SCRIPT, LINK 等元素),用于降低 XSS 攻击的风险;
      4. Strict-Transport-Security: max-age=15768000
        1. 预先告知浏览器只能通过 HTTPS 进行访问
        2. max-age 值,即是告知浏览器,在这个时间期限内,只能使用 HTTPS 访问;
        3. 该首部只有通过 HTTPS 发放时才会生效,通过 HTTP 发放是不生效的;
        4. 缺点:第一次使用 HTTPS 访问前,如果已经通过 HTTP 访问过,则该首部不会产生效果
      5. Set-Cookie: session=e827ea…..3i1689jp; Path=/; Secure; HttpOnly
        1. Secure 表示 session 值只能在 HTTPS 下发送,HTTP 下不发送(避免泄露)
        2. HttpOnly 表示仅在 HTTP 通信方式下,才能读取该 session 值,不能通过其他方式读取,避免被恶意脚本获取 session 值;
    4. 应对大规模访问
      1. 限制每个用户的访问:
        1. 前提:需要能够识别用户
        2. 方法:给每个用户设置 Token 或 key 来识别;
      2. 设置访问次数的上限:时间单位需要考虑用户如果超出限制,需要等待多久;
      3. 当超出次数限制时,返回 429 状态码,Too many requests
      4. 告知用户详细的限制信息,包括:单位时间允许次数,剩余次数,重置时间;
        1. 方式一:通过专门的API,让用户主动查询
        2. 方式二:在返回的消息的首部注明,包括:
          1. X-RateLimit-Limit,单位时间的访问上限;
          2. X-RateLimit-Remaining,剩余的访问次数;
          3. X-RateLimit-Reset,访问次数重置的时间;
      5. 服务器端可使用 Redis 来记录限制信息;
  7. 附录
    1. 公开 Web API 的准备工作
      1. 提供文档,可参考 https://apiblueprint.org 倡导的规范;
      2. 提供沙盒,让用户可以在这个环境中随意测试;
      3. 提供 console,让用户可以通过浏览器对 API 进行操作测试;例如:https://developers.facebook.com/tools/explorer
      4. 提供 SDK,让开发人员能够快速上手使用,避免自己写代码进行连接的工作;缺点:如果 API 更新频率很高,则维护 SDK 的工作量也很大;
    2. 确认清单
      1. URI 是否短小且容易输入
      2. URI 是否能够让人一眼看懂;
      3. URI 是否只由小写字母组成;
      4. URI 是否容易修改;
      5. URI 是否反映了服务端的架构;
      6. URI 规则是否统一;
      7. 是否使用了合适的 HTTP 方法;
      8. URI 用到的单词是否和业界相同;
      9. URI 用到的名词是否使用了复数形式;
      10. URI 里面有没有空格及需要编码的字符;
      11. URI 单词之间是否使用了连字符;
      12. 分布的设计是否恰当;
      13. 登录是否使用了 OAuth 2.0
      14. 响应数据格式是否默认使用 JSON
      15. 是否支持通过查询参数指定数据格式;
      16. 是否支持不必要的 JSONP
      17. 响应数据的内容是否支持客户端指定;
      18. 响应数据是否存在不必要的封装;
      19. 响应数据的结构是否尽量扁平化;
      20. 响应数据是否使用对象描述,而非数组
      21. 响应数据使用的单词名称是否和业界一致;
      22. 响应数据的名称是否尽可能短小;
      23. 响应数据的多个单词是否使用了连字符;
      24. 响应数据名称是否使用了奇怪的缩写;
      25. 响应数据单词的单复数形式是否和内容一致;
      26. 出错时的响应数据是否包含有助于客户分析原因的信息;
      27. 出错时有没有返回 HTML 数据;
      28. 有没有返回合适的状态码;
      29. 服务器在维护时有没有返回 503 状态码;
      30. 有没有返回合适的媒体类型;
      31. 必要时是否支持 CORS
      32. 有没有返回 Cache-Control,ETag,Last-Modified, Vary 等首部信息,以便客户端制定合适的缓存策略;
      33. 不想缓存的数据是否指定 no-cache 或 no-store
      34. 有没有对 API 进行版本管理
      35. API 版本的命名是否遵守版本控制规范;
      36. 有没有在 URI 里嵌入主版本编号,并且能够让人一目了然;
      37. 有没有考虑 API 终止提供时的相关事项;
      38. 有没有在文档里注明 API 的最低提供期限;
      39. 有没有使用 HTTPS 提供 API
      40. 有没有执行 JSON 转义;
      41. 能不能识别 X-Requested-With 首部,让浏览器无法通过 Scrip 元素读取 JSON 数据;
      42. 通过浏览器访问的API 有没有使用 XSRF Token
      43. API 在接收参数时,有没有检查非法参数;
      44. 能否避免重复发送,导致数据多次更新;
      45. 有没有在响应消息里添加各种增强安全的首部;
      46. 有没有实施访问限速;
      47. 对预想的用例来说,限制的次数是否可能设置得过少;

Web API 的设计和开发
https://ccw1078.github.io/2018/07/11/Web API 的设计和开发/
作者
ccw
发布于
2018年7月11日
许可协议