restful 规范与 drf 的安装使用 & drf 中 APIView 源码分析

内容概要

  • web 开发模式
  • API 接口
  • postman 测试软件的使用
  • restful 规范
  • drf 的安装与使用
  • cbv 的 View 源码分析
  • APIView 源码分析
  • drf 的 Request 类
  • drf 的 APIView 类执行过程

内容详细

web 开发模式

1、前后端不分离

在开发一个网站的过程中,前端页面需要使用后端框架的模板语法(DTL) 来渲染,比如 Django 自带的模板语法或者 jinjia2,这种前端页面在后端渲染完成之后才会把页面文档传送给前端

image

2、前后端分离

目前主流的开发模式,这种模式中前端先在后端的静态文件服务器(nfs)中获取静态文件(static 中的HTML、CSS、JS代码),需要数据的时候向后端发送请求只获取json格式的数据,再在前端运用js的BOM与DOM操作对页面进行渲染。

这样便实现了前后端分离,前端只需要从后端获取一定格式的数据,那么前端就可以是一个网页、app或者小程序,提高开发水平。

image

API 接口

简介: api 接口是前后端交互数据的媒介

api 接口包含:

  • 1、url 地址 : 向后端请求数据的地址
    • https://api.example.com
  • 2、请求方式 : get、post、put、patch、delete
  • 3、请求参数 :在 url 地址后面紧跟着的 ?name='elijah',可以看作是过滤条件
    • https://api.example.com/v1/zoos?limit=10
  • 4、响应结果 :包括响应首行(响应状态码)、响应头、响应体(json格式数据)

postman 测试软件的使用

过去我们开发网站开放出来的接口一般使用浏览器发送get请求来进行测试,但基于api接口的restful设计规范,我们还需要使用 post请求、put请求等,而浏览器只能发送 get请求

postman 是一种测试接口共组,在 postman 中可以满足我们的测试需求

市面上也有许多测试软件(postwoman),我们使用主流的postman

下载地址https://www.getpostman.com/downloads/

使用:

1、发送请求

image

前后端数据交互的编码格式:

  • urlencoded :正常的post请求提交数据:name=elijah
  • formdata :post请求上传文件:带文件二进制形式
  • json :在body体中的数据格式为:{"name":"elijah","age":18} (用的多)

发起请求时,可以在 body 中书写请求体:

image

image

2、建立集合批量发起测试

image

image

image

同时,这个批量测试也支持导出文件,然后发送给别人,再从文件中导出

image

restful 规范

REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。它首次出现在2000年Roy Fielding的博士论文中。

在前后端分离的开发模式中,为了方便数据交互,我们在设计前后端进行数据交互的 api 接口时,需要符合 restful 规范,它是一种写前后端分离的标准

对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)

10条 restful 规范:

1、通信协议一般使用 https 协议,为保障数据安全

2、设计的接口地址是可以让人一眼看出来是 api 接口(在接口中带api字眼)

  • https://api.baidu.com/books
  • https://127.0.0.1:8080/api/books

3、多数据版本共存

接口更新之后,老版本的接口依旧一起使用,等过了期限才弃用老版本(维护老版本接口),以便可以留存更多用户。

  • https://api.baidu.com/v1/login
  • https://api.baidu.com/v2/login

4、交互数据即是资源,使用名词(可以是复数)

  • https://api.baidu.com/v1/users
  • https://api.example.com/v1/animals

不建议使用动词,如 get_resource ,除非是 login 或者 register 这种接口

  • https://api.baidu.com/v2/login

5、用不同的请求方式表示不同的资源操作

  • get: 获取资源
  • post: 新增资源
  • put: 修改资源(全局修改)
  • patch: 修改资源(局部修改)
  • delete: 删除资源

6、过滤,通过在url上传参的形式传递搜索条件

  • https://api.example.com/v1/zoos?limit=10:指定返回记录的数量
  • https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置
  • https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数
  • https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
  • https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件

7、响应状态码(http的响应状态码,响应体的json数据中带状态码)

http 协议的状态码:

更多状态码请参考: https://www.cnblogs.com/elijah-li/p/16069638.html

 1xx:请求正在处理
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格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

更多状态码请参考: https://www.cnblogs.com/elijah-li/p/16069638.html

服务端自定义的状态码

响应体中返回json格式数据,数据中有 服务端自定义的状态码(code,status)

  • 1001 :用户名错误
  • 1002 :没有权限

8、返回错误信息(错误处理)

返回数据是json格式,带状态码和错误信息

 {
code: 1002
error: "Invalid API key"
}

9、不同的请求方式(不同操作)应该返回不同的资源

  • get:/collection/resources 获取多个资源 --》 返回资源对象的列表 [{name:elijah,age:18},{name:pyy,age:33}]
  • get /collection/resource 返回单个资源对象
  • post: 新增资源 --》 返回新增的资源对象
  • put: 修改资源(全局修改) --》 返回修改的资源对象
  • patch: 修改资源(局部修改) --》 返回修改的资源对象
  • delete: 删除资源 --》 返回空文档(或者错误文档)

10、返回资源中要跳转的链接地址

 {"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos", '''连接地址'''
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}

drf 的安装与使用

1、安装 djangorestframework

 pip install djangorestframework==3.10.3 

2、在 settings.py 文件中 注册 rest_framework

 INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'rest_framework',
]

3、在 models.py 中写表模型

 class Book(models.Model):
