問(wèn)題1:RESTful框架到底解決了什么問(wèn)題?(URL具有自描述性、資源表述與視圖的解耦合、互操作性利用構(gòu)建微服務(wù)以及集成第三方系統(tǒng)、無(wú)狀態(tài)性太高水平擴(kuò)展能力)
問(wèn)題2:項(xiàng)目在使用RESTful架構(gòu)時(shí)有沒(méi)有遇到一些問(wèn)題或隱患?(對(duì)資源訪問(wèn)的限制、資源從屬關(guān)系檢查、避免泄露業(yè)務(wù)信息防范可能的攻擊)
補(bǔ)充:下面的幾個(gè)和安全性相關(guān)的響應(yīng)頭在前面講中間件的時(shí)候提到過(guò)的。
- X-Frame-Options:DENY
- X-Content-Type-Options:nosniff
- X-XSS-Protection:1;mode=block;
- Strict-Transport-Security:max-age=3153600;
問(wèn)題3:如何保護(hù)API中的敏感信息以及防范重放攻擊?(摘要和令牌)
推薦閱讀:《如何有效防止API的重放攻擊》。
使用djangorestframework
安裝djangorestframework(為了方便描述,以下同意簡(jiǎn)稱drf)。
pip install djangorestframework
配置drf
INSTALLED_APPS = [
'rest_framework',
]
REST_FRAMEWORK = {
# 配置默認(rèn)頁(yè)面大小
'PAGE_SIZE': 10,
# 配置默認(rèn)的分頁(yè)類
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 配置異常處理器
# 'EXCEPTION_HANDLER': 'api.exceptions.exception_handler',
# 配置默認(rèn)解析器
# 'DEFAULT_PARSER_CLASSES': (
# 'rest_framework.parsers.JSONParser',
# 'rest_framework.parsers.FormParser',
# 'rest_framework.parsers.MultiPartParser',
# ),
# 配置默認(rèn)限流類
# 'DEFAULT_THROTTLE_CLASSES': (),
# 配置默認(rèn)授權(quán)類
# 'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.IsAuthenticated',
# ),
# 配置默認(rèn)認(rèn)證類
# 'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
# ),
}
編寫(xiě)序列化器
from rest_framework import serializers
from rest_framewor.serializers import MpdelSerializer
from common.models import District, HouseType, Estate, Agent
class DistrictSerializer(ModelSerializer):
class Meta:
model = District
fields = ('distid', 'name')
class HouseTypeSerializer(ModelSerializer):
class Meta:
model = HouseType
fields = '__all__'
class AgentSerializer(ModelSerializer):
class Meta:
model = Agent
fields = ('agentid', 'name', 'tel', 'servstar', 'certificated')
class EstateSerializer(ModelSerializer):
district = serializers.SerializerMethodField()
agents = serializers.SerializerMethodField()
@staticmethod
def get_agent(estate):
return AgentSerializer(estate.agents, many = True).data
@staticmethod
def get_district(estate):
return DistrictSerializer(estate.district).data
class Meta;
model = Estate
fields = '__all__'
方法1:使用裝飾器
@api_view(['GET'])
@cache_page(timeout=None,cache='api')
def province(request):
query = District.objects.filter(parent_isnull=True)
serializer = DisterictSerializer(queryset, many=True)
return Response(serializer.data)
@api_view(['GET'])
@cache_page(timeout=300, cache='api')
def cities(request, provid)
queryset = District.objects.filter(parent__distid=provid)
serializer = DistrictSerializer(queryset, many=True)
return Response(serializer.data)
urlpattern = [
path = ('districts/', views.provinces, name='districts'),
path = ('districts/<int:provid>/', view.cities, name = 'cities')
]
說(shuō)明:上面使用了Django自帶的視圖裝飾器(@cache_page)來(lái)實(shí)現(xiàn)API接口返回?cái)?shù)據(jù)的緩存。
方法2:使用APIview及其子類
更好的復(fù)用代碼,不要重復(fù)發(fā)明“輪子”。
class HouseTypeApiView(CacheResponseMixin, ListAPIView):
queryset = HouseType.objects.all()
serializer_class = HouseTypeSerializer
urlpattern = [
path('housetypes/', views.HouseTypeApiView.as_view(), name='housetypes'),
]
說(shuō)明:上面使用了drf_extensions提供的CacheResponseMixin混入類實(shí)現(xiàn)了對(duì)接口數(shù)據(jù)的緩存。如果重寫(xiě)了獲取數(shù)據(jù)的方法,可以使用drf_extensions提供的@Cache_response來(lái)實(shí)現(xiàn)對(duì)接口數(shù)據(jù)的緩存,也可以用自定義的函數(shù)來(lái)生成緩存中的key。當(dāng)然還有一個(gè)選擇就是通過(guò)Django提供的@method_decorator裝飾器,將@cache_page裝飾器處理為裝飾方法裝飾器,這樣也能提供使用緩存服務(wù)。
drf-extensions配置如下所示。
# 配置DRF擴(kuò)展來(lái)支持緩存API接口調(diào)用結(jié)果
REST_FRAMEWORK = {
'DEFAULT_CACHE_RESPONSE_TIMEOUT' : 300,
'DEFAULT_USER_CACHE' : 'default',
# 配置默認(rèn)緩存單個(gè)對(duì)象的key函數(shù)
'DEFAULT_OBJECT_CACHE_KEY_FUNC':'rest_framework_extensions.utils.default_object_cache_key_func',
# 配置默認(rèn)緩存對(duì)象列表的key函數(shù)
'DEFAULT_LIST_CACHE_KEY_FUNC' : 'rest_framework_extensions.utils.default_list_cache_key_func',
}
方法3:使用ViewSet及其子類
class HouseTypeView(CacheResponseMixin, viewsets.ModelViewSet):
queryset = HouseType.objects.all()
serializer_class = HouseTypeSerializer
pagination_class = None
router = DefaultRoute()
router.register('housetypes',views.HouseTypeViewSet)
urlpattern += router.urls
djangorestframework提供了Bootstrap定制的頁(yè)面來(lái)顯示接口返回的JSON數(shù)據(jù),當(dāng)然也可以使用POSTMAN這樣的工具來(lái)對(duì)API接口進(jìn)行測(cè)試
補(bǔ)充說(shuō)明
在這里順便提一下跟前端相關(guān)的幾個(gè)問(wèn)題
問(wèn)題一:如何讓瀏覽器能夠發(fā)起DELETE/PUT/PATCH?
<form method='post'>
<input type="hiden" name="_mehthod" value="delete">
</form>
if request.method == 'POST' and '_method' in request.POST:
request.method = request.POST['_method'].upper()
<script>
$.ajax({
'url' : '/api/provinces',
'type' : 'put',
'data' : {},
'dataType' : 'json',
'success' : 'functon(json){}',
'error' : function(){}
});
$.getJSON('api/provinces',function(json){});
</scritpt>
問(wèn)題2:如何解決多個(gè)JavaScript庫(kù)之間某個(gè)定義(如$函數(shù))沖突的 問(wèn)題?
<script src="js/jquery.min.js"></script>
<script src="js/abc.min.js"></script>
<script>
// $已經(jīng)被后加載的JavaScript庫(kù)占用了
// 但是可以直接用綁定在window對(duì)象上的jQuery去代替$
jQuery(function() {
jQuery('#okBtn').on('click', function() {});
});
</script>
<script src="js/abc.min.js"></script>
<script src="js/jquery.min.js"></script>
<script>
// 將$讓出給其他的JavaScript庫(kù)使用
jQuery.noConflict();
jQuery(function() {
jQuery('#okBtn').on('click', function() {});
});
</script>