Web API设计与开发

Web API的设计与开发

说明

丰富了附录B中的Web API检查清单,给予了一些必要的解释与说明,方便大家快速学习。

另外,以下只是作者的建议,使用需结合实际情况。

检查清单

  • URI是否短小且容易输入
好的例子:http://api.example.com/search
坏的例子:http://api.example.com/service/api/search。

域名已经是api,在URI就没有必要重复一些毫无意义的单词。
  • URI是否能让人一眼看懂
不要轻易使用缩写:http://api.example.com/sv/u
使用更地道的英语表达,比如搜索接口一般用search而不是find,可以多参照一些国外大厂的API。
  • URI是否只有小写字母组成
HTTP协议指定了:URL中除了schema和hostname不区分大小写,其他部分均大小写敏感。
因此,URI应该使用小写,禁止大小写混写。
  • URI是否容易修改
比如获取某个商品信息的URI应该长这样:http://api.example.com/v1/items/12346
从URI直观的即可知道获取56780商品的URI是这样:http://api.example.com/v1/items/56780。

开发者对URI的行为理解是自然的,不费心的。
  • URI是否反映了服务端的架构
不要暴露服务端是哪种开发语言,下面是错误的例子:
http://api.example.com/cgi-bin/get_user.php?user=100
  • URI规则是否统一
URI中的词汇和结构应该保持统一。

下面是一个错误的例子:

获取好友信息:http://api.example.com/friends?id=100
发送信息:http://api.example.com/friend/100/messages

首先,friends和friend的单复数形式不统一,你可以想象API使用者是如何被这种细小的差异坑了半天。
其次,一个通过get参数传参,一个通过URI路径传参,不够统一。

一个正确的例子:
获取好友信息:http://api.example.com/friends/100
发送信息:http://api.example.com/friends/100/messages

该例子遵循REST风格,下面讲解REST URI设计风格。

  • 有没有使用合适的HTTP方法
作者强调REST风格。

HTTP方法表示"进行怎样的操作",URI表示"资源",HTTP和URI一起则表示"对什么资源做什么操作"。

GET:获取资源

获取ID=100的好友信息,
GET http://api.example.com/friends/100

POST:新增资源

添加一位好友,相当于新建一个好友关系:
POST http://api.example.com/friends

PUT:更新已有资源

更新ID=100的好友信息(例如:更新备注信息),
PUT http://api.example.com/friends/100

PATCH:更新部分资源

和PUT类似,只是强调更新资源的部分信息,不常用。

DELETE:删除资源

删除ID=100的好友信息:
DELETE http://api.example.com/friends/100

HEAD:获取资源的元信息

这个作者没有详细去说一个资源的元信息应该是什么样的。

感觉和自己设计的API不太一样吧?这就是REST风格,常见于国外各种大厂。
  • URI里用到的单词所表达的意思是否和大部分API相同
有点重复,还是强调对于非英语母语的开发者,应该注意使用地道准确的单词。
比如,照片应该用photo而不是picture。
  • URI里用到的名词是否采用了复数形式
因为URI表示资源的集合,所以作者是建议总是使用复数形式。

正确的例子:http://api.example.com/friends/100
错误的例子:http://api.example.com/friend/100

另外,因为REST风格强调URI是资源,所以不应该在URI里出现动词,因为动作是HTTP方法表达的。

错误的例子:http://api.example.com/get_friend?id=100
  • URI里有没有空格符以及需要编码的字符
URL是会被urlencode编码的,所以不要在URI里使用空格(会被编码成+)、UTF-8字符、乱七八糟的符号等。

即不要影响URI的可读性。
  • URI里的单词和单词之间有没有使用连接符
因为URL中的hostname不允许使用下划线,所以作者建议URI部分总是使用连字符-来连接多个单词。
  • 分页的设计是否恰当
分页参数分2种风格,可以按情况使用:

第一种,表示第3页,每一页50条:
page=3&per_page=50

第二种:表示从100条开始的50条:
offset=100&limit=50

前者page从1开始增长,后者offset从0开始增长。

前者对用户来说自由度较低,缓存命中率较高。
后者对用户来说自由度较高,缓存命中率较低。

上述翻页风格称为"相对位置",深度翻页性能差(可以自己扩展学习),如果数据集合有更新,则翻页时可能看到重复内容或者错过一些内容。

与之相对的是"绝对位置",即指定last_id之前的N条数据,下次使用新的last_id继续获取。(个人理解瀑布流页面比较适合)

  • 登录有没有使用OAuth2.0
很常见的认证规范,让用户通过大厂的账号系统登录,并授权给第三方获取用户信息的权限。

