Django restframework解析器組件增加及源碼分析

在講述解析器之前,博主需要Django框架內部中(即沒有restframework)前端發出POST請求,後臺獲取的方式:request.body中一定有值,request.POST不一定有值。因爲request.POST有值,需要滿足兩個條件,我們看下源碼,既然沒有restframework,那麼request對象就是原生的request對象,我們可以使用type函數來獲取原生request對象類的位置:from django.core.handlers.wsgi import WSGIRequest。我們看下這個類的POST方法源碼及中文註釋

複製代碼

class WSGIRequest(HttpRequest):

    def _get_post(self):
        if not hasattr(self, '_post'):
            self._load_post_and_files()
        return self._post

    def _set_post(self, post):
        self._post = post

    # 這裏就是我們經常使用的request.POST來獲取參數值,這裏的request是原生的request
    POST = property(_get_post, _set_post)

複製代碼

之後我們可以點擊self._load_post_and_files(),就進入了原生request類中的_load_post_and_files方法,這樣就可以看到request,POST爲什麼沒有值,另外還可以發現request.body和request.POST是存在一定的關係的,博主添上原生request的部分代碼及中文註釋

複製代碼

    def _load_post_and_files(self):
        """Populate self._post and self._files if the content-type is a form type"""
        if self.method != 'POST':
            self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()
            return
        if self._read_started and not hasattr(self, '_body'):
            self._mark_post_parse_error()
            return

        # 這種請求頭代表上傳文件的
        if self.content_type == 'multipart/form-data':
            if hasattr(self, '_body'):
                # Use already read data
                data = BytesIO(self._body)
            else:
                data = self
            try:
                self._post, self._files = self.parse_file_upload(self.META, data)
            except MultiPartParserError:
                # An error occurred while parsing POST data. Since when
                # formatting the error the request handler might access
                # self.POST, set self._post and self._file to prevent
                # attempts to parse POST data again.
                # Mark that an error occurred. This allows self.__repr__ to
                # be explicit about it instead of simply representing an
                # empty POST
                self._mark_post_parse_error()
                raise
        # 這種請求頭會將request.body的數據按照一定格式轉化爲request.POST
        elif self.content_type == 'application/x-www-form-urlencoded':
            self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
        # 例如application/json這種請求頭,就會執行下面的語句
        else:
            self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()

複製代碼

所以request.POST有值的一個條件就是用戶的請求頭必須是application/x-www-form-urlencoded;還有一個條件是請求的參數格式必須是name=huxiansen&age=18類似這種的。

前端傳參數的格式一般分爲四種:前兩種格式request.POST是有值的,後兩種是沒有的

①form表單傳參

②ajax傳參,例如以下這種,默認的請求頭就是application/x-www-form-urlencoded,數據格式也會默認轉化name=huxiansen&age=18

$.ajax({
  url: ".....",
  type: "POST",
  data: {"name": "huxiansen", "age": 18}
})

③定製請求頭ajax傳參,例如以下這種,請求頭就是application/json,數據格式也會默認轉化name=huxiansen&age=18

複製代碼

$.ajax({
    url: ".....",
    type: "POST",
    headers: {"Content-Type": "application/json"},
    data: {"name": "huxiansen", "age": 18}
})

複製代碼

④定製請求參數格式ajax傳參,例如以下這種,請求頭就是application/json,數據格式{"name":"huxiansen", "age":"18"},後臺需要使用json.loads(request.body)

複製代碼

$.ajax({
    url: ".....",
    type: "POST",
    headers: {"Content-Type": "application/json"},
    data: JSON.stringfy({"name": "huxiansen", "age": 18})
})

複製代碼

爲簡化上面的獲取方式,restframework就提供解析器組件,獲取數據時調用data即可,如request.data,而且只有編寫了這一句代碼,解析器纔會起到真正的作用。接下來我們還是按照流程來查看解析器組件源代碼。

1、從用戶請求開始到APIView.dispatch()的這部分流程可以參考其他Django restframework組件源碼的介紹。這裏直接從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還是請求視圖類對象
        self.args = args
        self.kwargs = kwargs
        # 這裏是對原生的request加工處理,返回一個新的request對象
        # 封裝的內容:原生的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:
                # 通過python的反射機制反射到請求視圖類的方法名稱
                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

