文章目录

  • 一、django rest framework 框架的介绍
    • 1.什么是RESTful规范?
    • 2.RESTful API的介绍
  • 二、drf框架源码解读
    • 1.drf框架的使用
    • 2.APIView源码分析
    • 3.perform_authentication源码分析
      • - authentication函数使用
      • - authentication函数全局中使用
    • 4.check_permissions源码分析
      • - permissions函数使用
      • - permissions函数全局中使用
    • 5.check_throttles源码分析
      • - throttles函数使用
      • - throttles函数全局中使用
  • 三、drf框架内置认证类
    • 1.内置BaseAuthentication类
    • 2.内置BasePermission类
    • 3.内置BaseThrottle类
      • - SimpleRateThrottle类

一、django rest framework 框架的介绍

Django REST framework 是用于构建 Web API 的强大而灵活的工具包。

1.什么是RESTful规范?

  • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”。

  • REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态。

  • 所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性。

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

2.RESTful API的介绍

API的意思在本质上是一个或多个路由组成,通过RESTful规范定义规则,当需从该API中获取数据时,需通过相应路由url中的视图函数的校验且需遵循drf定义的规范。那么,知道这些后,我们设置API时需要注意的问题如下:

  1. 尽量将API部署在专用域名(会存在跨域问题,解决方法jsonp等…

  2. 定义接口一般在域名后添加/api/v(\d+)/(表示更新api的版本)

  3. 路径,视网络上任何东西都是资源,均使用名词表示(可复数)

  4. 在视图函数返回请求中定义规则

    • GET :从服务器取出资源(一项或多项)
    • POST :在服务器新建一个资源
    • PUT :在服务器更新资源(客户端提供改变后的完整资源)
    • PATCH :在服务器更新资源(客户端提供改变的属性)
    • DELETE :从服务器删除资源
  5. 过滤,通过在url上传参的形式传递搜索条件(类似在url后面添加?page=1&type=“list”)获取参数值。

  6. 错误处理,状态码是4xx时,应返回错误信息,error当做key。

    {error: "Invalid API key"
    }
    
  7. 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范。

    1.GET /collection:返回资源对象的列表(数组)
    2.GET /collection/resource:返回单个资源对象
    3.POST /collection:返回新生成的资源对象
    4.PUT /collection/resource:返回完整的资源对象
    5.PATCH /collection/resource:返回完整的资源对象
    6.DELETE /collection/resource:返回一个空文档
    
  8. 将获取到的参数,添加上该参数全部类似、相同信息的url地址

    {"link": {"rel":   "collection https://www.example.com/zoos","href":  "https://api.example.com/zoos","title": "List of zoos","type":  "application/vnd.yourformat+json"
    }}
    
  9. 状态码

    说明
    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格式)。
    422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
    500 INTERNAL SERVER ERROR - [*] 服务器发生错误,用户将无法判断发出的请求是否成功。

摘自:https://www.cnblogs.com/wupeiqi/articles/7805382.html

首先先安装一下drf框架

pip install djangorestframework

明白这些后我们就可以来尝试构造一个api来实现调用(使用上,推荐使用CBV,因为通过继承和封装、重写父类、调用父类更加灵活)

二、drf框架源码解读

要想深刻的理解drf那么阅读源码是必须的,所以接下来我们将通过阅读源码的方式来解释一下drf的一下原理和规范是如何定义的、通过什么定义的。首先我们先创建django项目。

  1. 通过命令python manage.py startapp api创建一个名为api的项目。
  2. 创建数据库(这里就使用sqlite3)
  3. 创建表结构

1.drf框架的使用

api/models.py如下:

from django.db import models# Create your models here.
class UserInfo(models.Model):user_type_choices = ((1, '普通用户'),(2, 'VIP'),(3, 'SVIP'),)user_type = models.IntegerField(choices=user_type_choices)username = models.CharField(max_length=32, unique=True)password = models.CharField(max_length=64)class UserToken(models.Model):user = models.OneToOneField(to="UserInfo", on_delete=models.CASCADE)token = models.CharField(max_length=64)

此时在命令行写入

1.python manage.py makemigrations命令即可完成数据库映射
2.python manage.py migrate命令即可完成数据库映射

此时打开sqlite3数据库添加数据:

urls.py如下:

from django.conf.urls import url
from api import viewsurlpatterns = [url(r'^api/v1/auth/$', views.AuthView.as_view()),
]

