Restful規範-APIView原始碼分析

先生發表於2021-07-03

一、Restful規範

Restful規範是一種web API介面的設計風格,在前後端分離的應用模式中適用較多。

這種風格的理念認為後端開發任務就是提供資料的,對外提供的是資料資源的訪問介面,所以在定義介面時,客戶端訪問的URL路徑就表示這種要操作的資料資源。

十條規範

1、是資料的安全保障:url連結一般都採用HTTPS協議進行傳輸

2、介面特徵表現,一看就知道是個api介面

用api關鍵字標識介面url:

https://api.baidu.com
https://www.baidu.com/api

3、多資料版本共存

在url連結中標識資料版本:url連結中的v1、v2就是不同資料版本的體現(只有在一種資料資源有多版本情況下)

https://api.baidu.com/v1
https://api.baidu.com/v2

4、資料即是資源,均使用名詞(可複數) **

介面一般都是完成前後臺資料的互動,互動的資料我們稱為資源

https://api.baidu.com/users
https://api.baidu.com/books
https://api.baidu.com/book

**5、資源操作由請求方式決定(method) ** **

操作資源一般都會涉及到增刪改查,使用提供請求方式來標識增刪改查動作

https://api.baidu.com/books 	- get請求:獲取所有書
https://api.baidu.com/books/1 	- get請求:獲取主鍵為1的書
https://api.baidu.com/books 	- post請求:新增一本書書
https://api.baidu.com/books/1 	- put請求:整體修改主鍵為1的書
https://api.baidu.com/books/1 	- delete請求:刪除主鍵為1的書

6、過濾,通過在url上傳參的形式傳遞搜尋條件

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:	指定篩選條件

7、響應狀態碼

正常響應
    響應狀態碼2xx
    200:常規請求
    201:建立成功
重定向響應
    響應狀態碼3xx
    301:永久重定向
    302:暫時重定向
客戶端異常
    響應狀態碼4xx
    403:請求無許可權
    404:請求路徑不存在
    405:請求方法不存在
伺服器異常
    響應狀態碼5xx
    500:伺服器異常

8、錯誤處理、應返回錯誤資訊,error當做key

{
    "error": "無許可權操作"
}

9、返回結果,針對不同操作,伺服器向返回的結果應該符合以下規範

GET /collection:			返回資源物件的列表(陣列)
GET /collection/resource:	返回單個資源物件
POST /collection:			返回新生成的資源物件
PUT /collection/resource:	返回完整的資源物件
PATCH /collection/resource:	返回完整的資源物件
DELETE /collection/resource:返回一個空文件

10、需要url請求的資源需要訪問資源的請求連結

{
    "status": 0,
    "msg": "ok",
    "results":[
        {
            "name":"肯德基(羅餐廳)",
            "img": "https://image.baidu.com/kfc/001.png"
        }
    ]
}

二、drf的簡單使用

1、在setting.py 的app中註冊

INSTALLED_APPS = [
    'rest_framework'
]

2、在models.py中寫表模型

class Book(models.Model):
    nid=models.AutoField(primary_key=True)
    name=models.CharField(max_length=32)
    price=models.DecimalField(max_digits=5,decimal_places=2)
    author=models.CharField(max_length=32)

3、新建一個序列化類

from rest_framework.serializers import ModelSerializer
from app01.models import  Book

class BookModelSerializer(ModelSerializer):
    class Meta:
        model = Book
        fields = "__all__"

4、在檢視中寫檢視類

from rest_framework.viewsets import ModelViewSet
from .models import Book
from .ser import BookModelSerializer

class BooksViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer

5、寫路由關係

from app01 import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()  # 可以處理檢視的路由器
router.register('book', views.BooksViewSet)  # 向路由器中註冊檢視集

# 將路由器中的所以路由資訊追到到django的路由列表中
urlpatterns = [
    path('admin/', admin.site.urls),
]
# 兩個列表相加
urlpatterns += router.urls

三、APIView原始碼分析

ModelViewSet繼承View(django原生View)

APIView繼承了View

先讀View的原始碼

CBV原始碼分析

from django.views import View

# urls.py
# views.Books.as_view()是一個函式記憶體地址,as_view是一個類方法,Books類直接呼叫,會把類自動傳入
path('books/', views.Books.as_view()), 
# as_view返回了一個內層函式view,相當於放了一個view的記憶體地址(View——>as_view——>內層函式)

