1.API與用戶的通訊協議,老是使用HTTPs協議。
2.域名
https://api.example.com 儘可能將API部署在專用域名(會存在跨域問題) https://example.org/api/ API很簡單
3.版本
URL,如:https://api.example.com/v1/ 請求頭 跨域時,引起發送屢次請求
4.路徑,視網絡上任何東西都是資源,均使用名詞表示(可複數)
https://api.example.com/v1/zoos https://api.example.com/v1/animals https://api.example.com/v1/employees
5.method
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.狀態碼
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 - [*]:服務器發生錯誤,用戶將沒法判斷髮出的請求是否成功。 更多看這裏:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
8.錯誤處理,應返回錯誤信息,error當作key。
{ error: "Invalid API key" }
9.返回結果,針對不一樣操做,服務器向用戶返回的結果應該符合如下規範。
GET /collection:返回資源對象的列表(數組) GET /collection/resource:返回單個資源對象 POST /collection:返回新生成的資源對象 PUT /collection/resource:返回完整的資源對象 PATCH /collection/resource:返回完整的資源對象 DELETE /collection/resource:返回一個空文檔
10.Hypermedia API,RESTful API最好作到Hypermedia,即返回結果中提供連接,連向其餘API方法,使得用戶不查文檔,也知道下一步應該作什麼。 {"link": { "rel": "collection https://www.example.com/zoos", "href": "https://api.example.com/zoos", "title": "List of zoos", "type": "application/vnd.yourformat+json" }}
setting裏html
REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES':[ 'rest_framework.parsers.JSONParser' 'rest_framework.parsers.FormParser' 'rest_framework.parsers.MultiPartParser' ] }
路由:python
urlpatterns = [ url(r'test/', TestView.as_view()), ]
視圖函數:web
from rest_framework.views import APIView from rest_framework.response import Response class TestView(APIView): def post(self, request, *args, **kwargs): print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
還有局部使用sql
僅處理請求頭content-type爲application/json的請求體數據庫
url.pydjango
from django.conf.urls import url, include from web.views.s5_parser import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]
views.pyjson
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.parsers import JSONParser class TestView(APIView): parser_classes = [JSONParser, ] def post(self, request, *args, **kwargs): print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
僅處理請求頭content-type爲application/x-www-form-urlencoded 的請求體api
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]
#!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.parsers import FormParser class TestView(APIView): parser_classes = [FormParser, ] def post(self, request, *args, **kwargs): print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
c. 僅處理請求頭content-type爲multipart/form-data的請求體跨域
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]
#!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.parsers import MultiPartParser class TestView(APIView): parser_classes = [MultiPartParser, ] def post(self, request, *args, **kwargs): print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="http://127.0.0.1:8000/test/" method="post" enctype="multipart/form-data"> <input type="text" name="user" /> <input type="file" name="img"> <input type="submit" value="提交"> </form> </body> </html>
d. 僅上傳文件數組
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'test/(?P<filename>[^/]+)', TestView.as_view(), name='test'), ]
#!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.parsers import FileUploadParser class TestView(APIView): parser_classes = [FileUploadParser, ] def post(self, request, filename, *args, **kwargs): print(filename) print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="http://127.0.0.1:8000/test/f1.numbers" method="post" enctype="multipart/form-data"> <input type="text" name="user" /> <input type="file" name="img"> <input type="submit" value="提交"> </form> </body> </html>
e. 同時多個Parser
當同時使用多個parser時,rest framework會根據請求頭content-type自動進行比對,並使用對應parser
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]
#!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.parsers import JSONParser, FormParser, MultiPartParser class TestView(APIView): parser_classes = [JSONParser, FormParser, MultiPartParser, ] def post(self, request, *args, **kwargs): print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
參考:https://www.cnblogs.com/liuqingzheng/articles/9766387.html
局部配置
視圖裏:
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class Course(APIView): renderer_classes = [JSONRenderer,] def get(self,request,*args,**kwargs): return Response('ok')
全局配置
settings.py
REST_FRAMEWORK={
'DEFAULT_RENDERER_CLASSES':['rest_framework.renderers.JSONRenderer',], }
不存數據庫的token驗證
def get_token(id,salt='123'): import hashlib md=hashlib.md5() md.update(bytes(str(id),encoding='utf-8')) md.update(bytes(salt,encoding='utf-8')) return md.hexdigest()+'|'+str(id) def check_token(token,salt='123'): ll=token.split('|') import hashlib md=hashlib.md5() md.update(bytes(ll[-1],encoding='utf-8')) md.update(bytes(salt,encoding='utf-8')) if ll[0]==md.hexdigest(): return True else: return False class TokenAuth(): def authenticate(self, request): token = request.GET.get('token') success=check_token(token) if success: return else: raise AuthenticationFailed('認證失敗') def authenticate_header(self,request): pass class Login(APIView): def post(self,reuquest): back_msg={'status':1001,'msg':None} try: name=reuquest.data.get('name') pwd=reuquest.data.get('pwd') user=models.User.objects.filter(username=name,password=pwd).first() if user: token=get_token(user.pk) # models.UserToken.objects.update_or_create(user=user,defaults={'token':token}) back_msg['status']='1000' back_msg['msg']='登陸成功' back_msg['token']=token else: back_msg['msg'] = '用戶名或密碼錯誤' except Exception as e: back_msg['msg']=str(e) return Response(back_msg) from rest_framework.authentication import BaseAuthentication class TokenAuth(): def authenticate(self, request): token = request.GET.get('token') token_obj = models.UserToken.objects.filter(token=token).first() if token_obj: return else: raise AuthenticationFailed('認證失敗') def authenticate_header(self,request): pass class Course(APIView): authentication_classes = [TokenAuth, ] def get(self, request): return HttpResponse('get') def post(self, request): return HttpResponse('post')
建立認證類
from rest_framework.authentication import BaseAuthentication class TokenAuth(): def authenticate(self, request): token = request.GET.get('token') token_obj = models.UserToken.objects.filter(token=token).first() if token_obj: return token_obj.user,token_obj else: raise AuthenticationFailed('認證失敗') def authenticate_header(self,request): pass
views.py
def get_random(name): import hashlib import time md=hashlib.md5() md.update(bytes(str(time.time()),encoding='utf-8')) md.update(bytes(name,encoding='utf-8')) return md.hexdigest() class Login(APIView): def post(self,reuquest): back_msg={'status':1001,'msg':None} try: name=reuquest.data.get('name') pwd=reuquest.data.get('pwd') user=models.User.objects.filter(username=name,password=pwd).first() if user: token=get_random(name) models.UserToken.objects.update_or_create(user=user,defaults={'token':token}) back_msg['status']='1000' back_msg['msg']='登陸成功' back_msg['token']=token else: back_msg['msg'] = '用戶名或密碼錯誤' except Exception as e: back_msg['msg']=str(e) return Response(back_msg) class Course(APIView): authentication_classes = [TokenAuth, ] def get(self, request): return HttpResponse('get') def post(self, request): return HttpResponse('post')
局部使用,只須要在視圖類里加入:
authentication_classes = [TokenAuth, ]
全局
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",] }
from rest_framework.permissions import BasePermission class UserPermission(BasePermission): message = '不是超級用戶,查看不了' def has_permission(self, request, view): # user_type = request.user.get_user_type_display() # if user_type == '超級用戶': user_type = request.user.user_type print(user_type) if user_type == 1: return True else: return False class Course(APIView): authentication_classes = [TokenAuth, ] permission_classes = [UserPermission,] def get(self, request): return HttpResponse('get') def post(self, request): return HttpResponse('post')
局部使用只須要在視圖類里加入:
permission_classes = [UserPermission,]
全局
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",] }
自定義的邏輯
#(1)取出訪問者ip # (2)判斷當前ip不在訪問字典裏,添加進去,而且直接返回True,表示第一次訪問,在字典裏,繼續往下走 # (3)循環判斷當前ip的列表,有值,而且當前時間減去列表的最後一個時間大於60s,把這種數據pop掉,這樣列表中只有60s之內的訪問時間, # (4)判斷,當列表小於3,說明一分鐘之內訪問不足三次,把當前時間插入到列表第一個位置,返回True,順利經過 # (5)當大於等於3,說明一分鐘內訪問超過三次,返回False驗證失敗
class MyThrottles(): VISIT_RECORD = {} def __init__(self): self.history=None def allow_request(self,request, view): #(1)取出訪問者ip # print(request.META) ip=request.META.get('REMOTE_ADDR') import time ctime=time.time() # (2)判斷當前ip不在訪問字典裏,添加進去,而且直接返回True,表示第一次訪問 if ip not in self.VISIT_RECORD: self.VISIT_RECORD[ip]=[ctime,] return True self.history=self.VISIT_RECORD.get(ip) # (3)循環判斷當前ip的列表,有值,而且當前時間減去列表的最後一個時間大於60s,把這種數據pop掉,這樣列表中只有60s之內的訪問時間, while self.history and ctime-self.history[-1]>60: self.history.pop() # (4)判斷,當列表小於3,說明一分鐘之內訪問不足三次,把當前時間插入到列表第一個位置,返回True,順利經過 # (5)當大於等於3,說明一分鐘內訪問超過三次,返回False驗證失敗 if len(self.history)<3: self.history.insert(0,ctime) return True else: return False def wait(self): import time ctime=time.time() return 60-(ctime-self.history[-1])
寫一個類,繼承自SimpleRateThrottle,(根據ip限制)問:要根據用戶如今怎麼寫
from rest_framework.throttling import SimpleRateThrottle class VisitThrottle(SimpleRateThrottle): scope = 'luffy' def get_cache_key(self, request, view): return self.get_ident(request)
在setting裏配置:(一分鐘訪問三次)
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES':{ 'luffy':'3/m' } }
在視圖類裏使用
throttle_classes = [MyThrottles,]
中文提示錯誤:
class Course(APIView): authentication_classes = [TokenAuth, ] permission_classes = [UserPermission, ] throttle_classes = [MyThrottles,] def get(self, request): return HttpResponse('get') def post(self, request): return HttpResponse('post') def throttled(self, request, wait): from rest_framework.exceptions import Throttled class MyThrottled(Throttled): default_detail = '傻逼啊' extra_detail_singular = '還有 {wait} second.' extra_detail_plural = '出了 {wait} seconds.' raise MyThrottled(wait)
1 視圖類都繼承過哪些類
2
3 class View(object):
4
5
6 class APIView(View):
7
8
9 class GenericAPIView(views.APIView):
10
11
12 class GenericViewSet(ViewSetMixin, generics.GenericAPIView)
13
14
15 class ModelViewSet(mixins.CreateModelMixin,
16
17 mixins.RetrieveModelMixin,
18
19 mixins.UpdateModelMixin,
20
21 mixins.DestroyModelMixin,
22
23 mixins.ListModelMixin,
24
25 GenericViewSet):
from rest_framework.pagination import CursorPagination # 看源碼,是經過sql查詢,大於id和小於id class Pager(APIView): def get(self,request,*args,**kwargs): # 獲取全部數據 ret=models.Book.objects.all() # 建立分頁對象 page=CursorPagination() page.ordering='nid' # 在數據庫中獲取分頁的數據 page_list=page.paginate_queryset(ret,request,view=self) # 對分頁進行序列化 ser=BookSerializer1(instance=page_list,many=True) # 能夠避免頁碼被猜到 return page.get_paginated_response(ser.data)
# http://127.0.0.1:8000/pager/?offset=4&limit=3 from rest_framework.pagination import LimitOffsetPagination # 也能夠自定製,同簡單分頁 class Pager(APIView): def get(self,request,*args,**kwargs): # 獲取全部數據 ret=models.Book.objects.all() # 建立分頁對象 page=LimitOffsetPagination() # 在數據庫中獲取分頁的數據 page_list=page.paginate_queryset(ret,request,view=self) # 對分頁進行序列化 ser=BookSerializer1(instance=page_list,many=True) # return page.get_paginated_response(ser.data) return Response(ser.data)
from rest_framework.pagination import PageNumberPagination # 一 基本使用:url=url=http://127.0.0.1:8000/pager/?page=2&size=3,size無效 class Pager(APIView): def get(self,request,*args,**kwargs): # 獲取全部數據 ret=models.Book.objects.all() # 建立分頁對象 page=PageNumberPagination() # 在數據庫中獲取分頁的數據 page_list=page.paginate_queryset(ret,request,view=self) # 對分頁進行序列化 ser=BookSerializer1(instance=page_list,many=True) return Response(ser.data) # 二 自定製 url=http://127.0.0.1:8000/pager/?page=2&size=3 # size=30,無效,最多5條 class Mypage(PageNumberPagination): page_size = 2 page_query_param = 'page' # 定製傳參 page_size_query_param = 'size' # 最大一頁的數據 max_page_size = 5 class Pager(APIView): def get(self,request,*args,**kwargs): # 獲取全部數據 ret=models.Book.objects.all() # 建立分頁對象 page=Mypage() # 在數據庫中獲取分頁的數據 page_list=page.paginate_queryset(ret,request,view=self) # 對分頁進行序列化 ser=BookSerializer1(instance=page_list,many=True) # return Response(ser.data) # 這個也是返回Response對象,可是比基本的多了上一頁,下一頁,和總數據條數(瞭解便可) return page.get_paginated_response(ser.data)
setting裏
REST_FRAMEWORK = { # 每頁顯示兩條 'PAGE_SIZE':2 }
路由:
url(r'^pager/$', views.Pager.as_view()),
Serializers
class BookSerializer1(serializers.ModelSerializer): class Meta: model=models.Book # fields="__all__" exclude=('authors',)
from rest_framework.versioning import QueryParameterVersioning,AcceptHeaderVersioning,NamespaceVersioning,URLPathVersioning #基於url的get傳參方式:QueryParameterVersioning------>如:/users?version=v1 #基於url的正則方式:URLPathVersioning------>/v1/users/ #基於 accept 請求頭方式:AcceptHeaderVersioning------>Accept: application/json; version=1.0 #基於主機名方法:HostNameVersioning------>v1.example.com #基於django路由系統的namespace:NamespaceVersioning------>example.com/v1/users/
#在CBV類中加入 versioning_class = URLPathVersioning
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.QueryParameterVersioning', 'DEFAULT_VERSION': 'v1', # 默認版本(從request對象裏取不到,顯示的默認值) 'ALLOWED_VERSIONS': ['v1', 'v2'], # 容許的版本 'VERSION_PARAM': 'version' # URL中獲取值的key }
基於正則的方式:
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'), ]
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import URLPathVersioning class TestView(APIView): versioning_class = URLPathVersioning def get(self, request, *args, **kwargs): # 獲取版本 print(request.version) # 獲取版本管理的類 print(request.versioning_scheme) # 反向生成URL reverse_url = request.versioning_scheme.reverse('test', request=request) print(reverse_url) return Response('GET請求,響應內容')
# 基於django內置,反向生成url from django.urls import reverse url2=reverse(viewname='ttt',kwargs={'version':'v2'}) print(url2)
views.py #局部設置 from rest_framework.versioning import QueryParameterVersioning,URLPathVersioning class Course(APIView): # versioning_class = QueryParameterVersioning #get傳參 versioning_class = URLPathVersioning #url地址 推薦 def get(self,request,*args,**kwargs): return Response('ok') settings.py #全局設置 REST_FRAMEWORK={ 'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning', 'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.QueryParameterVersioning', 'ALLOWED_VERSIONS':['v1','v2',], #容許版本 'VERSION_PARAM':'version', #參數 'DEFAULT_VERSION':'v1', #默認版本 } urls.py #get傳參 http://127.0.0.1:8000/api/course/?version=v1 url(r'^api/$',include('api.urls')) url(r'^course/$',CourseView.as_view())
api/urls.py
#url地址 http://127.0.0.1:8000/api/v1/course/ url(r'^api/$',include('api.urls'))
url(r'^(?P<version>[v1|v2]+)/course/$',CourseView.as_view()) #推薦 url(r'^api/(?P<version>\w+)/$',include('api.urls')) url(r'^(?P<version>)\w+/course/$',CourseView.as_view())
獲取版本
request.version