api/views如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework import exceptions
from api import modelsdef md5(user):import hashlibimport time# 生成随机字符串ctime = str(time.time())m = hashlib.md5(bytes(user, encoding='utf-8'))m.update(bytes(ctime, encoding='utf-8'))return m.hexdigest()# 这是csrf认证另一种写法
@method_decorator(csrf_exempt, name='dispatch')
class AuthView(APIView):def post(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': None}try:user = request._request.POST.get('username')pwd = request._request.POST.get('password')# 获取用户对象obj = models.UserInfo.objects.filter(username=user, password=pwd).first()if not obj:ret['code'] = 1001ret['msg'] = '用户名或密码错误'# 为登录用户创建tokentoken = md5(user)ret['token'] = token# 存在就更新,不存在就创建models.UserToken.objects.update_or_create(defaults={'token': token}, user=obj)except Exception as e:ret['code'] = 1002ret['msg'] = '请求异常'return JsonResponse(ret)

自此我们就成功的引入了django rest_framework框架,此时我们访问http://127.0.0.1:8000/api/v1/auth/显示:

可以发现视图通过返回相应的请求不同,给出不同请求的参数,每个请求传参方式也有所不同,配合数据库查询、加密token可以使api不会被其他人调用,做到了限制的操作。

2.APIView源码分析

通过上面的调用我们发现,我们的类对象继承了django rest framework中的APIView方法,所以下面我们就来分析一下APIView方法中是怎么样实现的。

因为是继承于View方法,所以也是通过反射的方式调用的,所以,我们可以直接查看一下APIView的dispath函数,看一下是执行了什么。

如下:

class APIView(View):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 = argsself.kwargs = kwargsrequest = self.initialize_request(request, *args, **kwargs)self.request = requestself.headers = self.default_response_headers  # deprecate?try:self.initial(request, *args, **kwargs)# Get the appropriate handler methodif 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_allowedresponse = handler(request, *args, **kwargs)except Exception as exc:response = self.handle_exception(exc)self.response = self.finalize_response(request, response, *args, **kwargs)return self.response

可以发现和View方法的dispatch类似,不过可以看到APIView在request中封装了一个initialize_request函数

此时我们查看initialize_request函数如下:

    def initialize_request(self, request, *args, **kwargs):"""Returns the initial request object."""parser_context = self.get_parser_context(request)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,丰富了一些功能)。

那么封装的到底是什么呢?我们先点击get_authenticators函数查看一下:

    def get_authenticators(self):"""Instantiates and returns the list of authenticators that this view can use."""return [auth() for auth in self.authentication_classes]

可以看到get_authenticators函数通过self.authentication_classes遍历auth,通过遍历的个数返回多个函数(如果当前找不到self.authentication_classes类就会去父类找),那么怎么去调用这个函数呢,此时我们去查看一下Request类的构造方法:

class Request:"""Wrapper allowing to enhance a standard `HttpRequest` instance.Kwargs:- request(HttpRequest). The original request instance.- parsers(list/tuple). The parsers to use for parsing therequest content.- authenticators(list/tuple). The authenticators used to tryauthenticating the request's user."""def __init__(self, request, parsers=None, authenticators=None,negotiator=None, parser_context=None):assert isinstance(request, HttpRequest), ('The `request` argument must be an instance of ''`django.http.HttpRequest`, not `{}.{}`.'.format(request.__class__.__module__, request.__class__.__name__))self._request = requestself.parsers = parsers or ()self.authenticators = authenticators or ()self.negotiator = negotiator or self._default_negotiator()self.parser_context = parser_contextself._data = Emptyself._files = Emptyself._full_data = Emptyself._content_type = Emptyself._stream = Emptyif self.parser_context is None:self.parser_context = {}self.parser_context['request'] = selfself.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSETforce_user = getattr(request, '_force_auth_user', None)force_token = getattr(request, '_force_auth_token', None)if force_user is not None or force_token is not None:forced_auth = ForcedAuthentication(force_user, force_token)self.authenticators = (forced_auth,)

此时我们可以看到在Request类中request赋值给了self._request,所以我们如果想在dispatch类中使用原生的request,即可通过request._request方法来获取。(在dispatch函数中request=initialize_request函数,initialize_request函数返回Request方法,且构造函数时_request=request,所以如果想调用authenticators方法,可以在dispatch函数中直接request.authenticators方法即可)。

明白这些后我们继续返回APIView方法的dispatch函数中:

class APIView(View):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 = argsself.kwargs = kwargsrequest = self.initialize_request(request, *args, **kwargs)self.request = requestself.headers = self.default_response_headers  # deprecate?try:self.initial(request, *args, **kwargs)# Get the appropriate handler methodif 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_allowedresponse = handler(request, *args, **kwargs)except Exception as exc:response = self.handle_exception(exc)self.response = self.finalize_response(request, response, *args, **kwargs)return self.response

