Django中Request类详解
Django主要利用可调用对象WSGIHandler来处理请求响应。
WSGIHandler会根据获得的environ构造WSGIRequest请求类,这里首先说一下environ。 environ是非常重要的变量,它包括所有的用户信息,即它负责接收所有的用户请求参数。environ包含 CGI 环境变量,环境变量在 CGI环境变量 有介绍。下面这些变量是必须存在的:
1、REQUEST_METHOD
HTTP 请求方法,例如 "GET", "POST"
2、SCRIPT_NAME
URL 路径的起始部分对应的应用程序对象,如果应用程序对象对应服务器的根,那么这个值可以为空字符串
3、PATH_INFO
URL 路径除了起始部分后的剩余部分,用于找到相应的应用程序对象,如果请求的路径就是根路径,这个值为空字符串
4、QUERY_STRING
URL路径中 ? 后面的部分
5、CONTENT_TYPE HTTP 请求中的 Content-Type 部分
6、CONTENT_LENGTH
HTTP 请求中的Content-Lengh 部分
7、SERVER_NAME, SERVER_PORT
与 SCRIPT_NAME,PATH_INFO 共同构成完整的 URL,它们永远不会为空。但是,如果 HTTP_HOST 存在的话,当构建 URL 时, HTTP_HOST优先于SERVER_NAME。
8、SERVER_PROTOCOL
客户端使用的协议,例如 "HTTP/1.0", "HTTP/1.1", 它决定了如何处理 HTTP 请求的头部。这个名字其实应该叫REQUEST_PROTOCOL,因为它表示的是客户端请求的协议,而不是服务端响应的协议。但是为了和CGI兼容,我们只好叫这个名字了。 *HTTP_ Variables
这个是一个系列的变量名,都以HTTP开头,对应客户端支持的HTTP请求的头部信息。
除了这些,WSGI还包含自己特有的一些变量: 但是,下面列出的这些 WSGI 相关的变量必须要包含:
1、wsgi.version
值的形式为 (1, 0) 表示 WSGI 版本 1.0
2、wsgi.url_scheme
表示 url 的模式,例如 "https" 还是 "http"
3、wsgi.input
输入流,HTTP请求的 body 部分可以从这里读取
4、wsgi.erros
输出流,如果出现错误,可以写往这里
5、wsgi.multithread
如果应用程序对象可以被同一进程中的另一线程同时调用,这个值为True
6、wsgi.multiprocess
如果应用程序对象可以同时被另一个进程调用,这个值为True
7、wsgi.run_once
如果服务器希望应用程序对象在包含它的进程中只被调用一次,那么这个值为True
至此environ变量就介绍完了。 上面说到WSGIHandler会根据environ生成Django可处理的请求类。该请求类的基类地HttpRequest(djang.http.request)。部分源码:
class HttpRequest(object):
"""A basic HTTP request."""
# The encoding used in GET/POST dicts. None means use default setting.
_encoding = None
_upload_handlers = []
def __init__(self):
# WARNING: The `WSGIRequest` subclass doesn't call `super`.
# Any variable assignment made here should also happen in
# `WSGIRequest.__init__()`.
self.GET = QueryDict(mutable=True)
self.POST = QueryDict(mutable=True)
self.COOKIES = {}
self.META = {}
self.FILES = MultiValueDict()
self.path = ''
self.path_info = ''
self.method = None
self.resolver_match = None
self._post_parse_error = False
该请求类主要有上面的几个属性,GET,POST,COOKIES,META,FILES,path,path_info,method等等。
1、GET属性存储了通过环境变量中的 "QUERY_STRING"获得的数据,是一个字典;
@cached_property
def GET(self):
# The WSGI spec says 'QUERY_STRING' may be absent.
raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '')
return http.QueryDict(raw_query_string, encoding=self._encoding)
2、POST属性存储通过POST请求获得的,POST请求参数存储在HTML的body中。这里注意,上传文件只能通过POST请求方式。
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.META.get('CONTENT_TYPE', '').startswith('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.META.get('CONTENT_TYPE', '').startswith('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()
3、COOKIES COOKIE 信息是通过环境变量的HTTP_COOKIES获得的。
@cached_property
def COOKIES(self):
raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
return http.parse_cookie(raw_cookie)
4、META
该属性包括了所有的请求头信息。
self.META = environ
下面的图展示了Request的详细信息实例:
再拿出WSGIRequest类的源码:
class WSGIRequest(http.HttpRequest):
def __init__(self, environ):
script_name = get_script_name(environ)
path_info = get_path_info(environ)
if not path_info:
# Sometimes PATH_INFO exists, but is empty (e.g. accessing
# the SCRIPT_NAME URL without a trailing slash). We really need to
# operate as if they'd requested '/'. Not amazingly nice to force
# the path like this, but should be harmless.
path_info = '/'
self.environ = environ
self.path_info = path_info
# be careful to only replace the first slash in the path because of
# http://test/something and http://test//something being different as
# stated in http://www.ietf.org/rfc/rfc2396.txt
self.path = '%s/%s' % (script_name.rstrip('/'),
path_info.replace('/', '', 1))
self.META = environ
self.META['PATH_INFO'] = path_info
self.META['SCRIPT_NAME'] = script_name
self.method = environ['REQUEST_METHOD'].upper()
_, content_params = cgi.parse_header(environ.get('CONTENT_TYPE', ''))
if 'charset' in content_params:
try:
codecs.lookup(content_params['charset'])
except LookupError:
pass
else:
self.encoding = content_params['charset']
self._post_parse_error = False
try:
content_length = int(environ.get('CONTENT_LENGTH'))
except (ValueError, TypeError):
content_length = 0
self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
self._read_started = False
self.resolver_match = None
def _get_scheme(self):
return self.environ.get('wsgi.url_scheme')
@cached_property
def GET(self):
# The WSGI spec says 'QUERY_STRING' may be absent.
raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '')
return http.QueryDict(raw_query_string, encoding=self._encoding)
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
@cached_property
def COOKIES(self):
raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
return http.parse_cookie(raw_cookie)
def _get_files(self):
if not hasattr(self, '_files'):
self._load_post_and_files()
return self._files
POST = property(_get_post, _set_post)
FILES = property(_get_files)
微信分享/微信扫码阅读