HTTP RESTful规范说明

1. API的就是程序员的UI,和其他UI一样,必须仔细考虑它的用户体验!

几个必须的原则:

  1. 当标准合理的时候遵守标准。

  2. API应该对程序员友好,并且在浏览器地址栏容易输入。

  3. API应该简单,直观,容易使用的同时优雅。

  4. API应该具有足够的灵活性来支持上层ui。

  5. API设计权衡上述几个原则。

2. URL设计

REST的核心原则是将你的API拆分为逻辑上的资源。这些资源通过http被操作(GET ,POST,PUT,DELETE). 显然从API用户的角度来看,”资源“应该是个名词。即使你的内部数据模型和资源已经有了很好的对应,API设计的时候你仍然不需要把它们一对一的都暴露出来。这里的关键是隐藏内部资源,暴露必需的外部资源。

URL遵循动词+宾语的结构

2.1 动词

请求 含义
GET 读取/Read
POST 新建/Create
PUT 更新/Update
PATCH 部分更新/Update
DELETE 删除/Delete

2.2 宾语

宾语必须为名词且最好为复数,同时要避免多级URL,可通过参数来展示分类. 使用复数使得URL更加规整, 这让API使用者更加容易理解,对开发者来说也更容易实现.

一旦定义好了要暴露的资源,你可以定义资源上允许的操作,以及这些操作和你的API的对应关系:

  • GET /tickets # 获取所有ticket列表

  • GET /tickets/12 # 查看某个具体的ticket(id为12)

  • POST /tickets # 新建一个ticket

  • PUT /tickets/12 # 更新ticket(id为12)

  • DELETE /tickets/12 # 删除ticekt(id为12)

可以看出使用REST的好处在于可以充分利用http的强大实现对资源的CURD功能。而这里只需要一个endpoint:/tickets, 再没有其他什么命名规则和url规则了.

2.3 处理资源关联

如何处理关联?关于如何处理资源之间的管理REST原则也有相关的描述:

  • GET /tickets/12/messages # 获取ticket(id为12)的所有messages列表

  • GET /tickets/12/messages/5 # 获取ticket(id为12)的某个具体message(id为5)

  • POST /tickets/12/messages # 为ticket(id为12)新建一个message

  • PUT /tickets/12/messages/5 # 更新ticket(id为12)的message(id为5)

  • PATCH /tickets/12/messages/5 # 部分更新ticket(id为12)的message(id为5)

  • DELETE /tickets/12/messages/5 # 删除ticket(id为12)的message(id为5)

其中,如果这种关联和资源独立,那么可以在资源的输出表示中保存相应资源的endpoint。然后API的使用者就可以通过点击链接找到相关的资源。如果关联和资源联系紧密。资源的输出表示就应该直接保存相应资源信息。(例如这里如果message资源是独立存在的,那么上面 GET /tickets/12/messages就会返回相应message的链接;相反的如果message不独立存在,他和ticket依附存在,则上面的API调用返回直接返回message信息)

2.4 版本化

在API上加入版本信息可以有效的防止用户访问已经更新了的API,同时也能让不同主要版本之间平稳过渡。

版本化API的通常方式有:

2.4.1 URI中设置版本

这种方式通常在URI中增加一段用于标识版本,例如/v1/v2等。例如:

curl https://example.com/api/v2/lists/3

这种方式的优势在于版本信息很容易明显的看出来,可以通过浏览器直接访问。

2.4.2 HTTP头中设置版本

这种方式的版本信息会放在HTTP的请求头中,通常会利用Accept字段,或者自定义一个字段。例如:

curl https://example.com/api/lists/3 \  
-H 'Accept: application/vnd.example.v2+json'

这种方式的好处是当版本升级时,URI保持不变,并且仅用于表示资源定位。

2.4.3 没有版本

版本化的目的是为了标识API的变化,如果API不会变化,或者每次都会重新扩展新的API,这种情况下,就可以标识版本信息。例如:

curl https://example.com/api/lists/3
2.4.4 一种折中方案

前面提到了三种版本化API的方式,通常情况下需要针对自己业务的特殊性来挑选其中的一种方式。但是,在实际应用场景中,情况会更加复杂,API的升级通常有两种情况:

  1. 大版本更新,例如字段类型变更、数据对象变更等。这种情况下无法满足对客户端的向下兼容,因此需要修改版本号。

  2. 小版本更新,例如增加可选参数、增加返回字段等。这种情况对于新客户端可以增加功能,对于老客户端仍然可以保持原有功能,可以不修改版本号。

因此,折中方案是基于URI中的大版本号和HTTP头中的小版本号整合的方式。下面通过一个简单的示例来解释。

用户管理平台

一个常用的用户管理平台,提供以下API,通过用户ID获取用户信息:

curl https://example.com/api/v1/user/1
...
{
  "id": 1,
  "name": "test",
  "email": "test@example.com"
}