继续往下走,到try中,又调用了一个initial函数,且需要带参数的值是已经封装的request(Request类),此时我们点进去查看initial函数:

    def initial(self, request, *args, **kwargs):"""Runs anything that needs to occur prior to calling the method handler."""self.format_kwarg = self.get_format_suffix(**kwargs)# Perform content negotiation and store the accepted info on the requestneg = self.perform_content_negotiation(request)request.accepted_renderer, request.accepted_media_type = neg# Determine the API version, if versioning is in use.version, scheme = self.determine_version(request, *args, **kwargs)request.version, request.versioning_scheme = version, scheme# Ensure that the incoming request is permittedself.perform_authentication(request)self.check_permissions(request)self.check_throttles(request)

参数传递先不管,我们先开一下后面initial函数调用的三个函数中的perform_authentication(身份验证)、permissions(许可验证)、throttles(节流认证、限制访问数量),可以发现这其实是DRF框架中给我们定义的规则,通过相应规范来获取数据,此时我们将一个一个查看其相应源码。

3.perform_authentication源码分析

此时点击perform_authentication函数查看:

    def perform_authentication(self, request):request.user

此时我们发现perform_authentication函数中包裹了一个request函数的user方法,此时的request函数是继承于Reuqest类的函数了,所以我们可以通过查看Reqeust类中是否有user方法。

  @propertydef user(self):"""Returns the user associated with the current request, as authenticatedby the authentication classes provided to the request."""if not hasattr(self, '_user'):with wrap_attributeerrors():self._authenticate()return self._user

可以看出在Request类中确实有该方法,且加入了@property装饰器,使其在调用时,无需传入()。通过方法可以看出,当前user函数中判断了当前是否有_user函数,如果没有就调用self._authenticate函数

此时点击self._authenticate函数查看:

    def _authenticate(self):"""Attempt to authenticate the request using each authentication instancein turn."""for authenticator in self.authenticators:try:user_auth_tuple = authenticator.authenticate(self)except exceptions.APIException:self._not_authenticated()raiseif user_auth_tuple is not None:self._authenticator = authenticatorself.user, self.auth = user_auth_tuplereturnself._not_authenticated()

可以看出,该方法通过遍历self.authenticators方法来获取认证相关的函数(如果在当前类(DataView)中有就用当前类的,没有就用父类的),之后将获取认证相关的函数调用了authenticate方法,所以能发现,如果我们想在当前类使用身份校验,需在方法中创建authenticate函数

此时点击authenticate函数:

class ForcedAuthentication:def __init__(self, force_user, force_token):self.force_user = force_userself.force_token = force_tokendef authenticate(self, request):return (self.force_user, self.force_token)

可以发现就是返回了一个user和一个token参数,所以在_authenticate函数的变量user_auth_tuple是一个元组。而在后面_authenticate函数中又添加了一个判断user_auth_tuple是否为空,如果不为空则返回出去,用我们自定义的值,反之调用了self._not_authenticated()函数
此时我们点击self._not_authenticated()函数查看:

    def _not_authenticated(self):self._authenticator = Noneif api_settings.UNAUTHENTICATED_USER:self.user = api_settings.UNAUTHENTICATED_USER()else:self.user = Noneif api_settings.UNAUTHENTICATED_TOKEN:self.auth = api_settings.UNAUTHENTICATED_TOKEN()else:self.auth = None

可以看到self._not_authenticated方法获取了api_settings中的参数进行判断,而参数即为APIView中的全局设置的默认参数。所以可以发现该函数就是判断用户是否传入token和user、传入是否为空,如果有一者为False,则返回默认参数

此时我们再去查看一下self.user和self.auth赋值给了哪个变量。
如下:

 @user.setterdef user(self, value):self._user = valueself._request.user = value@auth.setterdef auth(self, value):self._auth = valueself._request.auth = value

可以看到user和auth都是函数,不过是添加了@user.setter、@auth.setter装饰器,且把值赋给了self._request(原生的request),所以我们可以在继承APIView类的方法中通过request.user、request.auth来获取相应的token和user。

理解了这个后,在后面的 permissions(许可验证)、throttles(节流认证、限制访问数量)概念是基本一致的。

- authentication函数使用

通过源码的分析,我们大致已经能清楚内部的原理了,所以现在就可以通过authentication(身份校验)来实现一个订单和身份的校验。

urls.py如下:

