1.首先从RestFul说起
认识RESTful
RESTful API
实用的是如何正确地理解 RESTful架构和设计好RESTful API。
首先为什么要用RESTful结构呢?
大家都知道"古代"网页是前端后端融在一起的,比如之前的PHP,JSP等。在之前的桌面时代问题不大,但是近年来移动互联网的发展,各种类型的Client层出不穷,RESTful可以通过一套统一的接口为 Web,iOS和Android提供服务。另外对于广大平台来说,比如Facebook platform,微博开放平台,微信公共平台等,它们不需要有显式的前端,只需要一套提供服务的接口,于是RESTful更是它们最好的选择。在RESTful架构下
在前后端分离的应用模式里,后端API接口如何定义?
例如对于后端数据库中保存了商品的信息,前端可能需要对商品数据进行增删改查,那相应的每个操作后端都需要提供一个API接口:
- POST /add-goods 增加商品
- POST /delete-goods 删除商品
- POST /update-goods 修改商品
- GET /get-goods 查询商品信息
对于接口的请求方式与路径,每个后端开发人员可能都有自己的定义方式,风格迥异。
是否存在一种统一的定义方式,被广大开发人员接受认可的方式呢?
这就是被普遍采用的API的RESTful设计风格。
2. 名称
Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。维基百科称其为“具象状态传输”,国内大部分人理解为“表现层状态转化”。
RESTful是一种开发理念。维基百科说:REST是设计风格而不是标准。 REST描述的是在网络中client和server的一种交互形式;REST本身不实用,实用的是如何设计 RESTful API(REST风格的网络接口),一种万维网软件架构风格。
3. 总结
综合上面的解释,RESTful架构就是:
- 每一个URL代表一种资源;
- 客户端和服务器之间,传递这种资源的某种表现层;
- 客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
RESTful设计方法总结起来,最终的几点设计方法
1. 路径(Endpoint)
路径又称"终点"(endpoint),表示API的具体网址,每个网址代表一种资源(resource)
(1) 资源作为网址,只能有名词,不能有动词,而且所用的名词往往与数据库的表名对应。
举例来说,以下是不好的例子:
/getProducts
/listOrders
/retreiveClientByOrder?orderId=1
对于一个简洁结构,你应该始终用名词。 此外,利用的HTTP方法可以分离网址中的资源名称的操作。
GET /products :将返回所有产品清单
POST /products :将产品新建到集合
GET /products/4 :将获取产品 4
PATCH(或)PUT /products/4 :将更新产品 4
(2) API中的名词应该使用复数。无论子资源或者所有资源。
举例来说,获取产品的API可以这样定义
获取单个产品:http://127.0.0.1:8080/AppName/rest/products/1
获取所有产品: http://127.0.0.1:8080/AppName/rest/products
2. HTTP动词
对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面四个(括号里是对应的SQL命令)。
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- DELETE(DELETE):从服务器删除资源。
还有三个不常用的HTTP动词。
- PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性)。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
下面是一些例子。
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园(上传文件)
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
关注读取接口 friendships/friends
获取用户的关注列表 friendships/friends/ids
粉丝读取接口 friendships/followers
获取用户粉丝列表 friendships/followers/ids
关系读取接口 friendships/show
3. 过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoos/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
4. 状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。
- 200 OK - [GET]:服务器成功返回用户请求的数据
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
- 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT - [DELETE]:用户删除数据成功。
- 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
- 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
- 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
- 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
接下来我们使用Django来开发一套REST 接口
import json
from django.http import HttpResponse
from django.views import View
from django.http import JsonResponse
from booktest.models import BookInfo
# Create your views here.
# Django基础知识自定义RestAPI接口
# 1. 获取所有图书的数据 GET /books/
# 2. 新增一本图书数据 POST /books/
# 3. 获取指定的图书数据 GET /books/ID/
# 4. 修改指定的图书数据 PUT /books/ID/
# 5. 删除指定的图书数据 DELETE /books/ID/
# /books/
class BookListView(View):
def get(self, request):
"""获取所有图书的数据"""
# 获取所有图书的数据
books = BookInfo.objects.all()
# 将books中每个图书对象转换为字典
books_li = []
for book in books:
book_dict = {
'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 ''
}
books_li.append(book_dict)
# 返回图书列表的json数据,状态码: 200
return JsonResponse(books_li, safe=False)
def post(self, request):
"""新增一本图书数据"""
# 需求: 让客户端通过json数据传递新增图书参数(btitle, bpub_date)
# 获取请求体中原始数据
req_data = request.body # bytes
# 将bytes转换为str
json_str = req_data.decode()
# 将json字符串转换为python字典
req_dict = json.loads(json_str)
# 获取数据并进行校验
btitle = req_dict.get('btitle')
bpub_date = req_dict.get('bpub_date')
# TODO: 省略参数校验的过程...
# 创建并新增图书的数据
book = BookInfo.objects.create(
btitle=btitle,
bpub_date=bpub_date
)
# 返回响应: 状态码 201 新增图书的数据
book_dict = {
'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_dict, status=201)
# /books/ID/
# /books/(?P<pk>\d+)/
# /books/100/
class BookDetailView(View):
def get(self, request, pk):
"""获取指定的图书数据"""
# 根据pk获取对应的图书
try:
book = BookInfo.objects.get(pk=pk)
except BookInfo.DoesNotExist:
# 图书不存在
return HttpResponse(status=404)
# 返回应答: 状态码 200 对应图书数据
book_dict = {
'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_dict)
def put(self, request, pk):
"""修改指定的图书数据"""
# 需求: 让客户端通过json数据传递修改图书参数(btitle, bpub_date)
# 根据pk获取对应图书
try:
book = BookInfo.objects.get(pk=pk)
except BookInfo.DoesNotExist:
# 图书不存在
return HttpResponse(status=404)
# 接收数据并进行参数校验
# 获取请求体中原始数据
req_data = request.body # bytes
# 将bytes转换为str
json_str = req_data.decode()
# 将json字符串转换为python字典
req_dict = json.loads(json_str)
# 获取数据并进行校验
btitle = req_dict.get('btitle')
bpub_date = req_dict.get('bpub_date')
# TODO: 省略参数校验的过程...
# 修改对应的图书数据
book.btitle = btitle
book.bpub_date = bpub_date
book.save()
# 返回应答: 状态码 200 修改图书数据
book_dict = {
'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_dict)
def delete(self, request, pk):
"""删除指定的图书数据"""
# 根据pk获取对应图书
try:
book = BookInfo.objects.get(pk=pk)
except BookInfo.DoesNotExist:
# 图书不存在
return HttpResponse(status=404)
# 删除图书
book.delete()
# 返回应答: 状态码 204 空
return HttpResponse(status=204)
我们可以明确,restful-api开发的流程与主要任务
可以发现,在开发REST API接口时,视图中做的最主要有三件事:
- 将请求的数据(如JSON格式)转换为模型类对象
- 操作数据库
- 将模型类对象转换为响应的数据(如JSON格式)