在講述解析器之前,博主需要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