from django.conf.urls import url
from api import viewsurlpatterns = [url(r'^api/v1/auth/$', views.AuthView.as_view()),url(r'^api/v1/order/$', views.OrderView.as_view()),
]

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from api import models# perform_authentication校验类
class Authtication(object):def authenticate(self, request):token = request._request.GET.get('token')token_obj = models.UserToken.objects.filter(token=token).first()if not token_obj:raise exceptions.AuthenticationFailed('用户认证失败')print(token_obj.user, token_obj)return (token_obj.user,token_obj)def authenticate_header(self, request):passclass OrderView(APIView):"""订单相关"""authentication_classes = [Authtication, ]def get(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': None, 'data': None}try:ret['data'] = {'name': request.user.username,'context': '成功啦'}except Exception as e:passreturn JsonResponse(ret)

在Authtication类中的authenticate_header方法表示验证失败时返回的响应头,是必须要加的。

此时访问http://127.0.0.1:8000/api/v1/order/如下:

可以发现通过传入token值来判断用户身份,且在Authtication类中的authenticate方法校验时通过源码的分析,我们可以知道返回的两个参数会返回给request.user、request.auth,所以我们就可以调用了。

- authentication函数全局中使用

如果当一个类需要被多个类使用的时候如果每次都要写一遍在以后维护会相当不便,所以为了解决该问题我们需要将需要被多次调用的类放在全局中,让每个继承APIView的类都可以使用,在观察源码中,我们能发现,在APIView方法中定义了很多全局配置,api_setting,也就是我们使用时默认的配置,那么如果想通过自己的方式调用,该怎么修改呢?我们可以通过观察APIView方法中api_setting的配置信息源码。
如下:

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)def reload_api_settings(*args, **kwargs):setting = kwargs['setting']if setting == 'REST_FRAMEWORK':api_settings.reload()

可以发现,api_setting中是读取REST_FRAMEWORK的配置的,所以我们可以将其写在配置文件中。

为了以后利于维护,我们将全局调用类(这里就封装authentication函数用到的)写在一个文件夹下。

utils/auth.py如下:

from rest_framework import exceptions
from api import modelsclass Authtication(object):def authenticate(self, request):token = request._request.GET.get('token')token_obj = models.UserToken.objects.filter(token=token).first()if not token_obj:raise exceptions.AuthenticationFailed('用户认证失败')return (token_obj.user, token_obj)def authenticate_header(self, request):pass

settings.py如下:

REST_FRAMEWORK = {"DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ]
}

此时所有继承APIView的类都继承了Authtication方法,如果我们有的类不想继承,可以通过当前类的authentication_classes = []即可。

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BaseAuthentication
from api import modelsdef md5(user):import hashlibimport time# 生成随机字符串ctime = str(time.time())m = hashlib.md5(bytes(user, encoding='utf-8'))m.update(bytes(ctime, encoding='utf-8'))return m.hexdigest()@method_decorator(csrf_exempt, name='dispatch')
class AuthView(APIView):"""用户登录认证"""authentication_classes = []def post(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': None}try:user = request._request.POST.get('username')pwd = request._request.POST.get('password')# 获取用户对象obj = models.UserInfo.objects.filter(username=user, password=pwd).first()if not obj:ret['code'] = 1001ret['msg'] = '用户名或密码错误'# 为登录用户创建tokentoken = md5(user)ret['token'] = token# 存在就更新,不存在就创建models.UserToken.objects.update_or_create(defaults={'token': token}, user=obj)except Exception as e:ret['code'] = 1002ret['msg'] = '请求异常'return JsonResponse(ret)class OrderView(APIView):"""订单相关"""def get(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': None, 'data': None}try:ret['data'] = {'name': request.user.username,'context': '成功啦'}except Exception as e:passreturn JsonResponse(ret)

测试一下:

可以发现AuthView并没有继承Authtication类的方法。

且在APIView的全局配置中,给没有返回token,user参数的默认值也可以在全局配置中修改。

settings.py如下:

