RESTful API

RESRful API 设计与实战(一)

Richardson成熟度模型

rest model

  • level 0-The Swamp of POX
    仅使用HTTP作为传输协议,本质上是RPC的一种具体实现方式。RPC的思想是把本地函数映射为API,主题是动作,表示我要做什么。
  • level 1 - Resources
    引入资源的概念。每个资源都对应的标识和表达。
  • level 2 - HTTP Verbs
    Web服务使用不同的http方法来进行不同的操作,并且使用http状态码表达不同的结果。如使用GET方法来获取资源,POST方法创建资源。
  • level 3 - Hypermedia Controls
    Web服务使用HATEOAS。超媒体控制的一个显著优势是服务端在不影响客户端的前提下更改URI scheme。另外一个好处是,它可以帮助客户端开发者探索协议。

二 RESTful API 设计实践 (level 2)


2.1 URI

RESTful APIs 是关于资源的。URI的应该是可预测的,分层的结构。
可预测表示它们是一致的;分层表示资源具有关联关系。

通常,应该使用小写的复数形式来标识,snake_case格式。(有证据证明 snake_case比camelCase更易读

1
2
POST http://www.example.com/customers/33245/orders
POST http://www.example.com/customers/33245/orders/8769/lineitems

是否存在不适合使用复数的形式呢?
当然存在的,比如系统中的单例资源configutation。

1
GET|PUT|DELETE http://www.example.com/configuration

CURD不能表达的资源如何处理?

  1. 当操作不需要参数时,可以将操作处理成某个资源的属性。比如activate操作可以处理成一个activated的boolean类型的属性,然后用PATCH方法更新它。
  2. 将操作处理为子资源。

WEB开发中,使用JSON-RPC好,还是RESTful API好?

login和logout其实只是对session资源的创建和删除;

search本身就是个资源,使用POST创建,如果不需持久化,可以直接在Response中返回结果,如果需要(如翻页、长期缓存等),直接保存搜索结果并303跳转到资源地址就行了;

id多到连url都写不下的请求,应该创建task,用GET返回task状态甚至执行进度;

1
2
3
4
反例:POST /accounts/close
POST /closeAccount
正例:POST /accounts/4402278/close
POST /payments/sale/36C38912MN9658832/refund

2.2 Method

  • GET 查询资源
  • POST 创建资源
  • PUT 全量更新资源
  • PATCH 局部更新资源
  • DELETE 删除资源

安全性:任意多次对同一资源操作,都不会导致资源状态的变化。
幂等性:任意多次对同一资源操作,对资源的改变是一样的。

method 操作 安全性 幂等性
GET 查询资源
POST 创建资源
PUT 全量更新资源
PATCH 局部更新资源
DELETE 删除资源
1
2
反例:POST /accounts/4402278/delete
正例:DELETE /accounts/4402278

DELETE方法是幂等的吗?
考虑请求两次的结果对比:

  1. 资源无法删除,这种情况就是幂等的;
  2. 资源可以删除,第二次请求应该返回404,这种情况就不是幂等的。

2.3 Version

通常有三种解决方案:

  1. 在URI中加入版本号。(建议采用)
  2. Accept Header: Accept:v1
  3. 自定义Header

2.4 Status Code

status method desc 解释
200 all(GET POST …) OK 请求成功并返回实体
201 POST PUT Created 请求成功执行,返回创建的资源(URI)
400 all Bad Request 客户端错误,一般是参数错误
401 all Unauthorized 请求需要用户授权
403 all Forbidden 认证通过,但用户没有该权限。
404 all Not Found 资源不存在
500 all Internal Server Error 服务器遇到异常,无法完成请求。
1
2
3
4
反例:GET /accounts/123456 (资源不存在)
响应: HTTP status 200 (ok) with a body saying it's not found
正例:GET /accounts/123456 (资源不存在)
响应: HTTP status 404 (not found)

2.5 Wrapped Response

通常,应该是这样的,响应包含如下属性:

  • code 整数型的HTTP响应吗
  • status “success”,“fail”和“error”。fail表示5xx,error表示4xx,success代表1xx、2xx,3xx
  • message fail和error时才有的消息,表示错误提示。
  • data 响应体。
1
2
3
4
5
6
7
8
9
{
"code": 200,
"status": "success",
"data": {
"lacksTOS": false,
"invalidCredentials": false,
"authToken": "4ee683baa2a3332c3c86026d"
}
}

另外一种方式是只在错误的时候提供这样的响应。


2.6 Date/Time 处理

推荐采用字符串形式,如ISO 8061。Unix Timestamp的可读性较差,不推荐采用。


三 HATEOAS(level 3)

超媒体(Hypermedia)是一组可链接的资源,程序可以通过这些连接操作资源。
HATEOAS的核心是链接(links)

通常采用HATEOAS,是在响应体中加入“links”。”links”属性会包含以下三个子属性:

  • ref 链接说表示的关系
  • herf 链接的资源URI
  • method 访问URI的方法

常用ref属性:

ref属性 描述
self 资源本身
item 资源集合中的单个资源
first next prev last 第一页 …
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
"id": "36C38912MN9658832",
"create_time": "2017-02-19T22:01:53Z",
"update_time": "2017-02-19T22:01:55Z",
"state": "completed",
"amount": {
"total": "7.47",
"currency": "USD"
},
"protection_eligibility": "ELIGIBLE",
"protection_eligibility_type":
"transaction_fee": {
"value": "1.75",
"currency": "USD"
},
"links": [
{
"href": "https://api.paypal.com/v1/payments/sale/36C32",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.paypal.com/v1/payments/sale/36C3/refund",
"rel": "refund",
"method": "POST"
},
{
"href": "https://api.paypal.com/v1/payments/payment/5YK9",
"rel": "parent_payment",
"method": "GET"
}
]
}

HAL(Hypertext Application Language)是HATEOAS的一种实现。
HAL的资源包含三部分:

  • Links (to URIs)
  • Embedded Resources (i.e. other resources contained within them)
  • State (your bog standard JSON or XML data)

HAL Resources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
HTTP/1.1 201 Created
Content-Type: application/hal+json
Location: http://example.org/api/user/matthew
{
"_links": {
"self": {
"href": "http://example.org/api/user/matthew"
}
},
"id": "matthew",
"name": "Matthew Weier O'Phinney",
"_embedded": {
"contacts": [
{
"_links": {
"self": {
"href": "http://example.org/api/user/mac_nibblet"
}
},
"id": "mac_nibblet",
"name": "Antoine Hedgecock"
},
{
"_links": {
"self": {
"href": "http://example.org/api/user/spiffyjr"
}
},
"id": "spiffyjr",
"name": "Kyle Spraggs"
}
],
"website": {
"_links": {
"self": {
"href": "http://example.org/api/locations/mwop"
}
},
"id": "mwop",
"url": "http://www.mwop.net"
},
}
}

通过The HAL Browser 可以探索HAL。


参考

RESTful API设计最佳实践
RESTful API 规范 v1.0
REST anti-patterns
Learn REST: A RESTful Tutorial
PayPal APIs
hal primer
HAL
使用 Spring HATEOAS 开发 REST 服务

上一篇