DRF源码分析
1.预备知识
1.1CBV(class based view)和FBV(function based view)
写FBV的时候的url是这样写的,url(r'^login/', views.login),后面不能加(),加上括号后,django在加载的过程中就会执行函数。
我们工作中用到的更多是CBV,所以我们一般写接口的时候都写成CBV的。当我们发送get或者post请求时,django是怎么识别是get请求还是post请求的?
re_path('login/', views.LoginView.as_view())
from django.views import View
from django.shortcuts import render, HttpResponse
class LoginView(View):
def get(self, request):
return render(request, 'login.html')
def post(self, request):
return HttpResponse("Ok")
路由之所以写成 url(r'^login/', views.LoginView.as_view()), 是因为执行了LoginView类中的as_view的方法。如果LoginView类中没有,就从父类中寻找,我们点进去View,找到as_view方法,中间的源码部分看不懂的直接跳过,直接看返回的是什么?他返回的是view。又找到view方法,返回的是self.dispatch方法。

然后再self.dispatch中中执行了相应的请求函数。

此时 handler 就是get、post等请求方式,然后执行 LoginView 的 get、post 等方法
1.2 classmethod 和 classonlymethod
在学习面向对象时,我们学习过classmethod,它是一个装饰器,表示允许类直接调用该方法而不用传入实例对象,如下代码所示:
from django.utils.decorators import classonlymethod
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def show_info(self):
print("show info method been executed")
@classmethod
def show_info2(cls):
print("show info2 method been executed")
@classonlymethod
def show_info3(cls):
print("show info3 method been executed")
p1 = Person("pizza", 18)
# 普通方法可以通过实例对象和类调用
# 通过实例调用时默认传入self作为第一个参数,不需要手动传入参数
# 通过类调用时,必须手动指定实例对象传递给该方法
p1.show_info()
Person.show_info(p1)
# 被classmethod装饰器装饰后的方法可以通过实例对象和类直接调用
# 默认传入类名作为第一个参数
p1.show_info2()
Person.show_info2()
# 被classonlymethod装饰器装饰后的方法只能通过类调用
p1.show_info3() # 报错,Django框架实现了classonlymethd,不再允许实例对象调用被该装饰器装饰的方法
Person.show_info3()
1.3 hasattr、getattr、setattr
同样的,在学习面向对象时,我们学习过属性判断、属性查找、属性设置,接下来,再回顾一下:
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
self._hobby = 'girls'
def show_info(self):
print("show info method been executed")
p1 = Person("pizza", 18)
# 查看该对象是否有show_info方法
print(hasattr(p1, "show_info"))
# 查看该对象是否有age属性
print(hasattr(p1, "age"))
print(hasattr(p1, "hahaha"))
greeting = "Hello World"
# 设置属性
setattr(p1, "hahaha", greeting)
print(hasattr(p1, "hahaha"))
func = getattr(p1, "show_info")
print(func) # <bound method Person.show_info of <__main__.Person object at 0x102219d68>>
# 注意:直接调用,不需要传入self,getattr时已经绑定self到func了
func()
print(hasattr(Person, "show_info")) # True
print(hasattr(Person, "name")) # False
print(hasattr(Person, "country")) # False
# 给Person类设置属性
setattr(Person, "country", "china")
print(hasattr(Person, "country")) # True
print(hasattr(p1, "country")) # True
1.4 self 定位
class Request(object):
def show_request(self):
print(self)
class NewRequest(Request):
def show_request(self):
print(self)
request = NewRequest()
# 调用方法的实例对象是哪个,self就指向哪个对象
request.show_request()
1.5 Http请求协议
Http请求报文包含三部分:分别是请求行、请求报文头、请求报文体
POST /classbasedview/login/ HTTP/1.1 Host: 127.0.0.1:9001 Connection: keep-alive Content-Length: 114 Cache-Control: max-age=0 Origin: http://127.0.0.1:9001 Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Referer: http://127.0.0.1:9001/classbasedview/login/ Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: csrftoken=xPHeedcs8duBuCv031bCvsG1zMX1aGNpKlsPdCkhQHICd4lvMeHIEwkpJUQbLiOl; is_login=True; username=alex; last_time="2018-11-24 13:37:33"; fruit=xiangjiao
注意:post请求类型有formdata,而get没有,下面详细介绍几个常见的请求头信息:
- POST /classbasedview/login/ HTTP/1.1:第一行表示请求行,分别是请求方式(POST),请求URL,HTTP协议版本
- HOST:请求行下面的都是请求报文,HOST表示请求的主机和端口号
- Connection:请求连接方式
- Content-Length:内容长度(字节)
- Content-Type:请求数据的编码协议,只有POST,DELETE等请求方式有该部分内容(需重点记忆)
- User-Agent:客户端信息
- Accept:可接受的响应内容类型
- Accept-Encoding:采用什么方式对请求数据进行编码
- Cookie:服务器端设置的cookie
下面是请求报文体:
csrfmiddlewaretoken=EqHslTSOeI6TWMXwmFCTuLLeflkjWSJTgUdLeFw1Xtxp5S1ea8vYo3IOX7DEPnO4&username=pizzali&password=aaa
不同的Content-Type,请求报文体的格式不一样,application/x-www-form-urlencoded使用&符合来拼接请求的键值对,最后,本质上,还是通过socket将以上内容编码成字节数据,发送到服务器端,服务器则根据Content-Encoding先将数据整体解码,之后再通过Content-Type指定的编码协议来读取请求数据。
通俗一点理解,我们可以使用任何连接符(&)或者(……),只要服务器端还客户端互相认可即可。因为协议就是为了方便沟通双方进行信息交互的。
2、restful协议
restful与技术无关,代表的是一种软件架构风格,是represontational state transfer的简称。
所有的数据,不管是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性,
面向资源架构(ROA:Resource Oriented Architecture)
http协议请求方式:GET 、POST、DELETE、PUT、PATCH、OPTION、HEAD
restful使用不同的请求方式去做url的分支
GET
books-------->查看数据--------------------> 返回所有数据列表 :[{},{},{}]
POST
books-------->添加数据--------------------->返回添加数据 :{}
PUT
books/1------->更新pk=1的数据--------------->返回更新后的数据: {}
Delete
books/1-------> 删除pk=1的数据-------------->返回空
GET
books/1------->查看单条数据 --------------->返回查看的单条数据 {}
url唯一代表资源,http请求方式来区分用户行为
3、 Django RestFramework (DRF)
Django RestFramework是基于restful协议的一种框架。可以把它看成一个app。
先下载Django RestFramework,pip install djangorestframework
(1) APIView
(2) 序列化组件
(3) 视图类(mixin)
(4) 认证组件
(5) 权限组件
(6) 频率组件
(7) 分页组件
(8) 解析器组件
(9) 响应器组件
(10) url控制器
介绍restframework之前,我们先不用restframework写一个接口。
基于CBV的接口设计:
from .models import Course
import json
class CourseView(APIView):
def get(self,request):
course_list = Course.objects.all()
ret = []
for course in course_list:
ret.append({
"title":course.title,
"desc":course.desc,
})
return HttpResponse(json.dumps(ret,ensure_ascii=False))
基于DRF的接口设计
from rest_framework.views import APIView
class CourseView(APIView):
def get(self,request):
course_list = Course.objects.all()
ret = []
for course in course_list:
ret.append({
"title":course.title,
"desc":course.desc,
})
return HttpResponse(json.dumps(ret,ensure_ascii=False))
使用DRF框架做接口会更简单,因为DRF框架中还会有一些其他的作用,比如说认证,权限,频率的功能,DRF框架都帮我们做了。在这个例子中体现不出来,因为这个例子中接口单一,结构简单。
4、APIView源码分析
同样当用户访问 url(r'^course/', views.CourseView.as_view()), 执行了CourseView类中的as.view方法,如果没有,就从父类APIView中找as.View方法,

