1. 程式人生 > >原始碼分析 -解析器元件

原始碼分析 -解析器元件

一.基本使用

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]