在进行API接口设计时,不同的开发人员可能有不同的设计风格,风格迥异。

那是否存在一种统一的接口设计方式,被广大开发人员所接受呢?

答: 这就是被普遍采用的RESTful API设计风格。

1. Restful风格设计-关键点

1. URL路径

路径又称"终点"(endpoint),表示API的具体网址,每个网址代表一种资源(resource)。

(1)URL地址尽量使用名词,不使用动词。

举例来说,以下是不好的例子:

/getProducts /listOrders

对于一个简洁结构,应该始终用名词。

GET /products:将返回所有产品信息

POST /products:将新建产品信息

GET /products/4:将获取产品4

PUT /products/4:将更新产品4

(2)API中的名词应该使用复数,无论单个资源或者所有资源。

举例来说,获取产品的API可以这样定义:

获取单个产品:http://127.0.0.1:8080/products/1

获取所有产品: http://127.0.0.1:8080/products

2. 请求方式

访问同一个URL地址,采用不同的请求方式,代表要执行不同的操作。

常用的HTTP请求方式有下面四个:

请求方式

说明

GET

获取资源数据(单个或多个)

POST

新增资源数据

PUT

修改资源数据

DELETE

删除资源数据

例如:

GET /zoos:列出所有动物园
POST /zoos:新建一个动物园(上传文件)
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息
DELETE /zoos/ID:删除某个动物园

3. 过滤信息

过滤参数可以放在查询字符串中。

在访问API接口获取数据时,可能需要对数据进行过滤。

下面是一些常见的参数:

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。

4. 响应数据

针对不同操作,服务器向用户返回的响应数据应该符合以下规范:

GET /collection:返回资源对象的列表数据。
GET /collection/resource:返回单个资源对象数据。
POST /collection:返回新创建的资源对象数据。
PUT /collection/resource:返回完整的资源对象数据。
DELETE /collection/resource:返回空。

5. 响应数据格式

服务器返回的响应数据格式,应该尽量使用JSON。

6. 响应状态码

服务器向客户端返回的状态码和提示信息,常见的状态码如下:

200 OK - [GET/PUT]:服务器成功返回用户请求的数据
201 CREATED - [POST]:用户新建数据成功。
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

状态码的完全列表参见这里这里

2. Restful风格设计-其他

1. 域名

应该尽量将API部署在专用域名之下。

https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

https://www.example.com/api/

2. 版本

应该将API的版本号放入URL。

http://www.example.com/api/1.0/foo

http://www.example.com/api/1.1/foo

http://www.example.com/api/2.0/foo

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

3. 错误处理

如果状态码是4xx,服务器就应该向用户返回出错信息。

{
    error: "<error message>"
}

4. 超媒体

RESTful API最好做到Hypermedia(即返回结果中提供链接,指向其他API方法),使得用户不查文档,也知道下一步应该做什么。

比如,Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

 

3、REST接口开发的核心任务

分析一下上节的案例,可以发现,在开发REST API接口时,视图中做的最主要有三件事:

  • 将请求的数据(如JSON格式)转换为模型类对象
  • 操作数据库
  • 将模型类对象转换为响应的数据(如JSON格式)

1. 序列化Serialization

在以上操作中,涉及到两个概念:序列化和反序列化。

1)序列化

将程序中的一个数据结构类型转换为其他格式(字典、JSON、XML等),例如将Django中的模型类对象转换为JSON字符串,这个转换过程我们称为序列化。

如:

queryset = BookInfo.objects.all()
book_list = []
# 序列化
for book in queryset:
    book_list.append({
        'id': book.id,
        'btitle': book.btitle,
        'bpub_date': book.bpub_date,
        'bread': book.bread,
        'bcomment': book.bcomment,
        'image': book.image.url if book.image else ''
    })
return JsonResponse(book_list, safe=False)

2)反序列化

将其他格式(字典、JSON、XML等)转换为程序中的数据,例如将JSON字符串转换为Django中的模型类对象,这个过程我们称为反序列化。

如:

json_bytes = request.body
json_str = json_bytes.decode()

# 反序列化
book_dict = json.loads(json_str)
book = BookInfo.objects.create(
    btitle=book_dict.get('btitle'),
    bpub_date=book_dict.get('bpub_date')
)

通过上节课的例子可以看到,在开发REST API时,视图中要频繁的进行序列化与反序列化的操作。

注:维基百科中对于序列化的定义。

2. 总结

在开发REST API接口时,我们在视图中在做的最核心的事是:

  • 将数据库数据序列化为前端所需要的格式,并返回;
  • 将前端发送的数据反序列化为模型类对象,并保存到数据库中。