原始碼分析 -解析器元件
阿新 • • 發佈:2018-12-20
一.基本使用
1.json解析器
同樣以views檢視為為例,新增json解析器
from rest_framework.versioning import URLPathVersioning from rest_framework.parsers import JSONParser class UserView(APIView): '''檢視使用者資訊''' parser_classes = [JSONParser,] versioning_class =URLPathVersioning def get(self,request,*args,**kwargs): res={"name":"wd","age":22} return JsonResponse(res,safe=True) def post(self,request,*args,**kwargs): print(request.data) #獲取解析後的請求結果 return JsonResponse({"success":"ok"}, safe=True)
使用postman向http://127.0.0.1:8000/api/v1/user檢視傳送json資料,注意請求頭必須是application/json
2.form表單解析器
(1).檢視
from rest_framework.versioning import URLPathVersioning from rest_framework.parsers import JSONParser,FormParser class UserView(APIView): '''檢視使用者資訊''' parser_classes = [JSONParser,FormParser] ##JSONParser,解析頭資訊Content-Type:application/json,的json資料 ##FormParser,解析頭資訊Content-Type:x-www-form-urlencoded資料versioning_class =URLPathVersioning def get(self,request,*args,**kwargs): res={"name":"wd","age":22} return JsonResponse(res,safe=True) def post(self,request,*args,**kwargs): print(request.data) #獲取解析後的請求結果 return JsonResponse({"success":"ok"}, safe=True)
使用postman傳送form表單資料
後臺接受,並且結果已經轉化為QueryDict型別了
二. 原始碼分析
1.根據以上示例,梳理解析去解析資料的流程
- 獲取使用者請求
- 獲取使用者請求體
- 根據使用者請求頭資訊個parase_classes=[...],中的請求頭進行比較,匹配上請求頭就使用該解析器處理
- 解析器從請求體中拿資料進行處理,處理完成之後將結果返回給request.data
2.原始碼解析
View層: class LoginView(APIView): parser_classes = [FormParser, JSONParser] def get(self, request): return render(request, 'login.html') def post(self, request): # request.data return HttpResponse("Ok") Template層: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> </head> <body> <form action="", method="post" enctype="application/x-www-form-urlencoded"> {% csrf_token %} <p>使用者名稱:<input type="text" name="user"></p> <p>密碼:<input type="password" name="password"></p> <p><input type="submit" value="提交" ></p> </form> <button class="btn">點選提交Ajax資料</button> </body> <script> $('.btn').click(function () { $.ajax({ url: "", type: 'post', contentType: "application/json", data: JSON.stringify({ name: "pizza", age: 18 }) }) }) </script> </html>示例程式碼:
(1).根據以上示例,梳理解析器解析資料流程
- 獲取使用者請求
- 獲取使用者請求體
- 根據使用者請求頭資訊和parase_classes=[...],中的請求頭進行比較,匹配上請求頭就使用該解析器處理
- 解析器從請求體中拿資料進行處理,處理完成之後將結果返回給request.data
(2). 原始碼剖析:
先執行APIView的dispatch方法,以下是原始碼, 分析請看註解dispatch方法:
def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ self.args = args self.kwargs = kwargs #對原始request進行加工,豐富了一些功能 #Request( # request, # parsers=self.get_parsers(), # authenticators=self.get_authenticators(), # negotiator=self.get_content_negotiator(), # parser_context=parser_context # ) #request(原始request,[BasicAuthentications物件,]) #獲取原生request,request._request #獲取認證類的物件,request.authticators #1.封裝request request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # Get the appropriate handler method 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
執行initialize_request()方法,在該方法中,get_parsers用於獲取解析器,並被封裝到request.parsers中。
def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request)# return Request( request, parsers=self.get_parsers(), #獲取所有的解析器,封裝到request.parsers中 authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context )
get_parsers()原始碼,和認證、許可權一樣,解析器採用列表生成式返回解析器物件的列表,所以示例中定義解析器的變數是parser_classes:
def get_parsers(self): """ Instantiates and returns the list of parsers that this view can use. """ return [parser() for parser in self.parser_classes] #列表生成式,返回解析器物件
self.praser_classes,預設(全域性)配置
class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES #解析器全域性配置 authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS metadata_class = api_settings.DEFAULT_METADATA_CLASS versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
當呼叫request.data獲取請求資料時候將使用解析器,下面是request.data原始碼:
@property def data(self): if not _hasattr(self, '_full_data'): self._load_data_and_files() #執行_load_data_and_files(),獲取請求體資料獲取檔案資料 return self._full_data
執行self._load_data_and_files(),獲取請求資料或者檔案資料,self._load_data_and_files()原始碼:
def _load_data_and_files(self): """ Parses the request content into `self.data`. """ if not _hasattr(self, '_data'): self._data, self._files = self._parse() #執行self_parse(),獲取解析器,並對content_type進行解析,選擇解析器,返回資料 if self._files: #判斷檔案流資料,存在則加入到self._full_data(也就是我們的request.data)中 self._full_data = self._data.copy() , self._full_data.update(self._files) else: self._full_data = self._data #不存在將無檔案流的解析完成的資料賦值到self._full_data(request.data) # if a form media type, copy data & files refs to the underlying # http request so that closable objects are handled appropriately. if is_form_media_type(self.content_type): self._request._post = self.POST self._request._files = self.FILES
執行self._prase()方法,獲取解析器,並對請求的Content-Type進行解析,選擇解析器,返回解析後的資料,以下是self._prase原始碼:
def _parse(self): """ Parse the request content, returning a two-tuple of (data, files) May raise an `UnsupportedMediaType`, or `ParseError` exception. """ media_type = self.content_type #獲取請求體中的Content-Type try: stream = self.stream #如果是檔案資料,則獲取檔案流資料 except RawPostDataException: if not hasattr(self._request, '_post'): raise # If request.POST has been accessed in middleware, and a method='POST' # request was made with 'multipart/form-data', then the request stream # will already have been exhausted. if self._supports_form_parsing(): return (self._request.POST, self._request.FILES) #處理檔案型別資料 stream = None if stream is None or media_type is None: if media_type and is_form_media_type(media_type): empty_data = QueryDict('', encoding=self._request._encoding) else: empty_data = {} empty_files = MultiValueDict() return (empty_data, empty_files) parser = self.negotiator.select_parser(self, self.parsers) #選擇解析器, if not parser: raise exceptions.UnsupportedMediaType(media_type) try: parsed = parser.parse(stream, media_type, self.parser_context) #執行解析器的parse方法(從這裡可以看出每個解析器都必須有該方法),對請求資料進行解析 except Exception: # If we get an exception during parsing, fill in empty data and # re-raise. Ensures we don't simply repeat the error when # attempting to render the browsable renderer response, or when # logging the request or similar. self._data = QueryDict('', encoding=self._request._encoding) self._files = MultiValueDict() self._full_data = self._data raise # Parser classes may return the raw data, or a # DataAndFiles object. Unpack the result as required. try: return (parsed.data, parsed.files) #返回解析結果,元祖,解析後的資料在parsed.data(在load_data_and_files中使用self._data和self._files進行接受), 檔案資料在parsed.files中 except AttributeError: empty_files = MultiValueDict() return (parsed, empty_files)
以上就是整個django rest framework 解析器原始碼,下面我們來看看示例中json解析器的原始碼,說明請看註解:
class JSONParser(BaseParser): """ Parses JSON-serialized data. """ media_type = 'application/json' #解析的Content-Type型別 renderer_class = renderers.JSONRenderer strict = api_settings.STRICT_JSON def parse(self, stream, media_type=None, parser_context=None): #在原始碼中解讀過,該方法用於解析請求體 """ Parses the incoming bytestream as JSON and returns the resulting data. """ parser_context = parser_context or {} encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) try: decoded_stream = codecs.getreader(encoding)(stream) parse_constant = json.strict_constant if self.strict else None return json.load(decoded_stream, parse_constant=parse_constant) #本質使用json類進行解析 except ValueError as exc: raise ParseError('JSON parse error - %s' % six.text_type(exc))
3.總結
(1).解析器的本質:
django rest framework解析本質是根據請求頭中的Content-Type來實現,不同的型別使用不同的解析器,一個檢視可有多個解析器。
(2).使用:
#全域性使用 REST_FRAMEWORK = { #解析器 "DEFAULT_PARSER_CLASSES":["rest_framework.parsers.JSONParser","rest_framework.parsers.FormParser"] } #單一檢視使用 parser_classes = [JSONParser,FormParser]