Django中的request.POST和request.body


=Start=

缘由:

之前其实没想过Django中的request.POST和request.body有什么不同,最近踩了些坑,才知道,原来两者还是有区别的,一不小心就容易出错。

所以在此记录一下,方便以后参考。

正文:

参考解答:

下面是class HttpRequest(object)中获取POST QueryDict的函数部分:

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
    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()

这里主要关注后面的 if elif else这三个分支即可,从 elif self.content_type == 'application/x-www-form-urlencoded':这个分支能看到只有请求header中的 'Content-Type':'application/x-www-form-urlencoded'才会填充request.POST,其它情况下只有一个空的 <QueryDict: {}>

测试、验证如下:

from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
import json
import traceback

@csrf_exempt
def print_post_info(request):
    if request.method != 'POST':
        return HttpResponseBadRequest('unsupport method')
    print 'request.content_type = {0}'.format(request.META.get('CONTENT_TYPE', ''))

    try:
        if request.META.get('CONTENT_TYPE', '') == 'application/x-www-form-urlencoded':
            received_json_data = request.POST
        else:
            received_json_data = json.loads(request.body)

        print "request.body::{0}====".format(request.body)
        print "request.POST::{0}====".format(request.POST)
        print "received_json_data::{0}====".format(received_json_data)
        return HttpResponse('\nrequest.body: "{0}"\nrequest.POST: "{1}"'.format(request.body, request.POST))
    except Exception as e:
        return HttpResponseBadRequest(traceback.format_exc())

urls.py的配置很简单,在这里暂且略去。下面是用curl发测试请求的命令及其返回:

# 默认情况下,是以Content-Type为 'application/x-www-form-urlencoded' 发出的POST请求
$ curl -X POST http://127.0.0.1:8080/post -d 'short_name=unknown&long_name=Administrator'

request.body: "short_name=unknown&long_name=Administrator"
request.POST: "<QueryDict: {u'long_name': [u'Administrator'], u'short_name': [u'unknown']}>"

# 主动将Content-Type修改为 'application/json' 来发出错误的POST请求
$ curl -X POST http://127.0.0.1:8080/post -H 'Content-Type: application/json' -d 'short_name=unknown&long_name=Administrator'
Traceback (most recent call last):
  File "/Users/ixyzero/Desktop/python_web/do_test_func/download_file/views.py", line 35, in print_post_info
    received_json_data = json.loads(request.body)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 338, in loads
    return _default_decoder.decode(s)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 366, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 384, in raw_decode
    raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded

# 主动将Content-Type修改为 'application/json' 来发出正确的POST请求
$ curl -X POST http://127.0.0.1:8080/post -H 'Content-Type: application/json' -d '{"short_name": "unknown", "long_name": "Administrator"}'

request.body: "{"short_name": "unknown", "long_name": "Administrator"}"
request.POST: "<QueryDict: {}>"
$

对应的Server端收到的Content-Type分别是:

request.META.get('CONTENT_TYPE') == 'application/x-www-form-urlencoded'

request.META.get('CONTENT_TYPE') == 'application/json'

request.META.get('CONTENT_TYPE') == 'application/json'

几个心得、注意事项:

  • 能用 .get('key_name') 方式的就不要用 ['key_name'] 的方式;
  • 将任何可能出现Exception的地方都放在合适的try..catch中;
  • 测试多一点,问题少一点;
  • 监控多一点,睡得安心点;
  • 对核心接口一定要做好能力容量预估、管理;
  • 不怕出错,就怕出错了还不知道原因在哪;

 

参考链接:

=END=


《“Django中的request.POST和request.body”》 有 2 条评论

  1. Django的request对象里包含的header信息
    https://docs.djangoproject.com/en/2.0/ref/request-response/#django.http.HttpRequest.META
    `
    # 一个获取请求来源IP的方式
    if request.META.has_key(‘HTTP_X_FORWARDED_FOR’):
    ip = request.META[‘HTTP_X_FORWARDED_FOR’]
    elif request.META.has_key(‘REMOTE_ADDR’):
    ip = request.META[‘REMOTE_ADDR’]
    else:
    ip = ”

    CONTENT_LENGTH – The length of the request body (as a string).
    CONTENT_TYPE – The MIME type of the request body.
    HTTP_ACCEPT – Acceptable content types for the response.
    HTTP_ACCEPT_ENCODING – Acceptable encodings for the response.
    HTTP_ACCEPT_LANGUAGE – Acceptable languages for the response.
    HTTP_HOST – The HTTP Host header sent by the client.
    HTTP_REFERER – The referring page, if any.
    HTTP_USER_AGENT – The client’s user-agent string.
    QUERY_STRING – The query string, as a single (unparsed) string.
    REMOTE_ADDR – The IP address of the client.
    REMOTE_HOST – The hostname of the client.
    REMOTE_USER – The user authenticated by the Web server, if any.
    REQUEST_METHOD – A string such as “GET” or “POST”.
    `

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注