Restful規範-APIView源碼分析
- 2021 年 7 月 3 日
- 筆記
一、Restful規範
Restful規範是一種web API介面的設計風格,在前後端分離的應用模式中適用較多。
這種風格的理念認為後端開發任務就是提供數據的,對外提供的是數據資源的訪問介面,所以在定義介面時,客戶端訪問的URL路徑就表示這種要操作的數據資源。
十條規範
1、是數據的安全保障:url鏈接一般都採用HTTPS協議進行傳輸
2、介面特徵表現,一看就知道是個api介面
用api關鍵字標識介面url:
//api.baidu.com
//www.baidu.com/api
3、多數據版本共存
在url鏈接中標識數據版本:url鏈接中的v1、v2就是不同數據版本的體現(只有在一種數據資源有多版本情況下)
//api.baidu.com/v1
//api.baidu.com/v2
4、數據即是資源,均使用名詞(可複數) **
介面一般都是完成前後台數據的交互,交互的數據我們稱為資源
//api.baidu.com/users
//api.baidu.com/books
//api.baidu.com/book
**5、資源操作由請求方式決定(method) ** **
操作資源一般都會涉及到增刪改查,使用提供請求方式來標識增刪改查動作
//api.baidu.com/books - get請求:獲取所有書
//api.baidu.com/books/1 - get請求:獲取主鍵為1的書
//api.baidu.com/books - post請求:新增一本書書
//api.baidu.com/books/1 - put請求:整體修改主鍵為1的書
//api.baidu.com/books/1 - delete請求:刪除主鍵為1的書
6、過濾,通過在url上傳參的形式傳遞搜索條件
//api.example.com/v1/zoos?limit=10: 指定返回記錄的數量
//api.example.com/v1/zoos?offset=10: 指定返回記錄的開始位置
//api.example.com/v1/zoos?page=2&per_page=100:指定第幾頁,以及每頁的記錄數
//api.example.com/v1/zoos?sortby=name&order=asc:指定返回結果按照哪個屬性排序,以及排序順序
//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": "//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)