考虑以下两种变动情况:一种是用户id从数字变成了字符串,另一种是新增一个用户头像的值。

前者修改因为数据类型的变化,会导致客户端解析出现问题。因此这样的修改已经破坏了向下兼容性,此时就需要修改API的版本号。例如:

curl https://example.com/api/v2/user/1
...
{
  "id": "1",
  "name": "test",
  "email": "test@example.com"
}

第二种情况,对于旧客户端来说,只是增加了不使用的字段,通常的JSON格式解析库都可以忽略这些不使用的字段。对于新客户端则可以读取新的字段。例如:

curl https://example.com/api/v2/user/1
...
{
  "id": "1",
  "name": "test",
  "email": "test@example.com",
  "avatar": "http://example.com/1.jpg"
}

这种情况下,基本可以做到向下兼容,因此可以算是“小版本升级”。针对小版本升级,可以将小版本号放到HTTP头中。例如:

curl https://example.com/api/v2/user/1 \
    -H 'API-VERSION: 20170801'
...
{
  "id": "1",
  "name": "test",
  "email": "test@example.com",
  "avatar": "http://example.com/1.jpg"
}
2.4.5 后端路由

由于混合版本化的方式同时涉及到URI和HTTP头字段,前端代理(例如HAProxy、nginx)可以通过这些特定版本号字段将请求代理到对应的后端应用。

例如,前端使用HAProxy进行多版本分发,可以针对URI和HTTP头定制acl,然后再对这些acl进行组合,设置不同的backend。

acl is_v1 path_beg /api/v1
acl is_v2 path_beg /api/v2
acl is_version_1 hdr(API-VERSION) 20170801
acl is_version_2 hdr(API-VERSION) 20170701
use_backend old_server if is_v1 is_version_1
use_backend new_server if is_v2 is_version_2
backend old_server
...
backend new_server
...

这样可以将API版本化规则应用到不同的后端,以保证向下兼容性。

2.4.6 总结

基于版本化API规则,将“大版本”应用在URI上,将“小版本”应用在HTTP头字段上。通常来说,如果API升级之后破坏了向下兼容性,就应该升级“大版本”号;如果API升级可以向下兼容,可以升级“小版本”号。

版本化API有很多不同的设计方式,实际应用时,还是要根据业务场景进行选择,包括API版本升级频率,API稳定性等。通过HAProxy、nginx等代理服务,可以在确保向下兼容的情况下,由业务方决定老版本API的保留时间。

URL设计

URL遵循动词+宾语的结构

3.状态码

1xx 相关信息

API不需要1xx状态码

2xx 操作成功

状态码 含义
200 OK 成功
201 Created 生成了新的资源
202 Accepted 已经收到请求但还未处理完成/异步操作
204 No Content (删除时)资源已不存在

3xx 重定向

状态码 含义
301 Moved Permanently 永久重定向/资源的URL已被更新-API用不到
302 暂时重定向/用于GET请求-API用不到
303 See Other 暂时重定向/用于POST/PUT/DELETE请求
304 Not Modified 资源未更改/缓存
307 暂时重定向/用于GET请求-API用不到

4xx 客户端错误

状态码 含义
400 Bad Request 服务器不理解客户端的请求,未做任何处理
401 Unauthorized 用户未提供身份验证凭据,或者没有通过身份验证
403 Forbidden 用户通过了身份验证,但是不具有访问资源所需的权限
404 Not Found 所请求的资源不存在,或不可用
405 Method Not Allowed 用户已经通过身份验证,但是所用的 HTTP 方法不在他的权限之内
406 Not Acceptable 服务端不支持所需表示/参数格式错误
409 Conflict 通用冲突
410 Gone 所请求的资源已从这个地址转移,不再可用
412 Precondition Failed 前置条件失败(如执行条件更新时的冲突)
415 Unsupported Media Type 客户端要求的返回格式不支持
422 Unprocessable Entity 客户端上传的附件无法处理,导致请求失败
429 Too Many Requests 客户端的请求次数超过限额

5xx 服务端错误

状态码 含义
500 Internal Server Error 客户端请求有效,服务器处理时发生了意外
503 Service Unavailable 服务器无法处理请求,一般用于网站维护状态

4.服务器回应

API 返回的数据格式,不应该是纯文本,而应该是一个 JSON 对象,因为这样才能返回标准的结构化数据。所以服务端返回值的Content-Type属性应为application/json,同时客户端请求头ACCEPT属性应为application/json。

发生错误时,不要返回200状态码,这样相当于状态码失去了意义。应该用状态码表示发生的错误,然后在信息里写明错误详情

提供链接(这个看不懂)

应用范例

this.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
//手动控制HTTP状态码
this.Ctx.ResponseWriter.Write([]byte("byebye"))
//手动控制HTTP返回值
beego.Router("/user/:id([0-9]+",&controllers.UserController{},"GET:Get,PUT:Put,DELETE:Delete")
//同个接口路径不同类型访问导向不同方法
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容