內容概要 web 開發模式 API 介面 postman 測試軟體的使用 restful 規範 drf 的安裝與使用 cbv 的 View 源碼分析 APIView 源碼分析 drf 的 Request 類 drf 的 APIView 類執行過程 內容詳細 web 開發模式 1、前後端不分離 在開發 ...
內容概要
- web 開發模式
- API 介面
- postman 測試軟體的使用
- restful 規範
- drf 的安裝與使用
- cbv 的 View 源碼分析
- APIView 源碼分析
- drf 的 Request 類
- drf 的 APIView 類執行過程
內容詳細
web 開發模式
1、前後端不分離
在開發一個網站的過程中,前端頁面需要使用後端框架的模板語法(DTL) 來渲染,比如 Django 自帶的模板語法或者 jinjia2,這種前端頁面在後端渲染完成之後才會把頁面文檔傳送給前端
2、前後端分離
目前主流的開發模式,這種模式中前端先在後端的靜態文件伺服器(nfs)中獲取靜態文件(static 中的HTML、CSS、JS代碼),需要數據的時候向後端發送請求只獲取json格式的數據,再在前端運用js的BOM與DOM操作對頁面進行渲染。
這樣便實現了前後端分離,前端只需要從後端獲取一定格式的數據,那麼前端就可以是一個網頁、app或者小程式,提高開發水平。
API 介面
簡介: api 介面是前後端交互數據的媒介
api 介面包含:
- 1、url 地址: 向後端請求數據的地址
- 2、請求方式 : get、post、put、patch、delete
- 3、請求參數:在 url 地址後面緊跟著的 ?name='elijah',可以看作是過濾條件
- 4、響應結果:包括響應首行(響應狀態碼)、響應頭、響應體(json格式數據)
postman 測試軟體的使用
過去我們開髮網站開放出來的介面一般使用瀏覽器發送get請求來進行測試,但基於api介面的restful設計規範,我們還需要使用 post請求、put請求等,而瀏覽器只能發送 get請求
postman 是一種測試介面共組,在 postman 中可以滿足我們的測試需求
市面上也有許多測試軟體(postwoman),我們使用主流的postman
下載地址: https://www.getpostman.com/downloads/
使用:
1、發送請求
前後端數據交互的編碼格式:
- urlencoded:正常的post請求提交數據:name=elijah
- formdata:post請求上傳文件:帶文件二進位形式
- json:在body體中的數據格式為:{"name":"elijah","age":18} (用的多)
發起請求時,可以在 body 中書寫請求體:
2、建立集合批量發起測試
同時,這個批量測試也支持導出文件,然後發送給別人,再從文件中導出
restful 規範
REST與技術無關,代表的是一種軟體架構風格,REST是Representational State Transfer的簡稱,中文翻譯為“表徵狀態轉移”或“表現層狀態轉化”。它首次出現在2000年Roy Fielding的博士論文中。
在前後端分離的開發模式中,為了方便數據交互,我們在設計前後端進行數據交互的 api 介面時,需要符合 restful 規範,它是一種寫前後端分離的標準
對於REST這種面向資源的架構風格,有人提出一種全新的結構理念,即:面向資源架構(ROA:Resource Oriented Architecture)
10條 restful 規範:
1、通信協議一般使用 https 協議,為保障數據安全
2、設計的介面地址是可以讓人一眼看出來是 api 介面(在介面中帶api字眼)
3、多數據版本共存
介面更新之後,老版本的介面依舊一起使用,等過了期限才棄用老版本(維護老版本介面),以便可以留存更多用戶。
4、交互數據即是資源,使用名詞(可以是複數)
不建議使用動詞,如 get_resource ,除非是 login 或者 register 這種介面
5、用不同的請求方式表示不同的資源操作
- get: 獲取資源
- post: 新增資源
- put: 修改資源(全局修改)
- patch: 修改資源(局部修改)
- delete: 刪除資源
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、響應狀態碼(http的響應狀態碼,響應體的json數據中帶狀態碼)
http 協議的狀態碼:
更多狀態碼請參考: https://www.cnblogs.com/elijah-li/p/16069638.html
1xx:請求正在處理
200 OK - [GET]:伺服器成功返回用戶請求的數據,該操作是冪等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功。
202 Accepted - [*]:表示一個請求已經進入後臺排隊(非同步任務)
204 NO CONTENT - [DELETE]:用戶刪除數據成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,伺服器沒有進行新建或修改數據的操作,該操作是冪等的。
401 Unauthorized - [*]:表示用戶沒有許可權(令牌、用戶名、密碼錯誤)。
403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對),但是訪問是被禁止的。
404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,伺服器沒有進行操作,該操作是冪等的。
406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 當創建一個對象時,發生一個驗證錯誤。
500 INTERNAL SERVER ERROR - [*]:伺服器發生錯誤,用戶將無法判斷發出的請求是否成功。
更多狀態碼請參考: https://www.cnblogs.com/elijah-li/p/16069638.html
服務端自定義的狀態碼
響應體中返回json格式數據,數據中有 服務端自定義的狀態碼(code,status)
- 1001 :用戶名錯誤
- 1002 :沒有許可權
8、返回錯誤信息(錯誤處理)
返回數據是json格式,帶狀態碼和錯誤信息
{
code: 1002
error: "Invalid API key"
}
9、不同的請求方式(不同操作)應該返回不同的資源
- get:/collection/resources 獲取多個資源 --》 返回資源對象的列表 [{name:elijah,age:18},{name:pyy,age:33}]
- get /collection/resource 返回單個資源對象 {name:elijah,age:18}
- post: 新增資源 --》 返回新增的資源對象 {name:elijah,age:18}
- put: 修改資源(全局修改) --》 返回修改的資源對象 {name:elijah,age:18}
- patch: 修改資源(局部修改) --》 返回修改的資源對象 {name:elijah,age:18}
- delete: 刪除資源 --》 返回空文檔(或者錯誤文檔) {code:100,msg:刪除成功}
10、返回資源中要跳轉的鏈接地址
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos", '''連接地址'''
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
drf 的安裝與使用
1、安裝 djangorestframework
pip install djangorestframework==3.10.3
2、在 settings.py 文件中 註冊 rest_framework
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'rest_framework',
]
3、在 models.py 中寫表模型
class Book(models.Model):
name = models.CharField(verbose_name='書名', max_length=32)
price = models.DecimalField(verbose_name='價格', max_digits=5, decimal_places=2)
author = models.CharField(verbose_name='作者', max_length=32)
class Meta:
verbose_name_plural = '書籍表'
4、新建一個序列化類
在應用文件夾下創建一個 ser.py 作為序列化模塊
from rest_framework.serializers import ModelSerializer
from app01.models import Book
class BookModelSerializer(ModelSerializer): # 繼承的是 ModelSerializer
class Meta:
model = Book
fields = '__all__' # 註意別漏寫 fields
5、書寫視圖函數(CBV)
from rest_framework.viewsets import ModelViewSet
from .ser import BookModelSerializer, Book
# Create your views here.
class BookModelViewSet(ModelViewSet): # 繼承的是 ModelViewSet
queryset = Book.objects.all()
serializer_class = BookModelSerializer
6、書寫 url
from app01 import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('books', views.BookModelViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
]
urlpatterns += router.urls
9、遷移資料庫
python manage.py makemigrations
python manage.py migrate
10、在瀏覽器中測試介面
11、在 postman 中測試介面
註意: 在發送 put 請求(修改資源)、patch 請求(修改資源)、delete 請求(刪除資源)時,得在名詞後面添加具體的資源 id 值,並且要加上 “/” ,因為 postman 不會自動加斜桿
獲取資源:
新增資源:
修改資源:
刪除資源:
cbv 的 View 源碼分析
從路由文件 urls.py 的調用語句中看起
urlpatterns = [
path('CBV/', views.MyClass.as_view()),
]
# as_view() 函數加括弧優先調用 as_view函數
按住 ctrl 鍵,滑鼠點擊查看 as_view 源碼
@classonlymethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs) # self 是我們自定義類產生的對象
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'): # 自定義類必須接收 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) # view 函數調用 View 類中的dispath函數
# ... 中間省略部分源碼
return view # 返回view函數對象
從源碼中可以看出,as_view 調用之後得到的是 view 函數對象,這是個閉包函數,當視圖函數被觸發,調用的是 view函數,view函數return一個 dispatch 函數調用結果
我們再查看 dispatch 的源碼:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names: # 如果是request中的屬性,則用反射調用自身的響應的函數
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
APIView 源碼分析
APIView 是 rest_framework.views 中的一個類,是 drf 提供給我們更多功能的視圖函數類,也繼承了 Django 的 View
基於 CBV 先定義一個視圖函數
views.py :
from rest_framework.views import APIView
class BookAPIView(APIView):
def get(self, request):
pass
def post(self, request):
pass
urls.py :
urlpatterns = [
path('admin/', admin.site.urls),
path('book/',views.BookAPIView.as_view()) # 調用 as_view() 返回的是 view 函數的記憶體地址
]
調用的是 APIView 的 as_view 方法:
APIView 類繼承的是 Django 的 View,所以 super().as_view(**initkwargs) 調用了父類(View)中的 as_view,返回的是 View 中 as_view 返回的 view,在 view 函數里調用的 self.dispatch(request, *args, kwargs) 根據類的查找順序,調用的就是 APIView 中的 dispatch
class APIView(View):
@classmethod
def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs)
view.cls = cls
view.initkwargs = initkwargs
return csrf_exempt(view) # 所有繼承了 APIView 的視圖函數都忽略了 csrf 認證
- 調用了 APIView 的 dispatch方法
- 忽略了視圖函數的 csrf 認證
csrf_exempt(index) 相當於
@csrf_exempt
def index(request)
pass
# index=csrf_exempt(index)
APIView 中的 dispatch:
def dispatch(self, request, *args, **kwargs):
# 重寫了 Django 傳過來的 request 請求數據對象,給request增添了新屬性
request = self.initialize_request(request, *args, **kwargs)
self.request = request
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)
# 處理放回數據,具有 drf 特色的頁面
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
APIView 中的 initialize_request:
對原生的 request 進行處理,新增了一些功能和屬性
def initialize_request(self, request, *args, **kwargs):
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
initialize_request 中的 Request():
把原生的 request 賦給了 _request,原 request 請求對象的方法在 _request 中獲取就可以
class Request:
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
self._request = request
self._data = Empty
request 的變化
新的:<class 'rest_framework.request.Request'>
老的:<class 'django.core.handlers.wsgi.WSGIRequest'>
三大認證:
# 三大認證人如何走的
self.initial(request, *args, **kwargs)---》APIView的
核心代碼:
self.perform_authentication(request) # 認證
self.check_permissions(request) #許可權
self.check_throttles(request) # 頻率
drf 的 Request 類
重寫了 request 請求數據對象之後,原生的 request 方法一樣能用,因為 Request 類中的 getattr 方法(對象的 '.' 點攔截)中用放射機制獲取了原生 request 的所有屬性
Request --> __getattr__
def __getattr__(self, attr):
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
前端post請求傳入的數據,在原來的request.POST中只能處理urlencoded和formdata編碼格式,json格式不能處理。
而 Request 類實例化之後,提供了一個 data 方法,無論前端用什麼編碼post提交的數據,都從 request.data 中獲取。
@property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data