=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中;
- 测试多一点,问题少一点;
- 监控多一点,睡得安心点;
- 对核心接口一定要做好能力容量预估、管理;
- 不怕出错,就怕出错了还不知道原因在哪;
参考链接:
- Django的request.POST获取不到内容的原因
https://blog.csdn.net/liuxingen/article/details/54176205 - Django 的 request.body 解码
https://stackoverflow.com/questions/29780060/trying-to-parse-request-body-from-post-in-django
https://stackoverflow.com/questions/29514077/get-request-body-as-string-in-django
https://stackoverflow.com/questions/24771250/python-import-json-json-loadsrequest-body-2-7-3-4 - json.loads() 解码失败 UnicodeDecodeError
=END=
《 “Django中的request.POST和request.body” 》 有 2 条评论
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”.
`
本文解释文件上传的”multipart/form-data”模式是怎么回事。
https://blog.adamchalmers.com/multipart/
`
Why use multipart?
In Rust
What is multipart?
How is multipart implemented?
Compression
Why is this interesting?
`
https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types