REST与技术无关,代表的是一种软件架构风格
,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”。
REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态。
所有的数据,不过是通过网络获取的还是操作(增删改查)
的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性。
对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)
。
API的意思在本质上是一个或多个路由组成
,通过RESTful规范定义规则,当需从该API中获取数据时,需通过相应路由url中的视图函数的校验且需遵循drf定义的规范。那么,知道这些后,我们设置API时需要注意的问题如下:
尽量将API部署在专用域名(会存在跨域问题,解决方法jsonp等…
)
定义接口一般在域名后添加/api/v(\d+)/(表示更新api的版本
)
路径,视网络上任何东西都是资源,均使用名词
表示(可复数)
在视图函数返回请求中定义规则
过滤,通过在url上传参的形式传递搜索条件(类似在url后面添加?page=1&type=“list”)获取参数值。
错误处理,状态码是4xx时,应返回错误信息,error当做key。
{
error: "Invalid API key"
}
返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范。
1.GET /collection:返回资源对象的列表(数组)
2.GET /collection/resource:返回单个资源对象
3.POST /collection:返回新生成的资源对象
4.PUT /collection/resource:返回完整的资源对象
5.PATCH /collection/resource:返回完整的资源对象
6.DELETE /collection/resource:返回一个空文档
将获取到的参数,添加上该参数全部类似、相同信息的url地址
{
"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
状态码
码 | 说明 |
---|---|
0 OK - [GET] | 服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 |
1 CREATED - [POST/PUT/PATCH] | 用户新建或修改数据成功。 |
2 Accepted - [*] | 表示一个请求已经进入后台排队(异步任务) |
4 NO CONTENT - [DELETE] | 用户删除数据成功。 |
0 INVALID REQUEST - [POST/PUT/PATCH] | 用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。 |
1 Unauthorized - [*] | 表示用户没有权限(令牌、用户名、密码错误)。 |
3 Forbidden - [*] | 表示用户得到授权(与401错误相对),但是访问是被禁止的。 |
4 NOT FOUND - [*] | 用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 |
6 Not Acceptable - [GET] | 用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。 |
2 Unprocesable entity - [POST/PUT/PATCH] | 当创建一个对象时,发生一个验证错误。 |
0 INTERNAL SERVER ERROR - [*] | 服务器发生错误,用户将无法判断发出的请求是否成功。 |
摘自:https://www.cnblogs.com/wupeiqi/articles/7805382.html
首先先安装一下drf框架
pip install djangorestframework
明白这些后我们就可以来尝试构造一个api来实现调用(使用上,推荐使用CBV,因为更通过继承和封装、重写父类、调用父类更加灵活)
。
要想深刻的理解drf那么阅读源码是必须的,所以接下来我们将通过阅读源码的方式来解释一下drf的一下原理和规范是如何定义的、通过什么定义的。首先我们先创建django项目。
python manage.py startapp api
创建一个名为api的项目。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命令即可完成数据库映射
urls.py如下:
from django.conf.urls import url
from api import views
urlpatterns = [
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 models
def md5(user):
import hashlib
import 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'] = 1001
ret['msg'] = '用户名或密码错误'
# 为登录用户创建token
token = md5(user)
ret['token'] = token
# 存在就更新,不存在就创建
models.UserToken.objects.update_or_create(defaults={
'token': token}, user=obj)
except Exception as e:
ret['code'] = 1002
ret['msg'] = '请求异常'
return JsonResponse(ret)
自此我们就成功的引入了django rest_framework框架,此时我们访问http://127.0.0.1:8000/api/v1/auth/显示:
可以发现视图通过返回相应的请求不同,给出不同请求的参数,每个请求传参方式也有所不同,配合数据库查询、加密token可以使api不会被其他人调用
,做到了限制的操作。
通过上面的调用我们发现,我们的类对象继承了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 = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
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)
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 the
request content.
- authenticators(list/tuple). The authenticators used to try
authenticating 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 = request
self.parsers = parsers or ()
self.authenticators = authenticators or ()
self.negotiator = negotiator or self._default_negotiator()
self.parser_context = parser_context
self._data = Empty
self._files = Empty
self._full_data = Empty
self._content_type = Empty
self._stream = Empty
if self.parser_context is None:
self.parser_context = {
}
self.parser_context['request'] = self
self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
force_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方法
即可)。
明白这些后我们继续返回ViewAPI方法的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 = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
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)
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 request
neg = 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 permitted
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
参数传递先不管,我们先开一下后面initial函数调用的三个函数中的perform_authentication(身份验证)、permissions(许可验证)、throttles(节流认证、限制访问数量)
,可以发现这其实是DRF框架中给我们定义的规则,通过相应规范来获取数据
,此时我们将一个一个查看其相应源码。
此时点击perform_authentication函数查看:
def perform_authentication(self, request):
request.user
此时我们发现perform_authentication函数中包裹了一个request函数的user方法
,此时的request函数是继承于Reuqest类的函数
了,所以我们可以通过查看Reqeust类中是否有user方法。
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by 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 instance
in turn.
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
可以看出,该方法通过遍历self.authenticators方法来获取认证相关的函数(如果在当前类(DataView)中有就用当前类的,没有就用父类的)
,之后将获取认证相关的函数调用了authenticate方法,所以能发现,如果我们想在当前类使用身份校验,需在方法中创建authenticate函数
。
此时点击authenticate函数:
class ForcedAuthentication:
def __init__(self, force_user, force_token):
self.force_user = force_user
self.force_token = force_token
def 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 = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
if 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.setter
def user(self, value):
self._user = value
self._request.user = value
@auth.setter
def auth(self, value):
self._auth = value
self._request.auth = value
可以看到user和auth都是函数,不过是添加了@user.setter、@auth.setter装饰器,且把值赋给了self._request(原生的request)
,所以我们可以在继承ViewAPI类的方法中通过request.user、request.auth来获取相应的token和user。
permissions(许可验证)、throttles(节流认证、限制访问数量)
概念是基本一致的。
通过源码的分析,我们大致已经能清楚内部的原理了,所以现在就可以通过authentication(身份校验)来实现一个订单和身份的校验。
urls.py如下:
from django.conf.urls import url
from api import views
urlpatterns = [
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):
pass
class 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:
pass
return JsonResponse(ret)
在Authtication类中的authenticate_header方法表示验证失败时返回的响应头
,是必须要加的。
此时访问http://127.0.0.1:8000/api/v1/order/如下:
可以发现通过传入token值来判断用户身份,且在Authtication类中的authenticate方法校验时通过源码的分析,我们可以知道返回的两个参数会返回给request.user、request.auth
,所以我们就可以调用了。
如果当一个类需要被多个类使用的时候如果每次都要写一遍在以后维护会相当不便,所以为了解决该问题我们需要将需要被多次调用的类放在全局
中,让每个继承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 models
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('用户认证失败')
return (token_obj.user, token_obj)
def authenticate_header(self, request):
pass
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ]
}
此时所有继承ViewAPI的类都继承了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 models
def md5(user):
import hashlib
import 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'] = 1001
ret['msg'] = '用户名或密码错误'
# 为登录用户创建token
token = md5(user)
ret['token'] = token
# 存在就更新,不存在就创建
models.UserToken.objects.update_or_create(defaults={
'token': token}, user=obj)
except Exception as e:
ret['code'] = 1002
ret['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:
pass
return JsonResponse(ret)
测试一下:
可以发现AuthView并没有继承Authtication类的方法。
且在ViewAPI的全局配置中,给没有返回token,user参数的默认值也可以在全局配置中修改。
settings.py如下:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ],
"UNAUTHENTICATED_USER":None,
"UNAUTHENTICATED_TOKEN":None,
}
可以设置为none也同样可以设置其他参数(返回值需要是函数,可以使用lambda表达式:“其他参数”)
,当没有返回参数时,就会返回你修改的参数了。
本质上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即为报错时的错误信息提示
,所以可以根据自己的需求自行修改。
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
class MyPermission(object):
def has_permission(self, request, view):
if request.user.user_type != 3:
return False
return True
class 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:
pass
return JsonResponse(ret)
测试一下:
此时可以发现,我们通过在表结构创建的枚举类对象,判断用户类型不等于3的用户不能访问的权限
,通过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 False
return 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 models
def md5(user):
import hashlib
import 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'] = 1001
ret['msg'] = '用户名或密码错误'
# 为登录用户创建token
token = md5(user)
ret['token'] = token
# 存在就更新,不存在就创建
models.UserToken.objects.update_or_create(defaults={
'token': token}, user=obj)
except Exception as e:
ret['code'] = 1002
ret['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:
pass
return JsonResponse(ret)
测试一下:
可以发现此时permissions函数也在全局中使用了,如果不想使用的话即可在继承APIView方法中使用permission_classes=[]
即可。
此时访问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_durations
if 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
。
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
VISIT_RECORD = {
}
class VisitThrottle(object):
"""60s内只能访问3次"""
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 1.获取用户ip
remote_addr = request.META.get('REMOTE_ADDR')
ctime = time.time()
# 判断是否为第一次访问
if remote_addr not in VISIT_RECORD:
# 将第一次访问时间加上去
VISIT_RECORD[remote_addr] = [ctime, ]
return True
history = 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 True
def 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'] = 1001
ret['msg'] = '用户名或密码错误'
# 为登录用户创建token
token = md5(user)
ret['token'] = token
# 存在就更新,不存在就创建
models.UserToken.objects.update_or_create(defaults={
'token': token}, user=obj)
except Exception as e:
ret['code'] = 1002
ret['msg'] = '请求异常'
return JsonResponse(ret)
此时访问http://127.0.0.1:8000/api/v1/auth/如下:
此时可以发现,我们通过remote_addr = request.META.get(‘REMOTE_ADDR’)
获取ip地址,然后定义一个字典来存储该ip地址访问时间是否小于当前时间-60秒
,如果小于则能访问并增加一个记录,大于三次则不能访问。
utils/throttle.py如下:
import time
VISIT_RECORD = {
}
class VisitThrottle(object):
"""60s内只能访问3次"""
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 1.获取用户ip
remote_addr = request.META.get('REMOTE_ADDR')
ctime = time.time()
# 判断是否为第一次访问
if remote_addr not in VISIT_RECORD:
# 将第一次访问时间加上去
VISIT_RECORD[remote_addr] = [ctime, ]
return True
history = 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 True
def 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框架非常的灵活,在内部就有多种已经帮我们写好认证的类了,如果我们想要使用即可继承让我们全局的Authtication函数继承于BaseAuthentication类
即可。
utils/auto.py如下:
from rest_framework import exceptions
from api import models
from rest_framework.authentication import BaseAuthentication
class 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类的方法。
为了让我们的代码符合DRF规则,我们在自定义permissions函数的时候都需要继承BasePermission类,该类也是内部就有多种已经帮我们写好认证的类。
utils/auto.py如下:
from rest_framework.permissions import BasePermission
class MyPermission(BasePermission):
message = '必须是SVIP才可以访问哦~'
def has_permission(self, request, view):
if request.user.user_type != 3:
return False
return True
此时访问http://127.0.0.1:8000/api/v1/order/显示:
为了遵循规范我们自定义的VisitThrottle也需要继承BaseThrottle类。
import time
from rest_framework.throttling import BaseThrottle
VISIT_RECORD = {
}
class VisitThrottle(BaseThrottle):
"""60s内只能访问3次"""
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 1.获取用户ip
remote_addr = request.META.get('REMOTE_ADDR')
ctime = time.time()
# 判断是否为第一次访问
if remote_addr not in VISIT_RECORD:
# 将第一次访问时间加上去
VISIT_RECORD[remote_addr] = [ctime, ]
return True
history = 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 True
def wait(self):
ctime = time.time()
return 60 - (ctime - self.history[-1])
在DRF框架中,内置给了我们一个SimpleRateThrottle方法,该方法继承于BaseThrottle类,并且封装了很多的功能
,所以为了能更加深刻了解,我们就读一下源码。
class SimpleRateThrottle(BaseThrottle):
cache = default_cache
timer = time.time
cache_format = 'throttle_%(scope)s_%(ident)s'
scope = None
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
def __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.scope
raise 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, SimpleRateThrottle
class 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 VisitThrottle
def md5(user):
import hashlib
# 生成随机字符串m
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 = []
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'] = 1001
ret['msg'] = '用户名或密码错误'
# 为登录用户创建token
token = md5(user)
ret['token'] = token
# 存在就更新,不存在就创建
models.UserToken.objects.update_or_create(defaults={
'token': token}, user=obj)
except Exception as e:
ret['code'] = 1002
ret['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:
pass
return JsonResponse(ret)
此时通过全局配置使得throttle_classes默认使用以用户作为标识,而未登录状态设置为以id为标识。