在使用RestFramework以前咱們先用Django本身實現如下API。前端
API徹底能夠有咱們基於Django本身開發,原理是給出一個接口(URL),前端向URL發送請求以獲取數據。這樣能實現先後端分離的效果。但Django實現的API許多功能都須要咱們本身寫。django
URL編程
from django.contrib import admin from django.conf.urls import url, include from app01 import views from app02 import views urlpatterns = [ url('admin/', admin.site.urls), url('app02/', include('app02.urls'))#路由分發 ]
from app02 import views from django.conf.urls import url urlpatterns = [ url('^users/', views.users), url('^user/(\d+)', views.user), #######FBV與CBV的分界線######## url('^users/', views.UsersView.as_view()), url('^user/', views.UserView.as_view()), ]
views.pyjson
FBV後端
from django.shortcuts import render,HttpResponse import json def users(request): response = {'code':1000,'data':None} #code用來表示狀態,好比1000表明成功,1001表明 response['data'] = [ {'name':'Damon','age':22}, {'name':'Stefan','age':10}, {'name':'Elena','age':11}, ] return HttpResponse(json.dumps(response)) #返回就送字符串,前端解析 def user(request,pk): if request.method =='GET': return HttpResponse(json.dumps({'name':'Stefan','age':11})) #返回一條數據 elif request.method =='POST': return HttpResponse(json.dumps({'code':1111})) #返回一條數據 elif request.method =='PUT': pass elif request.method =='DELETE': pass
CBVapi
from django.views import View class UsersView(View): def get(self,request): response = {'code':1000,'data':None} response['data'] = [ {'name': 'Damon', 'age': 22}, {'name': 'Stefan', 'age': 10}, {'name': 'Elena', 'age': 11}, ] return HttpResponse(json.dumps(response),stutas=200) class UserView(View): def get(self,request,pk): return HttpResponse(json.dumps({'name':'haiyan','age':11})) #返回一條數據 def post(self,request,pk): return HttpResponse(json.dumps({'code':1111})) #返回一條數據 def put(self,request,pk): pass def delete(self,request,pk): pass
注:一般咱們在先後端分離進行編程時會推崇使用CBV的形式,CBV的代碼可讀性較高。緩存
安裝:app
pip3 install djangorestframework -i http://pypi.douban.com/simple/ --trusted-host=pypi.douban.com
RestFramework能夠直接在Django中使用,安裝完RestFramework後在Django中能夠當作模塊通常導入便可使用。(記得在settings.py中進行註冊,如app)前後端分離
URL與基於Django實現相同,這裏選用CBV的形式ide
from app02 import views from django.conf.urls import url urlpatterns = [ url('^users/', views.UsersView.as_view()),#CBV必需要有as_view() url('^user/', views.UserView.as_view()), ]
views.py
CBV
#導入rest_framework,自定義視圖的類需繼承APIView from rest_framework.views import APIView from rest_framework.response import Response class TestView(APIView): def dispatch(self, request, *args, **kwargs): """ 請求到來以後,在url中執行as_view()就會執行dispatch方法,dispatch方法是APIView類中內置的,根據請求方式不一樣觸發 get/post/put等方法。可自定製~ 注意:APIView中的dispatch方法有好多好多的功能 """ return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
注:重要的功都在APIView的dispatch中觸發。要掌握RestFramework,必須弄懂dispatch方法作了些什麼,這樣咱們才能夠根據本身的需求進行自定製。
咱們在繼承了APIView以後就能夠重寫裏面的方法進行自定製。此時咱們須要先弄懂APIView裏到底封裝了哪些方法。在APIView中,最重要的就是dispatch方法。
請求在url中執行as_view()時就會觸發dispatch,進入源碼咱們能夠看到dispatch主要作了四件事:
#在APIView類中: 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 # 1.封裝Django原始的request,使得用了framework之後的request都具備更多功能 """ 增長的功能 parsers=self.get_parsers(), authenticators=self.get_authenticators(),#獲取認證相關的全部類並實例化,傳入request對象 negotiator=self.get_content_negotiator(), parser_context=parser_context """ request = self.initialize_request(request, *args, **kwargs) self.request = request#將封裝後的request賦值給原始request self.headers = self.default_response_headers # deprecate? try: """ 2.版本處理、用戶認證、權限、訪問頻率限制 """ 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 #3.執行函數get/post/put/delete response = handler(request, *args, **kwargs) except Exception as exc: response = self.handle_exception(exc) #4.對返回結果進行再次加工 self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
接下來咱們對每一步進行具體的分析
第一步:封裝request
request = self.initialize_request(request, *args, **kwargs) #查看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(),#獲取認證相關的全部類並實例化,傳入request對象。優先找本身的,沒有就找父類的 negotiator=self.get_content_negotiator(), parser_context=parser_context )
1.一、咱們看到request封裝了一個認證的功能——獲取認證相關的全部的類並實例化,看看get_authenticators()作了什麼
def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ #返回的是對象列表[SessionAuthentication,BaseAuthentication] return [auth() for auth in self.authentication_classes] #self.authentication_classes是封裝有認證功能的類的列表 authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES #默認的,若是本身有會優先執行直接的
1.二、去settings.py查看默認的配置是什麼
DEFAULTS = { # Base API policies 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', #這時候就找到了他默認認證的類了,能夠導入看看 'rest_framework.authentication.BasicAuthentication' ),
1.三、導入SessionAutentication和BasicAuthentication查看這兩個雷兜風裝了什麼功能
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import BaseAuthentication
class BaseAuthentication(object): """ All authentication classes should extend BaseAuthentication. """ def authenticate(self, request): """ Authenticate the request and return a two-tuple of (user, token). """ raise NotImplementedError(".authenticate() must be overridden.") def authenticate_header(self, request): """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. """ pass
class BasicAuthentication(BaseAuthentication): """ HTTP Basic authentication against username/password. """ www_authenticate_realm = 'api' def authenticate(self, request): """ Returns a `User` if a correct username and password have been supplied using HTTP Basic authentication. Otherwise returns `None`. """ auth = get_authorization_header(request).split() if not auth or auth[0].lower() != b'basic': return None #返回none不處理。讓下一個處理 if len(auth) == 1: msg = _('Invalid basic header. No credentials provided.') raise exceptions.AuthenticationFailed(msg) elif len(auth) > 2: msg = _('Invalid basic header. Credentials string should not contain spaces.') raise exceptions.AuthenticationFailed(msg) try: auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':') #用partition切割冒號也包括 except (TypeError, UnicodeDecodeError, binascii.Error): msg = _('Invalid basic header. Credentials not correctly base64 encoded.') raise exceptions.AuthenticationFailed(msg) userid, password = auth_parts[0], auth_parts[2] # 返回用戶和密碼 return self.authenticate_credentials(userid, password, request) def authenticate_credentials(self, userid, password, request=None): """ Authenticate the userid and password against username and password with optional request for context. """ credentials = { get_user_model().USERNAME_FIELD: userid, 'password': password } user = authenticate(request=request, **credentials) if user is None: raise exceptions.AuthenticationFailed(_('Invalid username/password.')) if not user.is_active: raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) return (user, None) def authenticate_header(self, request): return 'Basic realm="%s"' % self.www_authenticate_realm
1.四、簡單自定製認證的類
咱們能夠看到源碼中最重要的就是BasicAuthentication的authenticate方法,因此要自定製認證的類只需重寫該方法便可
class MyAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get('token')#登陸用戶有tocken字段 obj = models.UserInfo.objects.filter(token=token).first() if obj: return (obj.username,obj) return None def authenticate_header(self, request): pass
第二步、版本處理、認證、權限、訪問頻率限制
self.initial(request, *args, **kwargs) #查看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. #2.1 處理版本信息,獲取版本必須用version version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted #2.2認證,將user封裝到request對象中 self.perform_authentication(request) #2.3 權限 self.check_permissions(request) #2.4 對請求用戶進行訪問頻率的限制 self.check_throttles(request)
2.2.一、認證:查看perform_authentication方法,發現只是將user封裝到了request中
def perform_authentication(self, request): request.user
2.2.二、查看request.user中都封裝了什麼
class Request(object): @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'): self._authenticate()#執行用戶認證 return self._user
2.2.三、執行self._authenticate() 開始用戶認證,若是驗證成功後返回元組: (用戶,用戶Token)
def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ #循環對象列表 for authenticator in self.authenticators: try: # 執行每個對象的authenticate 方法 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#返回一個元組,賦給了self,就能夠request.user,request.auth了 return self._not_authenticated()
2.2.四、在user_auth_tuple = authenticator.authenticate(self) 進行驗證,若是驗證成功,執行類裏的authenticatie方法。若是用戶沒有認證成功:self._not_authenticated()
def _not_authenticated(self): """ Set authenticator, user & authtoken representing an unauthenticated request. Defaults are None, AnonymousUser & None. """ # 若是跳過了全部認證,默認用戶和Token和使用配置文件進行設置 self._authenticator = None if api_settings.UNAUTHENTICATED_USER: self.user = api_settings.UNAUTHENTICATED_USER()# 默認值爲:匿名用戶AnonymousUser else: self.user = None# None 表示跳過該認證 if api_settings.UNAUTHENTICATED_TOKEN: self.auth = api_settings.UNAUTHENTICATED_TOKEN()# 默認值爲:None else: self.auth = None #默認值均可以在settings.py中進行自定製配置 REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, }
2.3.、權限控制
######check_permissions方法####### def check_permissions(self, request): """ Check if the request should be permitted. Raises an appropriate exception if the request is not permitted. """ for permission in self.get_permissions():#尋找類中的get_permissions()方法 if not permission.has_permission(request, self):#無權限則拋出異常 self.permission_denied( request, message=getattr(permission, 'message', None) ) ######get_permissions方法####### def get_permissions(self): """ Instantiates and returns the list of permissions that this view requires. """ return [permission() for permission in self.permission_classes]#去settings.py中查是否有權限配置
2.四、對訪問頻率進行限制(如下簡稱限流)
限流的主要目的仍是爲了防爬。一個網站的最終目的是爲了讓人去訪問的,可是有時候會有一些人工智能作一些對網站有傷害的事,這時候咱們就須要進行相應的限制了。權限的分配是一種對網站的保護的限制,但有些功能(好比看新聞、看動態等)是不須要任何權限只須要進入網站就能夠查看的,這時咱們就須要進行相應的限流操做,區分出非人類的用戶訪問予以限制。
def check_throttles(self, request): """ Check if request should be throttled. Raises an appropriate exception if the request is throttled. """ """ 循環每個throttle對象,執行allow_request方法 allow_request: 返回False,說明限制訪問頻率 返回True,說明不限制,通行 可自定製 """ for throttle in self.get_throttles(): if not throttle.allow_request(request, self): self.throttled(request, throttle.wait())#throttle.wait()表示多少秒後可再次訪問
from __future__ import unicode_literals import time from django.core.cache import cache as default_cache from django.core.exceptions import ImproperlyConfigured from rest_framework.settings import api_settings class BaseThrottle(object): """ Rate throttling of requests. """ def allow_request(self, request, view): """ Return `True` if the request should be allowed, `False` otherwise. """ raise NotImplementedError('.allow_request() must be overridden') def get_ident(self, request):#惟一標識 """ Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR if present and number of proxies is > 0. If not use all of HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR. """ xff = request.META.get('HTTP_X_FORWARDED_FOR') remote_addr = request.META.get('REMOTE_ADDR')#獲取IP等 num_proxies = api_settings.NUM_PROXIES#獲取代理信息 if num_proxies is not None: if num_proxies == 0 or xff is None: return remote_addr addrs = xff.split(',') client_addr = addrs[-min(num_proxies, len(addrs))] return client_addr.strip() return ''.join(xff.split()) if xff else remote_addr def wait(self): """ Optionally, return a recommended number of seconds to wait before the next request. """ return None#等待時長,可重寫
class SimpleRateThrottle(BaseThrottle): """ 一個簡單的緩存實現,只須要` get_cache_key() `。被覆蓋。 速率(請求/秒)是由視圖上的「速率」屬性設置的。類。該屬性是一個字符串的形式number_of_requests /期。 週期應該是:(的),「秒」,「M」,「min」,「h」,「小時」,「D」,「一天」。 之前用於節流的請求信息存儲在高速緩存中。 A simple cache implementation, that only requires `.get_cache_key()` to be overridden. The rate (requests / seconds) is set by a `throttle` attribute on the View class. The attribute is a string of the form 'number_of_requests/period'. Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') Previous request information used for throttling is stored in the cache. """ 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()#點進去看到須要些一個scope ,2/m self.num_requests, self.duration = self.parse_rate(self.rate) def get_cache_key(self, request, view):#這個至關因而一個半成品,咱們能夠來補充它 """ Should return a unique cache-key which can be used for throttling. Must be overridden. May return `None` if the request should not be throttled. """ raise NotImplementedError('.get_cache_key() must be overridden') def get_rate(self): """ Determine the string representation of the allowed request rate. """ if not getattr(self, 'scope', None):#檢測必須有scope,沒有就報錯了 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) def parse_rate(self, rate): """ Given the request rate string, return a two tuple of: <allowed number of requests>, <period of time in seconds> """ 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) # 一、一進來首先執行, def allow_request(self, request, view): """ Implement the check to see if the request should be throttled. On success calls `throttle_success`. On failure calls `throttle_failure`. """ if self.rate is None: return True self.key = self.get_cache_key(request, view)#二、執行get_cache_key if self.key is None: return True#不限制 self.history = self.cache.get(self.key, [])#三、獲得的key,默認是一個列表,賦值給了self.history, # self.history能夠理解爲每個ip對應的訪問記錄 self.now = self.timer() # Drop any requests from the history which have now passed the # throttle duration while self.history and self.history[-1] <= self.now - self.duration: self.history.pop() if len(self.history) >= self.num_requests: return self.throttle_failure() return self.throttle_success() def throttle_success(self): """ Inserts the current request's timestamp along with the key into the cache. """ self.history.insert(0, self.now) self.cache.set(self.key, self.history, self.duration) return True def throttle_failure(self): """ Called when a request to the API has failed due to throttling. """ return False def wait(self): """ Returns the recommended next request time in seconds. """ if self.history: remaining_duration = self.duration - (self.now - self.history[-1]) else: remaining_duration = self.duration available_requests = self.num_requests - len(self.history) + 1 if available_requests <= 0: return None return remaining_duration / float(available_requests)
第三步、執行函數get/post/put/delete
if request.method.lower() in self.http_method_names:#http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] handler = getattr(self, request.method.lower(),self.http_method_not_allowed)#反射 else: handler = self.http_method_not_allowed#拋出異常
第四步、對返回結果進行再次加工