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
posted @ 2020-10-20 16:28  luozx207  阅读(1465)  评论(0编辑  收藏  举报