Django源碼分析 request.POST 與 request.body 區別


request.POST

request實際上是django/core/handlers/wsgi.py::WSGIRequest的實例,而WSGIRequestHttpRequest的子類

class WSGIRequest(http.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

    POST = property(_get_post, _set_post)        

獲取request.POST的時候實際上是調用了WSGIRequest._get_post()方法

現在來看一下_load_post_and_files()方法

    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:
                self._mark_post_parse_error()
                raise
        elif self.content_type == 'application/x-www-form-urlencoded':
            self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
        else:
            self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()

正如方法的說明所述,這個方法只會處理content-type為表單類型的請求數據

因此當Content-Type=application/json時,我們取request.POST只會得到一個空的QueryDict

request.body

class HttpRequest(object):
    @property
    def body(self):
        if not hasattr(self, '_body'):
            # 調用read()之后會將此參數置為True
            if self._read_started:
                raise RawPostDataException("You cannot access body after reading from request's data stream")

            # Limit the maximum request data size that will be handled in-memory.
            # 通過請求頭的CONTENT_LENGTH參數得到請求的數據大小,單位為字節(byte)。但有時候獲取不到,見
            # https://www.cnblogs.com/nxlhero/p/11670942.html
            if (settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None and
                    int(self.META.get('CONTENT_LENGTH') or 0) > settings.DATA_UPLOAD_MAX_MEMORY_SIZE):
                raise RequestDataTooBig('Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.')

            try:
                self._body = self.read()
            except IOError as e:
                six.reraise(UnreadablePostError, UnreadablePostError(*e.args), sys.exc_info()[2])
            # 由於request.POST和request.body都會去讀self._stream,因此需要在用完之后復原
            self._stream = BytesIO(self._body)
        return self._body
    
    def read(self, *args, **kwargs):
        self._read_started = True
        try:
            # _stream將在WSGIRequest中被初始化為LimitedStream的實例
            # self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
            # 即直接從WSGI server獲取數據
            return self._stream.read(*args, **kwargs)
        except IOError as e:
            six.reraise(UnreadablePostError, UnreadablePostError(*e.args), sys.exc_info()[2])

因此,request.body取出來是字節碼,需要將其編碼為字符串

Content-Type=application/json時,還需要用json.loads處理得到的json字符串

request_params = json.loads(request.body.decode("utf-8"))

統一獲取請求數據

if request.method.lower() == "post":
    # 當 Content-Type 為表單類型時,這里就可以直接獲取到數據了
    request_params = request.POST
    if not request_params:
        try:
            # Content-Type=application/json 的情況
            request_params = json.loads(request.body.decode("utf-8"))
        except Exception:
            # 聲明了 Content-Type=application/json,卻不傳遞json字符串
            raise ApiException


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM