参考来源: RESTful规范, RESTful API 设计指南
一、什么是restful
-
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
-
REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态
-
所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性
-
对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)
总结就是:它就是一种规范,面向资源编程(把网络中所有东西,想象成资源)
1.1、规范(10条)
# 先定义好标准, 配置域名后, 根据请求类型,定义好路径,写好api版本,过滤掉非法字符串,返回结果需要定义好错误类型以及状态码,最后不管正常与否返回URL
-
API与用户的通信协议,总是使用HTTPs协议: https比http安全
-
域名:
https://api.example.com 尽量将API部署在专用域名(会存在跨域问题)
https://example.org/api/ API很简单
例如写一个查询所有图书的api接口:https://api.example.com/books
https://127.0.0.1/api/books
- 版本: 每个接口都应该有版本
https://api.example.com/v1/
https://127.0.0.1/api/v2/books (推荐用这种)
如果写到请求头 跨域时,引发发送多次请求
- **路径:**视网络上任何东西都是资源,均使用名词表示(可复数)
https://api.example.com/v1/books
https://api.example.com/v1/animals
https://api.example.com/v1/employees
不能这么写:
- 获取所有图书:https://127.0.0.1/api/get_all_books
- 新增一本书: https://127.0.0.1/api/add_book
统一都用这个: https://api.example.com/v1/books
- method: 请求方式
方法 | 说明 |
---|---|
GET | 从服务器取出资源(一项或多项) |
POST | 在服务器新建一个资源 |
PUT | 在服务器更新资源(客户端提供改变后的完整资源) |
PATCH | 在服务器更新资源(客户端提供改变的属性) |
DELETE | 从服务器删除资源 |
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:删除某个指定动物园的指定动物
- 过滤: 通过在url上传参的形式传递搜索条件
https://api.example.com/v1/zoos?limit=10
# 常见参数
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置
?page=2&per_page=100:指定第几页,以及每页的记录数
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
?animal_type_id=1:指定筛选条件
# 参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的
-
状态码 , 状态码-rfc2616
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 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格式)。 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
-
错误处理
# 如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可, 比如: { error: "Invalid API key"}
-
返回结果
# 针对不同操作,服务器向用户返回的结果应该符合以下规范。
GET /books:返回资源对象的列表(数组)
GET /books/1:返回单个资源对象
POST /books:返回新生成的资源对象 -新增,传数据,一旦新增完成,把新的资源对象返回
PUT /books/1:返回完整的资源对象
PATCH /books/1:返回完整的资源对象
DELETE /books/1:返回一个空文档
- Hypermedia API
# RESTful API最好做到 Hypermedia, 即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
{status:100, "msg": "成功" "url": "127.0.0.1/books/1"}
核心:返回结果中提供链接
- 总结规范行为
HTTP 方法 | 行为 | 示例 |
---|---|---|
GET | 获取资源的信息 | http://example.com/api/orders |
GET | 获取某个特定资源的信息 | http://example.com/api/orders/123 |
POST | 创建新资源 | http://example.com/api/orders |
PUT | 更新资源 | http://example.com/api/orders/123 |
DELETE | 删除资源 | http://example.com/api/orders/123 |
二、源码分析
2.1、CBV源码分析
- url
from django.urls import path
from app01.views import Cbv_view
urlpatterns = [
path("cbv/", Cbv_view.as_view(), name="cbv"),
]
class Cbv_view(View):
def get(self, request):
# django 中 request的属性, 注意后续与rest中request的属性
# django.core.handlers.wsgi.WSGIRequest
print(type(request))
return HttpResponse("get")
- View
class View:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
- as_views
# 类属性
@classonlymethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
# 执行:dispatch:谁的dispatch方法?写的cbv的那个c,视图中的那个视图类
# 比如访问时是get, 那么就直接返回GET值
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
- dispath
def dispatch(self, request, *args, **kwargs):
# request.method 前台请求的方法,转成了小写
# http_method_names View中定义的一个列表:是一堆请求方式
if request.method.lower() in self.http_method_names:
# getattr的第三个参数是默认值:self.http_method_not_allowed
# 拿到get方法的内存地址
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
- CBV 总结
url获取method请求, 到 as_view() --> view(闭包) --> dispatch --> 分发到不同的函数、执行函数、拿到结果、并返回
2.2、DRF
全称: djangorestframework,做为一个app,要使用就需要在sessing中注册app
-
使用步骤
# 三步曲 1、安装drf, pip install djangorestframework 2、app中注册, INSTALLED_APPS = ['rest_framework'] 3、导入模块, from rest_framework.views import APIView 4、继承使用, class Test(APIView): pass
2.2.1、request源码分析
-
urls
from app01.views import Test urlpatterns = [ path("test/", Test.as_view(), name="cbv"), ]
-
views
class Test(APIView): def get(self, request): # <class 'rest_framework.request.Request'> return HttpResponse("ok")
-
ApiView分析
-
as_view函数
# 注意: @classmethod def as_view(cls, **initkwargs): # 这里是ApiView的as_view # 这里继承是父类的as_view方法,又回到 View/as_view方法中 view = super().as_view(**initkwargs) view.cls = cls view.initkwargs = initkwargs # 当继承ApiView之后会局部禁用CSRF方法 return csrf_exempt(view) # view/as_view @classonlymethod def as_view(cls, **initkwargs): def view(request, *args, **kwargs): return self.dispatch(request, *args, **kwargs) # 根据mro继承关系, 此时的dispatch就应该是父类的dispatch,也就是ApiView的
-
-
ApiView/dispatch
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
# 往下看源码,看完在回到try中
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# 在 ApiView/initialize_request 下看 initial方法,这里处理了各种组件
# 比如认证,权限,频率相关
self.initial(request, *args, **kwargs)
# 注意看这, apiview的dispath其实与view的一样,都是分发获取头部信息
# 处理完之后直接dispatch分发
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
# 。。。 在这 dispatch方法 也就是 分发到不同的函数
return self.response
- ApiView/initialize_request
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
# 返回的是包装了 django request的方法
return Request(
request,
# 解析使用
parsers=self.get_parsers(),
# 认证组件
authenticators=self.get_authenticators(),
# 分页组件
negotiator=self.get_content_negotiator(),
# 解析组件
parser_context=parser_context
)
# request头部包装好之后,回上一步
- ApiView/initial
def initial(self, request, *args, **kwargs):
# 认证组件
self.perform_authentication(request)
# 权限组件
self.check_permissions(request)
# 频率组件
self.check_throttles(request)
- 总结
url获取method请求, 到 apiview/as_view() --> view/as_view --> view --> apiview/dispatch --> self.initialize_request --> self.initial --> method 分发到不同的函数、执行函数、拿到结果、并返回
2.2.2、数据获取源码分析
- 继承方法
class Test(APIView):
def get(self, request):
# http://127.0.0.1:8001/test/?name=xiong
# <class 'rest_framework.request.Request'>
print(request.data) # {}
print(request.GET) # <QueryDict: {'name': ['xiong']}>
return HttpResponse("ok")
- 当使用点号获取实例属性时
from rest_framework.request import Request
# __getattr__:当使用点号获取实例属性时,如果属性不存在就自动调用__getattr__方法。
def __getattr__(self, attr):
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
- request.GET
# 如果想使用 request.GET 那么可以使用 query_params方法来调用
@property
def query_params(self):
"""
More semantically correct name for request.GET.
"""
return self._request.GET
- 总结
-request.data 是个方法,包装成了属性,前台传过来body体中数据的数据,放在里面
-request.query_params 这个是原来GET中的数据
-request把原来的request包装进去了