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)
--------EOF---------
微信分享/微信扫码阅读