一句話歸納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變量指向的模版文件改掉就好了