APIView中的dispatch方法和刚才说的CBV中的dispatch方法不一样:

此时还是返回get、post之类的请求方式,然后执行 CourseView 类里的get、post之类的方法
5、数据编码格式
我们在提交post请求时,默认发送的是urlencode的数据编码格式 
是不能发送json数据编码格式给后端的,但是我们可以利用ajax发送json数据。
如何发送json数据到服务器?
用form表单发送请求时默认发送的是urlencod的编码格式:csrfmiddlewaretoken=9zhMjaMwYn2gPndPUueZdzzrALk04cbeoRvSefQKqUJW1rQnMP2wuRh1I0WN58Bd&user=yangbo&pwd=123。
这种编码格式发送过去之后django默认会解析这种编码格式。
那么我们再来用ajax发送请求:
$('.btn').click(function () {
$.ajax({
url:"",
type:'post',
data:{
user:'alex',
pwd:'123',
},
success:function (data) {
console.log(data)
}
})
})
我们要明白一件事情,就是这样发送的数据依然默认是urlencode编码格式。
JS Python
相当于
JSON.Stringfy(data) ============ json.dumps(data)
JSON.parser(data) ============ json.loads(data)
$('.btn').click(function () {
$.ajax({
url:"",
type:'post',
contentType:"json",
data:JSON.stringify({user:'alex',
pwd:'123',
}),
success:function (data) {
console.log(data)
}
})
})
这样发过去就是json数据的编码格式了。
数据虽然发送过去了,而且django也接受到了, 但是django只是默认解析urlencode编码格式,但是对于json的编码格式,django不会解析。所以他接受到的是空。
Django默认解析器:
if contentType:application/x-www-form-urlencoded:
user=yuan&pwd=123&a=1 -----》request.POST={"user":"yuan","pwd":123,"a":1}
else:
request.POST={}
那么在后台怎么知道数据发送过来呢?
通过request.body来拿。request.body里面放的是请求体里面的原生数据。


