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

内容概要

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

内容详细

web 开发模式

1、前后端不分离

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

创新互联建站专注于利川企业网站建设,响应式网站建设,商城网站开发。利川网站建设公司,为利川等地区提供建站服务。全流程按需网站设计,专业设计,全程项目跟踪,创新互联建站专业和态度为您提供的服务

2、前后端分离

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

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

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、发送请求

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

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

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

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

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

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/.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/.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 返回单个资源对象 {name:elijah,age:18}
  • post: 新增资源 --》 返回新增的资源对象 {name:elijah,age:18}
  • put: 修改资源(全局修改) --》 返回修改的资源对象 {name:elijah,age:18}
  • patch: 修改资源(局部修改) --》 返回修改的资源对象 {name:elijah,age:18}
  • delete: 删除资源 --》 返回空文档(或者错误文档) {code:100,msg:删除成功}

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、在浏览器中测试接口

11、在 postman 中测试接口

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

获取资源

新增资源:

修改资源:

删除资源:

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 的变化

新的:
老的:

三大认证:

# 三大认证人如何走的
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

文章标题:restful 规范与 drf 的安装使用 & drf 中 APIView 源码分析
分享网址:http://hbruida.cn/article/dsoghih.html