# 請求來了,如果路徑匹配,會執行————>  view(request)
def view(request, *args, **kwargs):
    self = cls(**initkwargs)
    self.setup(request, *args, **kwargs)
    if not hasattr(self, 'request'):
        raise AttributeError(
            "%s instance has no 'request' attribute. Did you override "
            "setup() and forget to call super()?" % cls.__name__
        )
        return self.dispatch(request, *args, **kwargs)
# 然後返回一個self.dispatch方法,self是誰呼叫就是誰,由於是Books.as_view(),所以self是Books物件,而Books類繼承了View類,所以執行在View類中的dispatch方法


def dispatch(self, request, *args, **kwargs):
    if request.method.lower() in self.http_method_names:	# 先判斷請求方式是否存在
        
        # 存在則反射self(Books)中的請求方法的記憶體地址————>handler=getattr(self,'get')
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)	# 執行get(request)

APIView原始碼分析

from rest_framework.views import APIView

# urls.py
# 這裡同樣寫的是個函式的記憶體地址,不過是用的APIView裡的as_view方法
path('booksapiview/', views.BooksAPIView.as_view()),	

# APIView的as_view方法(類的繫結方法)
@classmethod
def as_view(cls, **initkwargs):
    
		......
        
    # 主要的如下
    # 先呼叫了父類(View)的as_view方法
    view = super().as_view(**initkwargs)	
    # 當呼叫了父類(View)的as_view方法會執行self.dispatch(),此時的self是BooksAPIView,並且繼承了APIView類,所以此時的dispatch()方法是APIView的dispatch()方法(原dispatch方法被重寫了)
    
    view.cls = cls
    view.initkwargs = initkwargs
    # 以後的所有請求都沒有csrf認證了,只要繼承了APIView,就沒有CSRF認證
    return csrf_exempt(view)	
	# 區域性禁用csrf,在檢視上加裝飾器@csrf_exempt,和csrf_exempt(view)是一樣的
    
    
# 請求來了——》路由匹配上——》view(request)——》呼叫了self.dispatch()——》執行APIView的dispatch方法
# APIView的dispatch方法
def dispatch(self, request, *args, **kwargs):
    self.args = args
    self.kwargs = kwargs
    
    # 將原生的request包裝成一個新的request,以後在用request,就是新的request物件了
    request = self.initialize_request(request, *args, **kwargs)
    
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        # 三大認證模組
        self.initial(request, *args, **kwargs)

        # 這裡就相當於原生的dispatch方法的反射,先判斷請求方法是否存在,在去反射獲取請求方法的記憶體地址
        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



# APIView的initial方法(三大認證模組)
def initial(self, request, *args, **kwargs):
    	...
        
    # 認證元件:校驗使用者 - 遊客、合法使用者、非法使用者
    # 遊客:代表校驗通過,直接進入下一步校驗(許可權校驗)
    # 合法使用者:代表校驗通過,將使用者儲存在request.user中,再進入下一步校驗(許可權校驗)
    # 非法使用者:代表校驗失敗,丟擲異常,返回403許可權異常結果
    self.perform_authentication(request)
    
    # 許可權元件:校驗使用者許可權 - 必須登入、所有使用者、登入讀寫遊客只讀、自定義使用者角色
    # 認證通過:可以進入下一步校驗(頻率認證)
    # 認證失敗:丟擲異常,返回403許可權異常結果
    self.check_permissions(request)
    
    # 頻率元件:限制檢視介面被訪問的頻率次數 - 限制的條件(IP、id、唯一鍵)、頻率週期時間(s、m、h)、頻率的次數(3/s)
    # 沒有達到限次:正常訪問介面
    # 達到限次:限制時間內不能訪問,限制時間達到後,可以重新訪問
    self.check_throttles(request)

只要繼承了APIView,檢視類中的request物件都是新的,也就是上面那個request物件,

原生的request在新的request._request中

以後使用request物件,就像使用之前的request是一樣的(因為重寫了__getattr__方法)

from rest_framework.request import Request

# 點攔截,當新request.的時候觸發
def __getattr__(self, attr):
    try:
        return getattr(self._request, attr)	# 通過反射,獲取原生request物件,取出屬性或方法
    except AttributeError:
        return self.__getattribute__(attr)
    
# request.data 是一個方法,不過是被@property裝飾了
# request.data 是一個字典,post請求不管使用什麼編碼,傳過來的資料都在request.data中

# get請求的資料還可以在request.query_params中取
@property
def query_params(self):
    return self._request.GET

# 檢視類中
print(request.query_params)	# get請求,地址中的引數
# 原來在
print(request.GET)

相關文章