拿到 json格式的字节串了,那么我们怎么拿到数据呢?

但是当我们的courseview视图类函数继承了APIView,在APIView中重新封装了request ,所以不管是urlencode还是json数据的编码格式通过request.data都能够解析出来:
基于DRF的接口设计
from rest_framework.views import APIView
class CourseView(APIView):
def get(self,request):
course_list = Course.objects.all()
ret = []
for course in course_list:
ret.append({
"title":course.title,
"desc":course.desc,
})
return HttpResponse(json.dumps(ret,ensure_ascii=False))
def post(self,request):
print(request.data)
return HttpResponse('post.....')
6、DRF解析器
先说明DRF支持三种格式编码的解析,from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
点进去源码我们可以看到DRF中的解析器,默认支持三种:JSONParser,FormParser,MultiPartParser

源码部分:
当我们发送post请求时,request.data会在这一步进行解析数据。 所以关键的地方在.data部分。data是request的静态方法。所以我们找源码的时候应该找request中的静态方法data。我们都知道这个request是在APIView中的dispatch方法重新封装了。所以应该去APIVew中找dispatch方法,需要找到request怎么被重新封装的。

点进源码:

点进Resquest源码,找data静态方法:

这一步就是数据解析的过程,点进去源码,

点进源码self._parse:

点进源码self.parsers,

这就又回到实例化request的时候了。

点进去源码:

这里这个self,一层一层往上找,就找到了这个self.指的是视图类函数。所以在试图类函数中,定义了哪些解析器,他就支持哪些解析器。

那如果在我们的试图类函数中没有定义这个变量,那他是怎么走默认的解析器?
那我们点进parser_classes源码:

当我们点进api_settings中发现,api_settings是一个类的实例化对象,
但是在这个类中并没有DEFAULT_PARSER_CLASSES方法。
这里查一个知识点,在python基础中,一个类实例化后会进行初始化,执行__init__方法,但对于实例化没有的属性会走__getattr__方法。

所以,没有DEFAULT_PARSER_CLASSES方法,会走__getattr__方法,

而在我们的DEFAULTS中有DEFAULT_PARSER_CLASSES,

接下来:

点进去settings,找REST_FRAMEWORK,找不到就去全局settings中找。找不到就去默认字典字典中找,如果还是找不到,就赋值为空字典。



在最后的val就是 JSONParser,FormParser,MultiPartParser


浙公网安备 33010602011771号