name = models.CharField(verbose_name='书名', max_length=32)
price = models.DecimalField(verbose_name='价格', max_digits=5, decimal_places=2)
author = models.CharField(verbose_name='作者', max_length=32)

class Meta:
verbose_name_plural = '书籍表'

4、新建一个序列化类

在应用文件夹下创建一个 ser.py 作为序列化模块

 from rest_framework.serializers import ModelSerializer
from app01.models import Book


class BookModelSerializer(ModelSerializer): # 继承的是 ModelSerializer
class Meta:
model = Book
fields = '__all__' # 注意别漏写 fields

5、书写视图函数(CBV)

 from rest_framework.viewsets import ModelViewSet
from .ser import BookModelSerializer, Book


# Create your views here.


class BookModelViewSet(ModelViewSet): # 继承的是 ModelViewSet
queryset = Book.objects.all()
serializer_class = BookModelSerializer

6、书写 url

 from app01 import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('books', views.BookModelViewSet)


urlpatterns = [
path('admin/', admin.site.urls),
]
urlpatterns += router.urls

9、迁移数据库

 python manage.py makemigrations
python manage.py migrate

10、在浏览器中测试接口

image

11、在 postman 中测试接口

注意: 在发送 put 请求(修改资源)、patch 请求(修改资源)、delete 请求(删除资源)时,得在名词后面添加具体的资源 id 值, 并且要加上 “/” ,因为 postman 不会自动加斜杆

获取资源

image

新增资源:

image

修改资源:

image

删除资源:

image

cbv 的 View 源码分析

从路由文件 urls.py 的调用语句中看起

 urlpatterns = [
path('CBV/', views.MyClass.as_view()),
]
# as_view() 函数加括号优先调用 as_view函数

按住 ctrl 键,鼠标点击查看 as_view 源码

 @classonlymethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs) # self 是我们自定义类产生的对象
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'): # 自定义类必须接收 request 对象,否则主动报错
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs) # view 函数调用 View 类中的dispath函数
# ... 中间省略部分源码
return view # 返回view函数对象

从源码中可以看出,as_view 调用之后得到的是 view 函数对象,这是个闭包函数,当视图函数被触发,调用的是 view函数,view函数return一个 dispatch 函数调用结果

我们再查看 dispatch 的源码:

 http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']


def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names: # 如果是request中的属性,则用反射调用自身的响应的函数
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)

APIView 源码分析

APIView 是 rest_framework.views 中的一个类,是 drf 提供给我们更多功能的视图函数类,也继承了 Django 的 View

基于 CBV 先定义一个视图函数

views.py :

 from rest_framework.views import APIView

class BookAPIView(APIView):
def get(self, request):
pass

def post(self, request):
pass

urls.py :

 urlpatterns = [
path('admin/', admin.site.urls),
path('book/',views.BookAPIView.as_view()) # 调用 as_view() 返回的是 view 函数的内存地址
]

调用的是 APIView 的 as_view 方法:

APIView 类继承的是 Django 的 View,所以 super().as_view(**initkwargs) 调用了父类(View)中的 as_view,返回的是 View 中 as_view 返回的 view,在 view 函数里调用的 self.dispatch(request, *args, kwargs) 根据类的查找顺序,调用的就是 APIView 中的 dispatch

 class APIView(View):
@classmethod
def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs)
view.cls = cls
view.initkwargs = initkwargs
return csrf_exempt(view) # 所有继承了 APIView 的视图函数都忽略了 csrf 认证
  • 调用了 APIView 的 dispatch方法
  • 忽略了视图函数的 csrf 认证
 csrf_exempt(index) 相当于
@csrf_exempt
def index(request)
pass
# index=csrf_exempt(index)

APIView 中的 dispatch:

 def dispatch(self, request, *args, **kwargs):
# 重写了 Django 传过来的 request 请求数据对象,给request增添了新属性
request = self.initialize_request(request, *args, **kwargs)
self.request = request

try:
# 进行三大认证
self.initial(request, *args, **kwargs)
# 反射机制调用视图函数类中的方法,与原生 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)
except Exception as exc:
response = self.handle_exception(exc)

# 处理放回数据,具有 drf 特色的页面
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response

APIView 中的 initialize_request:

对原生的 request 进行处理,新增了一些功能和属性

 def initialize_request(self, request, *args, **kwargs):
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)

initialize_request 中的 Request():

把原生的 request 赋给了 _request,原 request 请求对象的方法在 _request 中获取就可以

 class Request:
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):

self._request = request
self._data = Empty
 request 的变化

新的:<class 'rest_framework.request.Request'>
老的:<class 'django.core.handlers.wsgi.WSGIRequest'>

三大认证:

 # 三大认证人如何走的
self.initial(request, *args, **kwargs)---》APIView的
核心代码:
self.perform_authentication(request) # 认证
self.check_permissions(request) #权限
self.check_throttles(request) # 频率

drf 的 Request 类

重写了 request 请求数据对象之后,原生的 request 方法一样能用,因为 Request 类中的 getattr 方法(对象的 '.' 点拦截)中用放射机制获取了原生 request 的所有属性

 Request --> __getattr__

def __getattr__(self, attr):
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)

前端post请求传入的数据,在原来的request.POST中只能处理urlencoded和formdata编码格式,json格式不能处理。

而 Request 类实例化之后,提供了一个 data 方法,无论前端用什么编码post提交的数据,都从 request.data 中获取。

 @property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data

标签: python

添加新评论