一句话归纳RESTful
一种设计风格,经过URL定位资源,HTTP动词描述操做。旨在让咱们更好更快地开发出web应用javascript
HTTPS前端
目的是让人一看url就知道这是xx网站请求数据的接口vue
例如:https://www.love.com # 用于给用户访问java
https:// api.love.com # 用于请求json数据
注意:该方法须要解决跨域问题,通常用cors解决,表现为一个请求发两次python
例如:https:/ /www.love.comios
https:/ /www .love.com/api/web
经常使用于版本过渡使用
https:// api.example.com/v1/ajax
经过url定位资源数据库
即面向资源编程,面向资源编程便是面向一类数据去编程。django
现实场景,有不少类数据,例如,产品类、订单类、用户类。每一个资源实际上对应一种资源。例如:面向产品类数据编程实际便是面向产品表所记录的数据去编程。面向资源编程最重要是怎样表示资源
即地址即资源(用地址的方式去描述一个资源),看到这个地址,就感受看到这个地址对应的资源是什么
对于资源的操做只有增删改查。在HTTP协议中对每个请求url都会有不一样的谓词GET/POST/PUT/PATCH/DELETE,对于每个表明资源的url配对不一样的谓词时会产生不一样意义
例如:对 http://api.demo.com/users 用GET方式去请求,则是请求全部用户数据,换作POST方式,则是要新增一个用户
若是记录数量不少,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。经过在url上GET传参的形式传递搜索条件
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:指定筛选条件
200 OK - [GET]:服务器成功返回用户请求的数据,该操做是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
204 NO CONTENT - [DELETE]:用户删除数据成功。
301:临时重定向
302:永久重定向
400 :用户发出的请求有错误,问题在用户的请求,该操做是幂等的。
401 :没有权限(令牌、用户名、密码错误)。
403:权限不够
404:资源不存在,该操做是幂等的。
500 :服务器发生错误
注:实际生产中,状态码与code同时使用,通常和前端讨论时要问前端对状态码有没有要求
若是状态码是4xx,就应该向用户返回出错信息。通常来讲,返回的信息中将error做为键名,出错信息做为键值便可。
{ error: "Invalid API key" }
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
最好作到Hypermedia,即返回结果中提供连接,连向其余API方法,使得用户不查文档,也知道下一步应该作什么。代码上不须要拼接新的连接
例如:访问orders,返回包含所有订单的列表,里面的每个元素都提供一个得到其详细信息的url
1、请求方式:HEAD、GET、POST 2、请求头信息: Accept Accept-Language Content-Language Last-Event-ID Content-Type 对应的值是如下三个中的任意一个 application/x-www-form-urlencoded multipart/form-data text/plain 注意:同时知足以上两个条件时,则是简单请求,不然为复杂请求
对待复杂请求时使用,浏览器会先发一次option请求预检,预检成功以后才发真正要发的请求
由于即便容许指定域名跨域,也会限制不在指定范围内的请求头或请求方法。因此先发一次预检过来验证一下你这个请求头或请求方法能不能被我所接受。
from django.utils.deprecation import MiddlewareMixin class CORSMiddleware(MiddlewareMixin): def process_response(self, request, response): # 添加响应头 # 容许你的域名来获取个人数据 response['Access-Control-Allow-Origin'] = 'http://localhost:8080' # 如果设置cookie跨域,则这里不能为'*' # # 容许你携带Content-Type请求头 response['Access-Control-Allow-Headers'] = """Content-Type,Accept-Encoding,Access-Control-Request-Headers,Access-Control-Request-Method,Connection,Host,Origin,User-Agent,Content-Disposition""" # # 容许你发送DELETE,PUT response['Access-Control-Allow-Methods'] = "GET,POST,DELETE,PUT"
" # 容许使用cookie,使用后,就跟日常没有跨域同样使用。而vue-cookie只是应用在须要咱们在在客户端手动操纵cookie的时候 response['Access-Control-Allow-Credentials'] = 'true' return response
在跨域过程当中,Cookie是默认不发送的。就算后端返回Cookie字段,浏览器也不会保存Cookie,更不会在下一次访问的时候发送到后端了。
跨域cookie的设置
第一步,在ajax中设置,withCredentials: true。
第二步,服务端的Access-Control-Allow-Origin 不能够设置为"*",必须指定明确的、与请求网页一致的域名
第三步,服务端的 Access-Control-Allow-Credentials: true,表明服务器接受Cookie和HTTP认证信息。由于在CORS下,是默认不接受的。
var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.withCredentials = true; // 携带跨域cookie xhr.send();
$.ajax({ type: "GET", url: url, xhrFields: { withCredentials: true // 携带跨域cookie }, processData: false, success: function(data) { console.log(data); } });
axios.defaults.withCredentials=true; // 让ajax携带cookie
有两种方法
正向代理(部署一个和浏览器同源的代理服务器进行转发,例如python中使用request进行转发,),因为服务器端没有同源策略限制,不管怎样的数据都会收到
反向代理(让服务器得到指定的同源域名),经过修改Nginx的配置文件,将指定的不一样源域名代理到当前服务器
通常的代理服务器指的是正向代理,vue的配置以下
vue的config/index.js中
代理服务器的缺点
使用代理服务器的缺点是对不一样源资源的转发请求,若是同时多个用户进行跨域请求,由于服务器内部须要进行额外的HTTP请求,那么服务器端的处理压力降会变大,从而致使阻塞等一系列性能问题,如需更好的方案,仍是得使用Nginx等反向代理服务器进行端口代理处理。
restfulframework是基于Django的一个组件,目的是帮助开发高效开发出RESTful风格的web后端
因为RESTful须要多种不一样的请求方法,所以视图模式最好使用CBV实现,由于CBV是基于反射实现的,原理是根据请求方式不一样,执行不一样的方法。
CBV的流程是:url路由->as_view方法->dispatch(反射,执行其成员函数)
restfulframework的APIView是这个组件的核心,继承django基础的View,在这基础上封装了不少诸如认证,权限等小组件。
restfulframework流程以下:
请求进来->走dispatch->版本->认证->权限->节流
->借由反射执行视图->借由解析器拿请求数据->借由序列化器进行数据校验
->根据业务逻辑拿数据库数据->借由序列化器格式化数据库数据->若是数据量大,借由分页器分页->借由渲染器在浏览器渲染不一样的显示
除了这些,restframework还有路由组件,视图组件
APiView认证组件的源码剖析
第一步:进来就走dispatch
第二步:dispatch里面对原生的request进行进一步封装
第三步:而后执行self.initial(request)
第四步:self.initial里面执行认证self.perform_authticate
第五步:self.perform_authticate执行request.user
第六步:request.user内部将全部的认证类(全局/局部),并经过列表生成式建立认证对象,
第七步:最后遍历取出认证对象并调用其authenticate方法一一进行验证
附:使用django-restfulframework注意点
首先要了解,在分布式系统中,必需要从新考虑“登陆凭证”的问题。由于session共享的处理十分麻烦。
因此大多数都采用jwt的认证方式,即登陆成功后返回一个token做为用户的认证票据,能够放在请求头或者url GET参数中带过来进行验证
token的常见处理是,在数据库中定义一个新的表来存储。以用户OnetoOne关联
好,进入正题,restfulframework提供认证组件,这让开发者省去写装饰器或者中间件去验证用户认证票据的流程。
开发者只须要定义符合指定接口的认证类,配置上便可使用。
若是不指定任何的认证类,默认是
'DEFAULT_AUTHENTICATION_CLASSES'= ( 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ), # SessionAuthentication对经过login(request,user)登陆的用户有CSRF检测。 # BasicAuthentication不少老牌路由器登陆的方法。方式是使用浏览器自带的"对话框”方式将用户名密码通过base64加密后放在请求头里面发过去 如今基本被淘汰,直接写在配置上没有任何反应。
REST_FRAMEWORK = { # 全局使用的认证类 "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.FirstAuthtication', ], #覆盖框架定义全局认证类 # "UNAUTHENTICATED_USER":lambda :"匿名用户" "UNAUTHENTICATED_USER":None, # 匿名,request.user = None "UNAUTHENTICATED_TOKEN":None,# 匿名,request.auth = None }
from rest_framework.authentication import BaseAuthentication from rest_framework import exceptions class UserAuth(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") usertoken= UserToken.objects.filter(token =token).first() if usertoken: # 只能返回None或指定元组,一旦其中一个认证对象返回了元组,则整个认证流程结束 return usertoken.user, usertoken.token else: raise AuthenticationFailed({"code":1001,"error":"用户认证失败"}) # raise AuthenticationFailed("认证失败!") # restframework内部会有专门的捕捉,并返回 def authenticate_header(self,request): # 编写当认证失败时给浏览器返回的响应头 pass
注意:认证类的authenticate返回
REST_FRAMEWORK = { "DEFAULT_PERMISSION_CLASSES":['api.utils.permission.SVIPPermission'] }
from rest_framework.permissions import BasePermission class SVIPPermission(BasePermission): #按规范 须要继承BasePermission message = "必须是SVIP才能访问" def has_permission(self, request, view): if request.user.user_type != 3: return False return True
注意权限类的两个方法:
1.has_permission
has_permission只能返回True和False,True表示轮到下一个权限对象检查,False表示这个权限检查不经过,终止检查流程返回当前的Message信息。
自定权限类的has_permission若返回False会触发异常,框架内部会捕捉异常并返回一个message信息。
2.针对GenericViewSet的 has_object_permission
权限检查有一个def has_object_permission(self, request, view, obj): 这个是用户检查用户是否有操做这个对象obj的权限(粒度更小)
这个调用时机是在,GenericViewSet(实际上是GenericView)的get_object里面有一个check_object_permissions,他循环调用了权限检查对象的has_object_permission检查是否对这个对象有操做权限。
换句话说,也就是在使用GenericViewSet,才会使得has_object_permission起做用
#VISTIT_RECORD字典能够用django缓存替换 import time VISIT_RECORD = {} class VisitThrottle(BaseThrottle): def __init__(self): self.history = None def allow_request(self,request,view): # 1. 获取用户IP remote_addr = self.get_ident(request) 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 while history and history[-1] < ctime - 60: history.pop() if len(history) < 3: history.insert(0,ctime) return True # return True # 表示能够继续访问 # return False # 表示访问频率过高,被限制 def wait(self): # 还须要等多少秒才能访问 ctime = time.time() return 60 - (ctime - self.history[-1])
注意:throttle_classes=[] ,allow_request返回True或False,True表示交给下一个频率类对象检查,False表示检查不经过,终止检查流程并返回当前的wait函数所返回的提示时间。
频率访问的内置类很是精彩,有一个SimpleRateThrottle经过在配置文件里面设置相应的频率,内部便可实现。还有它的wait方法提供了倒数功能
AnonRateThrottle:用户未登陆请求限速,经过IP地址判断 UserRateThrottle:用户登录后请求限速,经过token判断 from rest_framework.throttling import BaseThrottle, SimpleRateThrottle class AnonRateThrottle(SimpleRateThrottle): scope = "Anon" def get_cache_key(self, request, view):
# 返回查找键值 return self.get_ident(request) class UserRateThrottle(SimpleRateThrottle): scope = "User" def get_cache_key(self, request, view): return request.user.username
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': ( 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ), 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', 'user': '1000/day' } } # DEFAULT_THROTTLE_RATES 包括 second, minute, hour, day
通常不须要本身编写类,只要根据本身版本表示的形式在settings.py配置好便可
目的是能够在视图方法中轻松地用request.version获取版本
附加了一个锦上添花的request.versioning_scheme。这货能够帮助反向生成当前视图版本的其余url
request.versioning_scheme.reverse(viewname='index',request=request)。
版本表示的形式
urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/users/$', views.UsersView.as_view()), ]
setting.py配置
# 配置容许的版本,版本参数key,和默认的版本
REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", "DEFAULT_VERSION":'v1', "ALLOWED_VERSIONS":['v1','v2'], "VERSION_PARAM":'version', }
通常不须要本身编写类,只要根据本身版本表示的形式在settings.py配置好便可
解析器的本质是根据请求头Content-Type的不一样让不一样的解析器去处理
为的是能够轻松使用request.data(request.body数据的转换)轻松得到传过来的数据,通常解析完成后为字典形式
通常为JSONParser,FormParser
from rest_framework.parsers import JSONParser,FormParser parser_classes = [JSONParser,FormParser,] #JSONParser:表示只能解析content-type:application/json头,content-type:application/json头表示数据是json格式 #FormParser:表示只能解析content-type:application/x-www-form-urlencoded头
文件上传解析器
FileUploadParser只适用于单文件上传,若要夹带另外数据则应该是 parser_classes = [FormParser, MultiPartParser]
分别从request.data和request.FILES中得到
FileUploadParser只适用于单文件上传,若要夹带另外数据则应该是
c. FileUploadParser 对于request.FILES和request.data的数据都是同样的
若是调用FileUploadParser时,有传入filename参数,就使用其做为filename
若是url中没有设置filename参数,那么客户端在请求头中就必须设置Content-Disposition,例如Content-Disposition: attachment; filename=upload.jpg
a. let headers = {
// 'Content-Type': 'application/x-www-form-urlencoded',
'Content-Disposition': `attachment; filename=${content.file.name}`
}
序列化器能够作数据校验和queryset的序列化显示
数据校验
from rest_framework import serializers class UserSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField()
# 自定义验证字段,要么返回原值value,要么报错 def validate_xxx(self,value): # from rest_framework import exceptions # raise exceptions.ValidationError('字段验证不经过') return value
ModelSerializer版
class UserInfoSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo # fields = "__all__" fields = ['id','username','password','oooo','rls','group','x1'] depth = 0 # 0表明只去当前本身层去取,数字越大,就往foreignkey 或 manytomany 取出相应的字典形式数据
class UserGroupView(APIView): def post(self, request, *args, **kwargs): ser = UserGroupSerializer(data=request.data) if ser.is_valid(): print(ser.validated_data['title']) else: print(ser.errors) return HttpResponse('提交数据')
序列化处理
序列化和以前序列化的对比
序列化的字段处理,主要讲model里特殊字段的处理
使用source参数(source参数能够是指向要序列化对象__dict__里的任意一个,框架内部会根据source利用反射查找)。
当存在source参数时,Serializer类变量的变量名就可任意定义了。source参数能够是函数名,可是该函数不能带参数,其框架内部用iscallable检查是否能够调用,如若能够,序列化时调用返回
rls = serializers.SerializerMethodField() # 自定义显示 def get_rls(self, row): #函数名如同form的clean_xx同样都是配对字段的。get_xx role_obj_list = row.roles.all() ret = [] for item in role_obj_list: ret.append({'id':item.id,'title':item.title}) return ret #返回的是字段要显示的
3. 还能够定义本身的Field,覆写to_representation,该方法返回字段对应的序列化结果
1.lookup_field 替代了source来进行反射查找,而且这个参数使用'_'分割
2.使用时要在serilizer对象初始化时加入context={'request':request}
url(r'^(?P<version>[v1|v2]+)/group/(?P<xxx>\d+)$', views.GroupView.as_view(),name='gp') #测试url
class UserInfoSerializer(serializers.ModelSerializer): group = serializers.HyperlinkedIdentityField(view_name='gp',lookup_field='group_id',lookup_url_kwarg='xxx') class Meta: model = models.UserInfo # fields = "__all__" fields = ['id','username','password','group','roles'] #extra_kwargs={'user':{'min_length':6},'password':{'validators':[xxxx,]] #验证时候添加,xxx是指定的函数对象 depth = 0 # 0 ~ 10
REST_FRAMEWORK = { #分页 "PAGE_SIZE":2 #每页显示多少个 }
固然咱们也能够本身继承PageNumberPagination,在类里面定义
class MyPageNumberPagination(pagination.PageNumberPagination): page_size = 5 max_page_size = 10 page_size_query_param = 'size' page_query_param = 'page' ○ 使用 def get(self, request, *args, **kwargs): # 测试分页对象 from rest.utils.pagination import MyPageNumberPagination from rest.utils.FormSerializer import RoleFormSerializer roles = models.RoleInfo.objects.all() # 建立分页对象 pager = MyPageNumberPagination() # pager = pagination.PageNumberPagination() # 须要在setting里配置,否则没数据 # 在数据库中获取分页的数据 data_list = pager.paginate_queryset(queryset=roles, request=request, view=self) # data_list 已是roleinfo对象列表,接下来序列化 ser = RoleFormSerializer(instance=data_list, many=True) return Response(ser.data)
#继承主要是改变max_limit最大大小 class MyOffsetPagination(pagination.LimitOffsetPagination): default_limit = 2 # 默认每页大小 max_limit = 5 # 人工指定的最大大小 limit_query_param = 'limit' offset_query_param = 'offset' a. 使用 def get(self, request, *args, **kwargs): roles = models.RoleInfo.objects.all() pager = MyOffsetPagination() data_list = pager.paginate_queryset(queryset=roles, request=request, view=self) ser = RoleFormSerializer(instance=data_list, many=True) return Response(ser.data)
游标分页
加密分页,只有上一页和下一页功能
# 特殊的是要设置ordering和返回时是用专用的方法 class MyCursorPagination(pagination.CursorPagination): ordering = '-id' # 必定要设置好排序规则 page_size = 5 max_page_size = 10 page_size_query_param = 'size' cursor_query_param = 'cursor' • 使用 from rest.utils.pagination import MyCursorPagination pager = MyCursorPagination() data_list = pager.paginate_queryset(queryset=roles, request=request, view=self) ser = RoleFormSerializer(instance=data_list, many=True) return pager.get_paginated_response(ser.data) # 使用游标分页特殊的方法返回
复杂逻辑 GenericViewSet 或 APIView
增删改查:ModelViewset
增删 CreateModelMixin,DestroyModelMixin,GenericViewSet
改变请求方式对应的调用函数,要经过对as_view({'get':'list'','post':'add'})传参
注意:但凡支持{‘get’:’list’}这种as_view传参,就表明其继承了ViewSetMixin。因此class MyView(ViewSetMixin,APIView) 替代GenericViewSet
改变请求方式调用的视图函数这个在多个url对应一个CBV时有奇效。
增删改查组件,以多继承的方式使用
如 mixins.ListModelMixin里面有一个list函数,它提供了像上面同样的用法,即显示一个对象集合
from rest_framework import mixins class RolesView3(viewsets.GenericViewSet, mixins.ListModelMixin): queryset = models.RoleInfo.objects.all() serializer_class = RoleFormSerializer pagination_class = MyPageNumberPagination
继承了GenericViewSet,并集齐了增删改查,局部更新组件
注意:url必定要有一个命名正则pk
url(r'^(?P<version>[v1|v2]+)/role/(?P<pk>\d+)/$',RoleView.as_view({'get': 'retrieve', 'delete': 'destroy', 'put': 'update', 'patch': 'partial_update'}),name='role'),
因为url的增长和查看,因此通常定义两个url,一个针对象集合,一个针对单一对象的操做
#视图只需一个,url两个 class RoleView(viewsets.ModelViewSet): queryset = models.RoleInfo.objects.all() # 直接传入全局对象,框架内部会根据传入的pk返回单一对象 serializer_class = RoleFormSerializer pagination_class = MyPageNumberPagination
当出现这么一个需求:在url后面.json,使得渲染器显示原生的的json数据
咱们的解决方案是,有定义两条url配合渲染器达到效果
url(r'^(?P<version>[v1|v2]+)/roles4/$',RolesView4.as_view({'get': 'list', 'post': 'create'}), name='roles4'), url(r'^(?P<version>[v1|v2]+)/roles4\.(?P<format>\w+)$', RolesView4.as_view({'get': 'list', 'post': 'create'}), name='roles4'),
或者定义一条url达到效果
url(r'^(?P<version>[v1|v2]+)/roles4(\.(?P<format>\w+))?$',RolesView4.as_view({'get': 'list', 'post': 'create'}), name='roles4'),
路由组件提供全自动路由
所谓全自动,指不用再as_view指定组件的指定函数名称,并且还生成了.json后缀,自动生成单一对象和对象集合的4组url
使用场景:单一个api对一个表进行简单的增删改查(配合ModelViewset)用得多,可是单单的增删功能就没有必要了
# 如下写在url文件里 from rest_framework import routers router = routers.DefaultRouter() router.register(r'xxxx', views.RouterView) # xxxx表明url标志符
urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/', include(router.urls)), #注意: xxxx表明url标志符,在指定前缀正则('^(?P<version>[v1|v2]+)/)后面添加 # http://127.0.0.1:8000/api/v1/xxx/
通常配置在settings.py里,目的是配置浏览器如何显示json(为了页面更好看)
from rest_framework import renderers renderer_classes = [renderers.JSONRenderer, renderers.BrowsableAPIRenderer, renderers.AdminRenderer] # 例如使用 admin渲染器:http://127.0.0.1:8000/api/v1/roles4/?format=admin
注:能够自定义显示页面
将BrowsableAPIRenderer的template变量指向的模版文件改掉就好了