複製代碼

2、可以看到解析器的類是被封裝到新的Reqeust中,附上Request中有關於解析器組件的代碼

複製代碼

class APIView(View):

    # 如果請求視圖類中沒有這個變量和值,就會使用全局配置文件的值
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        # 封裝成新的request對象
        return Request(
            # 原生的request
            request,
            # 解析器類列表
            parsers=self.get_parsers(),
            # 點擊查看用戶認證的列表是什麼
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

    def get_parsers(self):
        """
        Instantiates and returns the list of parsers that this view can use.
        """
        # 返回解析器類對象列表
        return [parser() for parser in self.parser_classes]

複製代碼

3、看到上面這一部分的代碼就很容易看出在請求視圖類中或者在全局配置文件中需要如何配置了:parser_classes = [解析器類]或者REST_FRAMEWORK = {"DEFAULT_PARSER_CLASSES":["解析器類的全路徑"]}

4、解析類的封裝已經完成,其他的操作就是請求視圖類中使用request.data了,開頭也已提及原因了。附上請求視圖類的post方法及request.data代碼了

複製代碼

class GroupsView(APIView):

    def post(self, request):
        # 從這開始一追究竟式如何解析數據
        print(request.data)
        return Response(data={"code": 200})

複製代碼

5、這個request是新封裝的request,博主附上有關於request.data的源代碼及中文註釋

複製代碼

class Request(object):

    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            # 先執行下面的這個方法
            self._load_data_and_files()
        return self._full_data

    def _load_data_and_files(self):
        """
        Parses the request content into `self.data`.
        """
        if not _hasattr(self, '_data'):
            # 根據用戶的請求頭信息判斷使用哪一個解析器, 一般files的應該是文件上傳
            self._data, self._files = self._parse()
            if self._files:
                self._full_data = self._data.copy()
                self._full_data.update(self._files)
            else:
                self._full_data = self._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

    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
        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:
            # 根據匹配的解析器對象去執行parse方法
            parsed = parser.parse(stream, media_type, self.parser_context)
        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)
        except AttributeError:
            empty_files = MultiValueDict()
            return (parsed, empty_files)

複製代碼

6、裏面有一行代碼比較關鍵:parser = self.negotiator.select_parser(self, self.parsers),這一行代碼方法名稱可以知道是去匹配使用哪一個解析器類對象,參數也是解析器類對象列表,可以從繼承父類的子類中select_parser()方法可以看出,附上源代碼及中文註釋

複製代碼

class BaseContentNegotiation(object):
    def select_parser(self, request, parsers):
        raise NotImplementedError('.select_parser() must be implemented')

    def select_renderer(self, request, renderers, format_suffix=None):
        raise NotImplementedError('.select_renderer() must be implemented')


class DefaultContentNegotiation(BaseContentNegotiation):
    settings = api_settings

    def select_parser(self, request, parsers):
        """
        Given a list of parsers and a media type, return the appropriate
        parser to handle the incoming request.
        """
        # 循環解析器類對象列表的元素
        for parser in parsers:
            # 這部分肯定就是判斷解析器的請求頭信息是否相等於用戶的請求頭信息
            if media_type_matches(parser.media_type, request.content_type):
                return parser
        return None

複製代碼

7、第6步中已經是匹配到了一種解析器類對象,然後在第5步中根據得到的解析器類對象去執行下面的parse方法,一般前端傳來的參數和請求頭的方式的POST請求有兩種:一種是JSON傳參,一種是Form表單傳參。restframework也已經有了這兩種解析器,其中還有其他解析器(from rest_framework.parser import BaseParser)

複製代碼

class JSONParser(BaseParser):
    """
    Parses JSON-serialized data.
    """
    media_type = 'application/json'
    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)
        except ValueError as exc:
            raise ParseError('JSON parse error - %s' % six.text_type(exc))


class FormParser(BaseParser):
    """
    Parser for form data.
    """
    media_type = 'application/x-www-form-urlencoded'

    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parses the incoming bytestream as a URL encoded form,
        and returns the resulting QueryDict.
        """
        parser_context = parser_context or {}
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
        data = QueryDict(stream.read(), encoding=encoding)
        return data

複製代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章