REST framework 说明和源码剖析
REST的出处
Roy Fielding的毕业论文。
论文地址:Architectural Styles and the Design of Network-based Software Architectures
REST章节:Fielding Dissertation: CHAPTER 5: Representational State Transfer (REST)

REST名称
REST:REpresentational State Transfer = 直接翻译:表现层状态转移。全称是 Resource Representational State Transfer:通俗来讲就是:资源在网络中以某种表现形式进行状态转移。分解开来:
Resource:资源,即数据(前面说过网络的核心)。比如 newsfeed,friends等; Representational:某种表现形式,比如用JSON,XML,JPEG等; State Transfer:状态变化。通过HTTP动词实现。
REST的优点
在互联网开始初期,网页都是前端和后端融在一起的,在这之前的桌面时代问题不大,但是随着近几年互联网的飞速发展,各种类型的客户不断出现,RESTful API可以通过一套统一的接口对不同的客户端提供数据,况且,现在许多平台不需要提供前端页面,只是需要提供数据而已(微信公众号平台等),于是RESTful便是他们最好的选择;REST架构如下:

很明显,如果我们使用上面的这种结构,那么就很容易做到前后端分离的效果,各种客户端可以独自完成前端功能和页面(PC端可以使用vue来搭建项目),需要使用数据的额时候则可以通过api借口向后台获取数据进行渲染
什么是RESTful
REST与技术无关,代表的是一种软件架构风格;
REST从资源的角度审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态,将一切数据视为资源是REST与其它架构风格最基本的属性;
REST本身不实用,使用的是如何设计RESTful API(REST风格的网络接口);
如何设计RESTful API
- 域名 URL root:
- http://api.example.com 尽量将API部署在相同域名(否则会存在跨域问题);
- http://example.org/api/ API;
- 版本 API versioning:
- 可以放在URL里面,可以刻用HTTP的header
- /api/v1/ 放在url中;
- 请求头 跨域时,应发多次请求(解决方案:cors);
- 可以放在URL里面,可以刻用HTTP的header
- 路径URL应该使用名词而不是动词,且土建用复数
- api.example.com/v1/animals 获取动物列表;
- api.example.com/v1/friends 获取某人的好友列表;
- 同一个路径使用HTTP协议里的动词来实现资源的增、删、改、查等操作
- GET 用来获取资源,返回资源对象的列表(数组);
- POST 用来新建资源,返回单个资源对象;
- PUT 用来跟新资源(整条数据都跟新),返回完整的资源对象;
- PATCH 用来跟新资源(更新一条数据的部分信息),返回完整的资源对象;
- ELETE 用来删除资源,返回一个空文档;