REST_FRAMEWORK = {"DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ],"UNAUTHENTICATED_USER":None,"UNAUTHENTICATED_TOKEN":None,}

可以设置为none也同样可以设置其他参数(返回值需要是函数,可以使用lambda表达式:“其他参数”),当没有返回参数时,就会返回你修改的参数了。

4.check_permissions源码分析

本质上perform_authentication的源码和check_permissions源码也是类似的,不过check_permissions方法的作用是用于权限的管理,而perform_authentication方法是做登录认证的,话不多说,直接看check_permissions源码吧。
如下:

    def check_permissions(self, request):for permission in self.get_permissions():if not permission.has_permission(request, self):self.permission_denied(request,message=getattr(permission, 'message', None),code=getattr(permission, 'code', None))

可以看到,check_permissions方法遍历了self.get_permissions函数,获取到permission之后,通过调用其has_permission方法来判断是否存在,存在则略(内部has_permission返回ture,则有权限访问),不存在则调用permission_denied方法(内部has_permission返回false,无权限访问)抛出异常

此时我们点开self.get_permissions函数如下:

    def get_permissions(self):return [permission() for permission in self.permission_classes]

可以看到也是通过遍历的方法,将其封装成一个函数,且该函数必须调用has_permission方法,所以我们在自定义的时候必须要加上该方法,和需要的两个request、view传参。

self.permission_denied函数如下:

    def permission_denied(self, request, message=None, code=None):if request.authenticators and not request.successful_authenticator:raise exceptions.NotAuthenticated()raise exceptions.PermissionDenied(detail=message, code=code)

该函数判断了前面的登录验证、当前认证是否存在(不为空),且缺一不可,根据不同情况抛出不同的异常。而这个抛出异常的message即为报错时的错误信息提示,所以可以根据自己的需求自行修改。

- permissions函数使用

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from api import modelsclass MyPermission(object):def has_permission(self, request, view):if request.user.user_type != 3:return Falsereturn Trueclass OrderView(APIView):"""订单相关"""permission_classes = [MyPermission, ]def get(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': None, 'data': None}try:ret['data'] = {'name': request.user.username,'context': '成功啦'}except Exception as e:passreturn JsonResponse(ret)

测试一下:

此时可以发现,我们通过在表结构创建的枚举类对象,判断用户类型不等于3的用户不能访问的权限,通过permissions函数成功的实现了。

- permissions函数全局中使用

为了后期管理起来方便,我们也将其封装在一个文件中,并放入全局配置中(和authentication方法是一样的)。通过APIVIew中的全局配置中,我们得知了permissions函数的全局配置为DEFAULT_PARSER_CLASSES。

settings.py如下:

REST_FRAMEWORK = {"DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ],"UNAUTHENTICATED_USER": None,"UNAUTHENTICATED_TOKEN": None,"DEFAULT_PERMISSION_CLASSES": ['api.utils.permissions.MyPermission', ],}

utils/permissions.py如下:

class MyPermission(object):message = '必须是SVIP才可以访问哦~'def has_permission(self, request, view):if request.user.user_type != 3:return Falsereturn True

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BaseAuthentication
from api import modelsdef md5(user):import hashlibimport time# 生成随机字符串ctime = str(time.time())m = hashlib.md5(bytes(user, encoding='utf-8'))m.update(bytes(ctime, encoding='utf-8'))return m.hexdigest()@method_decorator(csrf_exempt, name='dispatch')
class AuthView(APIView):"""用户登录认证"""authentication_classes = []permission_classes = []def post(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': None}try:user = request._request.POST.get('username')pwd = request._request.POST.get('password')# 获取用户对象obj = models.UserInfo.objects.filter(username=user, password=pwd).first()if not obj:ret['code'] = 1001ret['msg'] = '用户名或密码错误'# 为登录用户创建tokentoken = md5(user)ret['token'] = token# 存在就更新,不存在就创建models.UserToken.objects.update_or_create(defaults={'token': token}, user=obj)except Exception as e:ret['code'] = 1002ret['msg'] = '请求异常'return JsonResponse(ret)class OrderView(APIView):"""订单相关"""def get(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': None, 'data': None}try:ret['data'] = {'name': request.user.username,'context': '成功啦'}except Exception as e:passreturn JsonResponse(ret)

测试一下:

可以发现此时permissions函数也在全局中使用了,如果不想使用的话即可在继承APIView方法中使用permission_classes=[]即可。

5.check_throttles源码分析

此时访问check_throttles源码:

    def check_throttles(self, request):throttle_durations = []for throttle in self.get_throttles():if not throttle.allow_request(request, self):throttle_durations.append(throttle.wait())if throttle_durations:durations = [duration for duration in throttle_durationsif duration is not None]duration = max(durations, default=None)self.throttled(request, duration)

check_throttles的源码和check_permissions源码很类似,也是通过返回的值是Ture或Flase,来决定是否抛出异常或返回结果,不过check_throttles方法是用于限制请求次数的(节流),且在自定义函数时需写上两个函数,duration即为将遍历出来当前时间最大的那个值,赋值回去当剩余限制时间使用

self.get_throttles函数如下:

    def get_throttles(self):return [throttle() for throttle in self.throttle_classes]

可以发现check_throttles函数中通过self.get_throttles遍历,和前面类似,将其封装成了一个函数,且该函数必须要有allow_request、wait方法(返回的时间),且必须传入两个参数request、view

- throttles函数使用

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from api import models
import timeVISIT_RECORD = {}class VisitThrottle(object):"""60s内只能访问3次"""def __init__(self):self.history = Nonedef allow_request(self, request, view):# 1.获取用户ipremote_addr = request.META.get('REMOTE_ADDR')ctime = time.time()# 判断是否为第一次访问if remote_addr not in VISIT_RECORD:# 将第一次访问时间加上去VISIT_RECORD[remote_addr] = [ctime, ]return Truehistory = VISIT_RECORD.get(remote_addr)self.history = history# 判断histor是否有值,且最后一个值的时间是否小于60m,小于则可以移除while history and history[-1] < ctime - 60:history.pop()if len(history) < 3:VISIT_RECORD[remote_addr].insert(0, ctime)return Truedef wait(self):ctime = time.time()return 60 - (ctime - self.history[-1])@method_decorator(csrf_exempt, name='dispatch')
class AuthView(APIView):"""用户登录认证"""authentication_classes = []permission_classes = []throttle_classes = [VisitThrottle, ]def post(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': None}try:user = request._request.POST.get('username')pwd = request._request.POST.get('password')# 获取用户对象obj = models.UserInfo.objects.filter(username=user, password=pwd).first()if not obj:ret['code'] = 1001ret['msg'] = '用户名或密码错误'# 为登录用户创建tokentoken = md5(user)ret['token'] = token# 存在就更新,不存在就创建models.UserToken.objects.update_or_create(defaults={'token': token}, user=obj)except Exception as e:ret['code'] = 1002ret['msg'] = '请求异常'return JsonResponse(ret)

此时访问http://127.0.0.1:8000/api/v1/auth/如下:

此时可以发现,我们通过remote_addr = request.META.get(‘REMOTE_ADDR’)获取ip地址,然后定义一个字典来存储该ip地址访问时间是否小于当前时间-60秒,如果小于则能访问并增加一个记录,大于三次则不能访问。

- throttles函数全局中使用

通过APIVIew中的全局配置中,我们得知了throttles函数的全局配置为DEFAULT_THROTTLE_CLASSES

utils/throttle.py如下:

import timeVISIT_RECORD = {}class VisitThrottle(object):"""60s内只能访问3次"""def __init__(self):self.history = Nonedef allow_request(self, request, view):# 1.获取用户ipremote_addr = request.META.get('REMOTE_ADDR')ctime = time.time()# 判断是否为第一次访问if remote_addr not in VISIT_RECORD:# 将第一次访问时间加上去VISIT_RECORD[remote_addr] = [ctime, ]return Truehistory = VISIT_RECORD.get(remote_addr)self.history = history# 判断histor是否有值,且最后一个值的时间是否小于60m,小于则可以移除while history and history[-1] < ctime - 60:history.pop()if len(history) < 3:VISIT_RECORD[remote_addr].insert(0, ctime)return Truedef wait(self):ctime = time.time()return 60 - (ctime - self.history[-1])

settings.py如下:

REST_FRAMEWORK = {"DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ],"UNAUTHENTICATED_USER": None,"UNAUTHENTICATED_TOKEN": None,"DEFAULT_PERMISSION_CLASSES": ['api.utils.permissions.MyPermission', ],"DEFAULT_THROTTLE_CLASSES": ['api.utils.throttle.VisitThrottle'],}

三、drf框架内置认证类

1.内置BaseAuthentication类

DRF框架非常的灵活,在内部就有多种已经帮我们写好认证的类了,如果我们想要使用即可继承让我们全局的Authtication函数继承于BaseAuthentication类即可。

utils/auto.py如下:

from rest_framework import exceptions
from api import models
from rest_framework.authentication import BaseAuthenticationclass Authtication(BaseAuthentication):def authenticate(self, request):token = request._request.GET.get('token')token_obj = models.UserToken.objects.filter(token=token).first()if not token_obj:raise exceptions.AuthenticationFailed('用户认证失败')return (token_obj.user, token_obj)def authenticate_header(self, request):return 'Basic realm="api"'

此时访问http://127.0.0.1:8000/api/v1/order/显示:

可以发现,当我们没有传入值的时候,BaseAuthentication类会让我们登录,失败的时候也会返回错误信息,成功也可以返回数据,所以可以得出,当我们在写全局类中都必须要继承于BaseAuthentication类的方法。

2.内置BasePermission类

为了让我们的代码符合DRF规则,我们在自定义permissions函数的时候都需要继承BasePermission类,该类也是内部就有多种已经帮我们写好认证的类。

utils/auto.py如下:

from rest_framework.permissions import BasePermissionclass MyPermission(BasePermission):message = '必须是SVIP才可以访问哦~'def has_permission(self, request, view):if request.user.user_type != 3:return Falsereturn True

此时访问http://127.0.0.1:8000/api/v1/order/显示:

3.内置BaseThrottle类

为了遵循规范我们自定义的VisitThrottle也需要继承BaseThrottle类。

import time
from rest_framework.throttling import BaseThrottleVISIT_RECORD = {}class VisitThrottle(BaseThrottle):"""60s内只能访问3次"""def __init__(self):self.history = Nonedef allow_request(self, request, view):# 1.获取用户ipremote_addr = request.META.get('REMOTE_ADDR')ctime = time.time()# 判断是否为第一次访问if remote_addr not in VISIT_RECORD:# 将第一次访问时间加上去VISIT_RECORD[remote_addr] = [ctime, ]return Truehistory = VISIT_RECORD.get(remote_addr)self.history = history# 判断histor是否有值,且最后一个值的时间是否小于60m,小于则可以移除while history and history[-1] < ctime - 60:history.pop()if len(history) < 3:VISIT_RECORD[remote_addr].insert(0, ctime)return Truedef wait(self):ctime = time.time()return 60 - (ctime - self.history[-1])

- SimpleRateThrottle类

在DRF框架中,内置给了我们一个SimpleRateThrottle方法,该方法继承于BaseThrottle类,并且封装了很多的功能,所以为了能更加深刻了解,我们就读一下源码。

class SimpleRateThrottle(BaseThrottle):cache = default_cachetimer = time.timecache_format = 'throttle_%(scope)s_%(ident)s'scope = NoneTHROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATESdef __init__(self):if not getattr(self, 'rate', None):self.rate = self.get_rate()self.num_requests, self.duration = self.parse_rate(self.rate)

我们先从SimpleRateThrottle类的构造方法来看,第一步先是通过反射去查找rate函数,当前也没有rate函数,所以会执行get_rate函数

    def get_rate(self):if not getattr(self, 'scope', None):msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %self.__class__.__name__)raise ImproperlyConfigured(msg)try:return self.THROTTLE_RATES[self.scope]except KeyError:msg = "No default throttle rate set for '%s' scope" % self.scoperaise ImproperlyConfigured(msg)

此时get_rate函数又通过反射去查找scope函数,有scope函数(不过需要自己定义,默认none),所以往下走到self.THROTTLE_RATES[self.scope],该方法通过[self.scope为键返回了回去。不过此时我们发现THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES,所以THROTTLE_RATES是一个配置文件,而获取key的规则就是scope的值(键)

返回构造函数,执行self.num_requests, self.duration = self.parse_rate(self.rate),其目的是给self.num_requests, self.duration赋值,此时我们点击parse_rate函数。

    def parse_rate(self, rate):if rate is None:return (None, None)num, period = rate.split('/')num_requests = int(num)duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]return (num_requests, duration)

此时我们可以发现,该方法是将传入的值以’/‘进行分割,num_requests表示请求多少次,而duration表示限制时间是多少,通过该规则,我们明白了返回参数是一个字典(通过scope当键,获取以’/'分割的数据规则)我们可以通过全局配置,进行使用。

SimpleRateThrottle类还帮我们封装了一个get_cache_key函数(会将保存得值保存在default_cache的缓存中),用于限制访问用的,返回值即为以什么作为唯一标识

utils/throttle.py如下:

import time
from rest_framework.throttling import BaseThrottle, SimpleRateThrottleclass VisitThrottle(SimpleRateThrottle):scope = "scope"def get_cache_key(self, request, view):# 通过ip地址return self.get_ident(request)class UserThrottle(SimpleRateThrottle):scope = "user"def get_cache_key(self, request, view):# 用户名return request.user.username

settings.py如下:

REST_FRAMEWORK = {"DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ],"UNAUTHENTICATED_USER": None,"UNAUTHENTICATED_TOKEN": None,"DEFAULT_PERMISSION_CLASSES": ['api.utils.permissions.MyPermission', ],"DEFAULT_THROTTLE_CLASSES": ['api.utils.throttle.UserThrottle'],"DEFAULT_THROTTLE_RATES": {"scope": '3/m',"user":'5/m',},
}

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from api import models
import time
from api.utils.throttle import VisitThrottledef md5(user):import hashlib# 生成随机字符串mctime = str(time.time())m = hashlib.md5(bytes(user, encoding='utf-8'))m.update(bytes(ctime, encoding='utf-8'))return m.hexdigest()@method_decorator(csrf_exempt, name='dispatch')
class AuthView(APIView):"""用户登录认证"""authentication_classes = []permission_classes = []throttle_classes = [VisitThrottle,]def post(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': None}try:user = request._request.POST.get('username')pwd = request._request.POST.get('password')# 获取用户对象obj = models.UserInfo.objects.filter(username=user, password=pwd).first()if not obj:ret['code'] = 1001ret['msg'] = '用户名或密码错误'# 为登录用户创建tokentoken = md5(user)ret['token'] = token# 存在就更新,不存在就创建models.UserToken.objects.update_or_create(defaults={'token': token}, user=obj)except Exception as e:ret['code'] = 1002ret['msg'] = '请求异常'return JsonResponse(ret)class OrderView(APIView):"""订单相关"""def get(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': None, 'data': None}try:ret['data'] = {'name': request.user.username,'context': '成功啦'}except Exception as e:passreturn JsonResponse(ret)

此时通过全局配置使得throttle_classes默认使用以用户作为标识,而未登录状态设置为以id为标识。

测试一下:

python Django之 DRF(一)框架介绍、源码分析相关推荐

  1. 计算机毕业设计Python+django 宠物领养中心小程序(源码+系统+mysql数据库+Lw文档)

    项目介绍 据世界动物保护协会统计,全世界大概有5亿只流浪狗和散养的狗和大致同样数量的流浪猫,而这些主要源于主人的弃养.同时,在很多地区,狗和猫都处于散养状态,这部分的动物,也经常会变成流浪动物.猫和狗 ...

  2. 计算机毕业设计Python+django的零食销售商城网站(源码+系统+mysql数据库+Lw文档)

    项目介绍 ​随着人们生活条件的改善,人们对生活的追求也越来越高.在闲暇之时品尝上美味的零食,是当前很多人的一个休闲方式.当前临时市场鱼目混杂,种类繁多很多消费者不知道如何去选购更加美味可口的零食.尤其 ...

  3. 视频教程-RPC服务框架(Dubbo)源码分析-Java

    RPC服务框架(Dubbo)源码分析 鲁班学院-子路老师曾就职于谷歌.天猫电商等多家互联网公司,历任java架构师.研发经理等职位,参与并主导千万级并发电商网站与后端供应链研发体系搭建,多次参与电商大 ...

  4. Apollo 2.0 框架及源码分析(一) | 软硬件框架

    原文地址:https://zhuanlan.zhihu.com/p/33059132 前言 如引言中介绍的,这篇软硬件框架多为现有消息的整合加一些个人的想法.关于 Apollo 介绍的文章已经有许多, ...

  5. Python微型Web框架Bottle源码分析

    Bottle 是一个快速,简单和轻量级的 WSGI 微型 Web 框架的 Python.它作为单个文件模块分发,除了 Python 标准库之外没有依赖关系. 选择源码分析的版本是 Release 于 ...

  6. Spring框架—SpringBean源码分析

    原文作者:Javadoop 原文地址:Spring IOC 容器源码分析 在继续往下之前,我们需要先了解 BeanDefinition.我们说 BeanFactory 是 Bean 容器,那么 Bea ...

  7. 高性能网络I/O框架-netmap源码分析

    前几天听一个朋友提到这个netmap,看了它的介绍和设计,确实是个好东西.其设计思想与业界不谋而合--因为为了提高性能,几个性能瓶颈放在那里,解决方法自然也是类似的. netmap的出现,它既实现了一 ...

  8. 阿里开源一站式分布式事务框架seata源码分析(AT模式下TM与RM分析)

    序言: 对于阿里开源分布式事务框架seata的详细了解可以参考官网,这里不会详细介绍.本章只会介绍seata中AT模式的源码分析(对阿seata有一定了解或者成功完成过demo). seata中一个事 ...

  9. skynet 框架snax源码分析----变量注入

    skynet为了简化服务的编写,推出了snax框架,源码里也有一个例子pingserver.这是snax原创文章的第一篇,所以先就分析snax框架里的interface.lua源码,它的实现应用了一个 ...

  10. FATFS文件系统框架及源码分析

    FATFS是一个为小型嵌入式系统设计的通用FAT(File Allocation Table)文件系统模块.FatFs 的编写遵循ANSI C,并且完全与磁盘I/O层分开.因此,它独立(不依赖)于硬件 ...

最新文章

  1. 基于群集的Hyper-v Server副本
  2. java dwr实现消息推送_dwr消息推送
  3. 计算机操作系统读者和写者模型的简单介绍以及思考
  4. solr基本查询和高级查询
  5. 1.5编程基础之循环控制_16买房子
  6. Python self,init,对象属性
  7. vue 下载文件(后台返回为二进制流)
  8. 逆向CS生成的exe马
  9. 数据的对齐(alignment)
  10. Python API+Postman+jmeter
  11. Html中如何自定义Video显示的长宽比
  12. 蓝桥杯试题 算法提高 Cutting Chains
  13. 【案例】别人卖1000刀!一套油管自动生产视频赚钱方法详解,FFMPEG高手看来
  14. 计算机考研如何安排时间安排,2015年考研计算机复习计划及时间安排
  15. 怎样使用pdf转换器?一篇文章教会你
  16. Excel学习笔记:P13-页首、页尾设计、表格加水印
  17. Linux服务器需要安装代理软件EPS(agent)数据库
  18. 用 GTK 来写 Hello World
  19. mysql columns表_mysql8 参考手册-INFORMATION_SCHEMA COLUMNS表
  20. oracle job

热门文章

  1. 这些证书可以在复试时加分!有机会一定要考!
  2. 测量6J1电子管的一些基本特性
  3. ESP8266(ESP-12F)案例实操 -- 8x32点阵显示(MAX7219)
  4. MySQL卸载以及重装
  5. 教你如何在iOS项目中设置各种字体
  6. Kettle变量和自定义java代码的实例应用
  7. 微应用如何实现自动更新提示
  8. @zabbix监控自定义监控项
  9. (kuangbin带你飞--最短路径)MPI Maelstrom(dijstra模板题)
  10. Android逆向 微信小游戏破解(一):我要当皇上满级修改