作者表达的意思是你如果对外提供API,应该提供OAuth2.0认证,这样第三方调用API时携带access_token,我们即可校验其是否有权使用API。

最后,作者描述了一下REST API的几个等级:

REST LEVEL0:使用HTTP
REST LEVEL1: 引入资源的概念
REST LEVEL2:引入HTTP动词(GET/POST/PUT/DELETE等)
REST LEVEL3:引入HATEOAS概念

本书是REST LEVEL2。

LEVEL3中的HATEOAS概念尚未普及,其思路是API返回的数据中应该包括下一步行为对应的URI是什么,客户端请求下一步的URI就可以得到进一步的数据。

比如文章列表的返回值:

{
    "articles": [
        {
            "title": "good",
            "uri": "https://api.example.com/v1/articles/12345",
            "rel": "article/detail"
        }
    
    ]

}

每一篇文章的uri告知客户端如何获取文章详情,属于一种高度灵活。


  • 响应数据格式有没有使用JSON作为默认格式
越简单的东西越容易普及,JSON比XML简单的多,也满足需求,另外Javascript天然支持json。

客户端没有明确指定返回值格式的话,JSON应该作为默认的返回值格式。
  • 是否支持通过查询参数来指定数据格式
如果服务端支持多种返回数据格式,那么客户端可以指定。

通过get参数:
https://api.example.com/v1/users?format=xml

通过扩展名:
https://api.example.com/v1/users.xml

通过HTTP头部:
GET /v1/users
Host: api.example.com
Accept: application/xml

作者建议首先使用HTTP头部,因为更符合HTTP协议规范;其次使用查询参数,避免使用扩展名。
  • 是否支持不必要的JSONP
JSONP可以实现跨域HTTP调用,其原理是基于"}

好在大多数JSON库默认会在编码时会进行适当的转义,因此最终得不到执行:
{"username":"可以实施攻击。
因为接口返回的是JSON,而加载时指定了vb语言肯定是无法解析成功的,因此攻击者通过设置window.onerror = function(err) {}即可被浏览器回调,从而从错误信息中获取到用户信息。

解决这个问题的方法是禁止通过script标签调用API,判定方法就是服务端判断请求中是否有Header X-Requested-With,因为Ajax请求默认会携带这个header而script不会。
  • 通过浏览器访问的API有没有使用XSRF token
XSRF称为跨站点请求伪造。

攻击者在自己的网站做一个form表单,提交地址写为目标网站的表单提交地址。当受害者访问攻击者网站时,攻击者通过javascript自动提交form表单(form.submit),即可完成向目标网站的提交(想象这是一个转账表单)。

form表单提交不受同源策略(跨域)影响,因此可以达成上述攻击手段。

解决方法就是在表单里生成一次性的CSRF token放在隐藏字段中,并把token种植在用户cookie中,在用户提交表单到API时可以检查表单token和cookie中的token一致,则允许提交。
  • API在接收参数时有没有仔细检查非法的参数(负数等)
作者以减少用户积分的API为例,如果传入一个负数积分,会导致减法变成加法,导致用户积分越来越多。

所以API需要严格校验参数是否合法。
  • 有没有做到即使请求重复发送,数据也不会多次更新
作者其实就是想表达幂等性,举了一个支付系统的例子,就不详细描述了。
  • 有没有在响应消息里添加各种增强安全性的首部
有很多header是作者建议总是加在API响应头里的,可以给浏览器很多建议,提升安全等级,就不一一描述了。

比较重要的一点是set-cookie时的安全问题:
1)Secure属性:表示cookie只能在访问https链接时才能被发送给服务端,这样可以彻底避免cookie被攻击者在网络中嗅探到。
2)HttpOnly属性:cookie仅能供HTTP调用时使用,而不允许javascript直接获取cookie,这样可以避免网站出现XSS漏洞的时候,攻击者通过JS代码把用户的会话cookie盗走。
  • 有没有实施访问限速
限速是为了保护API服务,避免超过负载。

限速一般是针对每个用户的,限速的单位是多少分钟内最多访问多少次。

从实际存储上可以采用Redis,key的数量大概是"API的数量 * 用户数量"。

API超出限速应该返回429 Too Many Requests的http code,最好还能给出Retry-After告知多久后可以继续使用。

  • 对预想的用例来说限速的次数有没有设置得过少
一般来说,应该为开放的API开发一套dashboard管理后台,从而可以灵活的为不同的用户设置不同的限速值,以及查看实时速率以及剩余调用次数等信息。

购买链接

Web API设计与开发

你可能感兴趣的:(Web API设计与开发)