- 过滤,通过在url传参的形式传递搜索条件
- api.example.com/v1/friends?limit=10 制定返回记录的数量
- api.example.com/v1/friends?offset=10 制定返回记录的开始位置
- api.example.com/v1/friends?lfriend_type_id=1 指定筛选条件
- 使用正确的HTTP Status Code表示访问状态(详细点击)
- 200 OK - [GET]:服务器成功返回用户请求的数据。
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功;
- 204 NO CONTENT - [DELETE]:用户删除数据成功;
- 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误;
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误);
- 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的;
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功;
基于Django项目创建の基本流程
路由系统
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'users/', views.UserInfo.as_view())
]
CBV视图
from rest_framework.views import APIView
from django.http import HttpResponse
class UserInfo(APIView):
def dispatch(self, request, *args, **kwargs):
"""
请求到来之后,都要执行dispatch方法,dispatch方法根据请求方式不同触发 get/post/put等方法
注意:APIView中的dispatch方法有好多好多的功能
"""
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
return HttpResponse("...")
def post(self, request, *args, **kwargs):
return HttpResponse("...")
# 这里只是做简单的介绍,并不涉及业务逻辑的处理
上面就是就是REST framework框架的基本流程,重要的功能都是在APIView中的dispathch中触发;
REST Framework框架的流程(dispath起)
代码执行顺序从上往下(总体流程,并非每行代码都做解释)
- initialize_request 重新封装Request对象,下面是封装的内容
- reqeust, 封装原request;
- parsers=self.get_parsers(), 解析用户请求数据;
- authenticators=self.get_authenticators(), 认证相关
- negotiator=self.get_content_negotiator(), 选择相关
- parser_context=parser_context 封装self和参数
- self.initial(request, *args, **kwargs) 进行初始化
- version, scheme = self.determine_version(request, *args, **kwargs) 获取版本信息
- self.perform_authentication(request) 用户验证
- self.check_permissions(request) 权限验证
- self.check_throttles(request) 访问次数控制
配置书写的位置
# 在settings中配置这样的字典,里面存放的是我们自定义的配置,可以是版本相关、验证、权限、限制访问次数之类的相关信息;
REST_FRAMEWORK = {
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
"VERSION_PARAM":"version",
"DEFAULT_VERSION":'v1',
"ALLOWED_VERSIONS":['v1','v2'],
"UNAUTHENTICATED_USER":None,
"UNAUTHENTICATED_TOKEN":None,
"DEFAULT_AUTHENTICATION_CLASSES":[
#"app01.views.CustomAuthentication",
],
"DEFAULT_PERMISSION_CLASSES":[
""
],
"DEFAULT_THROTTLE_RATES": {
'anon': '5/m'
}
}
获取版本信息
首先去配置中查看 DEFAULT_VERSIONING_CLASS 中读取类(from rest_framework.versioning import BaseVersioning可以从这个路径里面查看如何获取版本)类的路径(用点分隔,最后一个位置应该是类的名字),读取后实例化该类,并执行类中的determine_version方法,该方法需要返回版本的信息,我们可以从上面的路径中选择出一个类写入配置中,然后按照它的要求在前端进行版本信息的传输,那么默认有几个关于获取版本信息的配置呢,它们又是什么原理呢?下面我们先总体说明下流程后再分析获取版本内容的代码
步骤一:
initial中调用 self.determine_version()方法,拿到一个元组(存放的是版本信息和生成该版本信息的类的对象)后将两个内容分别赋值给新Request中的 version以及versioning_scheme属性
1 def initial(self, request, *args, **kwargs): 2 # Determine the API version, if versioning is in use. 3 version, scheme = self.determine_version(request, *args, **kwargs) 4 request.version, request.versioning_scheme = version, scheme
步骤二:
在determine_version() 方法中判断是否启用版本相关的配置(self.versioning_class),如果没有启用,那么返回元组(None, None),否则的话实例化配置中存放的类,并执行类中的determine_version( )方法
1 versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
1 def determine_version(self, request, *args, **kwargs): 2 """ 3 If versioning is being used, then determine any API version for the 4 incoming request. Returns a two-tuple of (version, versioning_scheme) 5 """ 6 # 如果没有启用版本配置,那么返回元组(None, None) 7 if self.versioning_class is None: 8 return (None, None) 9 # 否则的话实例化配置中存放的类,并执行类中的determine_version()方法 10 scheme = self.versioning_class() 11 # 执行结束后,该方法会返回版本的信息,并和生成该版本信息的类的对象一起封装成元组后返回 12 return (scheme.determine_version(request, *args, **kwargs), scheme)
步骤三:
我们写在配置中的类实例化后执行的 determine_version方法应该做些什么呢,我们可以进去看一下,我们可以打印self.versioning_class来获取下默认的版本配置里面的内容(可能是空),后面的用户验证或者权限、限制访问次数等都是这个套路,里面存放的其实是路劲的字符串,我们可以通过import的方式后按住Ctrl和鼠标左键配合,进入到这个文件中查看各个类的代码
from rest_framework.versioning import BaseVersioning
1 class BaseVersioning(object): 2 """ 3 该类主要用来被作为基类继承并实现; 4 default_version: 配置默认的版本信息? 5 allowed_versions: 配置允许的版本信息 6 version_param: 前端传版本信息时所使用的key? 7 """ 8 default_version = api_settings.DEFAULT_VERSION 9 allowed_versions = api_settings.ALLOWED_VERSIONS 10 version_param = api_settings.VERSION_PARAM 11 12 def determine_version(self, request, *args, **kwargs): 13 # 需要实现这个方法,否则将会报错 14 msg = '{cls}.determine_version() must be implemented.' 15 raise NotImplementedError(msg.format( 16 cls=self.__class__.__name__ 17 )) 18 19 def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): 20 # 调用 _reverse 方法,用来反向生成 URL 21 return _reverse(viewname, args, kwargs, request, format, **extra) 22 23 def is_allowed_version(self, version): 24 # 判断是否是合法的版本,如果没有配置allowed_versions,则合法 25 # 否则判断该版本是否为默认的版本或在允许的版本中 26 if not self.allowed_versions: 27 return True 28 return ((version is not None and version == self.default_version) or 29 (version in self.allowed_versions))
1 class AcceptHeaderVersioning(BaseVersioning): 2 """ 3 从类的名字就可以看出,这个将版本信息放在了请求投中 4 下面是列子的样式 5 GET /something/ HTTP/1.1 6 Host: example.com 7 Accept: application/json; version=1.0 8 """ 9 invalid_version_message = _('Invalid version in "Accept" header.') 10 11 def determine_version(self, request, *args, **kwargs): 12 media_type = _MediaType(request.accepted_media_type) 13 # 下面调用的函数就是去请求头中获取数据 14 # self.version_param 头文件中发送过来的版本信息的key 15 # self.default_version 如果头文件中不存在版本信息,默认值 16 version = media_type.params.get(self.version_param, self.default_version) 17 version = unicode_http_header(version) 18 # 调用is_allowed_version方法,判断版本是否合法 19 if not self.is_allowed_version(version): 20 raise exceptions.NotAcceptable(self.invalid_version_message) 21 return version
其它的类都是大同小异,这里就不全部举例了,从上面的结果很容易看出,如果我们需要使用版本获取的配置的话,只需要在配置中定义上使用哪一个方式后定义好一些必要的参数,然后按照上面的示例就可以完成版本的配置和检验
用户验证
由前面可以知道,我们的流程是先进行封装Request后在执行的用户验证这个方法,其实在封装Request的时候,已经为Reqeust对象封装了一个 self.authenticators 的属性,这个属性我们等会儿会用到,封装详情如下:
initialize_request 调用 get_authenticators ,将它的返回值传入到Request对象中
1 def initialize_request(self, request, *args, **kwargs): 2 """ 3 Returns the initial request object. 4 """ 5 parser_context = self.get_parser_context(request) 6 7 return Request( 8 request, 9 parsers=self.get_parsers(), 10 # 调用self.get_authenticators(), 11 # 在该方法中遍历配置中的存放对象路径的列表 12 # 分别实例化后的列表作为参数传给Request对象 13 authenticators=self.get_authenticators(), 14 negotiator=self.get_content_negotiator(), 15 parser_context=parser_context 16 )
1 def get_authenticators(self): 2 """ 3 Instantiates and returns the list of authenticators that this view can use. 4 """ 5 return [auth() for auth in self.authentication_classes]
1 authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
1 def __init__(self, request, parsers=None, authenticators=None, 2 negotiator=None, parser_context=None): 3 self._request = request 4 self.authenticators = authenticators or () # 存入用户验证相关信息格式 [用来验证的obj, 用来验证的obj]
现在正式进入用户验证方面,在 initial 函数中,我们调用了 self.perform_authentication(request) 方法来帮助我们完成用户验证
1 def initial(self, request, *args, **kwargs): 2 version, scheme = self.determine_version(request, *args, **kwargs) 3 request.version, request.versioning_scheme = version, scheme 4 5 # 进行用户验证 6 self.perform_authentication(request) 7 self.check_permissions(request) 8 self.check_throttles(request)
在 perform_authentication 这个方法中的代码特别简单,就一句 request.user
def perform_authentication(self, request):
request.user
很容易发现,在这个方法中调用了request中的user方法(使用了装饰器 property)
浙公网安备 33010602011771号