不少API並非真正的實現了RESTful,而應該叫作RPC (Remote Procedure Call 遠程過程調用),Roy Fielding曾經提到了它們的區別,原文以下:html
I am getting frustrated by the number of people calling any HTTP-based interface a REST API. Today’s example is the SocialSite REST API. That is RPC. It screams RPC. There is so much coupling on display that it should be given an X rating. What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint? In other words, if the engine of application state (and hence the API) is not being driven by hypertext, then it cannot be RESTful and cannot be a REST API. Period. Is there some broken manual somewhere that needs to be fixed? — Roy Fielding https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
大概意思是,若是應用狀態引擎(API)不是超文本驅動的,那麼就不是RESTful。python
個人理解是,像超文本同樣攜帶一個地址,能夠尋址定位信息,如超文本的link屬性。spring
Hypermedia指的是,返回結果中提供連接,連向其餘API方法,使得用戶不查文檔,也知道下一步應該作什麼。好比,當用戶向api.example.com的根目錄發出請求,會獲得這樣一個文檔:數據庫
{"link": { "rel": "collection https://www.example.com/zoos", "href": "https://api.example.com/zoos", "title": "List of zoos", "type": "application/vnd.yourformat+json" }}
上面代碼表示,文檔中有一個link屬性,用戶讀取這個屬性就知道下一步該調用什麼API了。rel表示這個API與當前網址的關係(collection關係,並給出該collection的網址),href表示API的路徑,title表示API的標題,type表示返回類型。django
摘取自:http://www.ruanyifeng.com/blog/2014/05/restful_api.htmljson
回到教程的例子。在前面咱們已經爲snippets
和users
建立了Endpoint,如今來建立根目錄的Endpoint,編輯snippets/views.py
:api
from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.reverse import reverse @api_view(['GET']) def api_root(request, format=None): return Response({ 'users': reverse('user-list', request=request, format=format), 'snippets': reverse('snippet-list', request=request, format=format) })
reverse()
函數用來返回snippets/urls.py
中viewname對應的url,如path('users/', views.UserList.as_view(), name='user-list')
。restful
而後添加到snippets/urls.py
中:app
path('', views.api_root),
還記得在上篇文章中提到的Snippet.highlighted
字段麼:ide
咱們如今爲它建立Endpoint,繼續編輯snippets/views.py
:
from rest_framework import renderers from rest_framework.response import Response class SnippetHighlight(generics.GenericAPIView): queryset = Snippet.objects.all() renderer_classes = [renderers.StaticHTMLRenderer] def get(self, request, *args, **kwargs): snippet = self.get_object() return Response(snippet.highlighted)
而後添加到snippets/urls.py
中:
path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view()),
由於
snippet.highlighted
不是JSON而是HTML,因此用[renderers.StaticHTMLRenderer]
返回預渲染的(pre-rendered)HTML。
在Web API設計中,通常有如下幾種方式來表示實體之間的關係:
前2個比較熟悉,後面幾個有點不太懂,我理解是相似於數據庫的關聯關係表。
DRF支持以上全部方式,這裏咱們用DRF的HyperlinkedModelSerializer
來實現真正的RESTful。在snippets/serializers.py
中把咱們以前的代碼:
class SnippetSerializer(serializers.ModelSerializer): owner = serializers.ReadOnlyField(source='owner.username') class Meta: model = Snippet fields = ['id', 'title', 'code', 'linenos', 'language', 'style', 'owner'] class UserSerializer(serializers.ModelSerializer): snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all()) class Meta: model = User fields = ['id', 'username', 'snippets']
修改成:
class SnippetSerializer(serializers.HyperlinkedModelSerializer): owner = serializers.ReadOnlyField(source='owner.username') highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html') class Meta: model = Snippet fields = ['url', 'id', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style'] class UserSerializer(serializers.HyperlinkedModelSerializer): snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True) class Meta: model = User fields = ['url', 'id', 'username', 'snippets']
其中ModelSerializer
換成了HyperlinkedModelSerializer
,後者的區別以下:
默認不包含id
字段
包含url
字段,用HyperlinkedIdentityField
表示
源碼:
serializer_url_field = HyperlinkedIdentityField
關係用HyperlinkedRelatedField
表示,而不是PrimaryKeyRelatedField
源碼:
serializer_related_field = HyperlinkedRelatedField
因爲用了HyperlinkedModelSerializer,SnippetSerializer和UserSerializer的url字段默認指向的是'{model_name}-detail'
url pattern,這是DRF定義的,在示例中就是'snippet-detail'
和'user-detail'
。新增的highlight
字段和url
字段是同樣的類型,它指向的是'snippet-highlight'
,而不是'snippet-detail'
。
既然已經提到了url pattern,那麼在snippets/urls.py
中修改一下:
from django.urls import path from rest_framework.urlpatterns import format_suffix_patterns from snippets import views # API endpoints urlpatterns = format_suffix_patterns([ path('', views.api_root), path('snippets/', views.SnippetList.as_view(), name='snippet-list'), path('snippets/<int:pk>/', views.SnippetDetail.as_view(), name='snippet-detail'), path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view(), name='snippet-highlight'), path('users/', views.UserList.as_view(), name='user-list'), path('users/<int:pk>/', views.UserDetail.as_view(), name='user-detail') ])
name就是在
serializers.py
和views.py
中用到的。
REST設計基本原則提到了:處理好分頁。DRF添加分頁的方式很簡單,編輯tutorial/settings.py
文件:
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10 }
我以前是在學SpringBoot的時候瞭解過RESTful API的超連接API,文章開頭的那一段介紹就是當時寫的筆記,DRF提供了HyperlinkedModelSerializer
來實現,仍是比較好理解的,其中的細節須要在實戰中再多多熟悉。
參考資料:
https://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/