Django(三):HttpReques

发布时间:2019-03-01 10:36:10编辑:auto阅读(2240)

      当一个请求连接进来时,django会创建一个HttpRequest对象来封装和保存所有请求相关的信息,并且会根据请求路由载入匹配的视图函数。每个请求的视图函数都会返回一个HttpResponse。

      HttpRequest和HttpResponse可以从django.http中导入。

      1、HttpRequest类

     函数  功能描述
    HttpRequest.scheme 请求协议(http或者https)
    HttpRequest.body 以字节的方式返回请求体内容;可以通过HttpRequest.POST获取处理后的key和value,也可以通过HttpRequest.read()格式化
    HttpRequest.path 返回请求的完整路径,不包括协议和域名
    HttpRequest.GET  GET请求参数,返回一个queryDict对象
    HttpRequest.POST 获取表单提交的数据,如果是通过POST请求提交的其它非表单数据,可以使用HttpRequest.Body获取;使用时可以通过if request.method == "PSOT"来进行预判断
    HttpRequest.method 返回请求方式
    HttpRequest.environ 返回一个字典,包含所有django运行的环境信息
    HttpRequest.content_type 文件格式
    HttpRequest.content_params 参数
    HttpRequest.COOKIES 返回一个字典,包含浏览器存储的所有cookie
    HttpRequest.FILES 返回一个MultiValueDict,包含上传的文件
    HttpRequest.META 返回一个包含所有请求相关信息的字典(包含Headers),同environ
    HttpRequest.resolver_match 返回请求处理的url及相关参数
    HttpRequest.session 中间件,设置session,一个可读可写的字典对象
    HttpRequest.get_host() 获取请求的主机和端口
    HttpRequest.get_port() 获取端口
    HttpRequest.get_full_path() 返回完整路径,同path
    HttpRequest.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None 获取以一个cookie
    HttpRequest.is_ajax() 判断是否为ajax请求
    HttpRequest.is_secure() 判断是否为https请求

      示例:

    class HttpRequest:
        """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
            self.content_type = None
            self.content_params = None
    
        def __repr__(self):
            if self.method is None or not self.get_full_path():
                return '<%s>' % self.__class__.__name__
            return '<%s: %s %r>' % (self.__class__.__name__, self.method, self.get_full_path())
    
        def _get_raw_host(self):
            """
            Return the HTTP host using the environment or request headers. Skip
            allowed hosts protection, so may return an insecure host.
            """
            # We try three options, in order of decreasing preference.
            if settings.USE_X_FORWARDED_HOST and (
                    'HTTP_X_FORWARDED_HOST' in self.META):
                host = self.META['HTTP_X_FORWARDED_HOST']
            elif 'HTTP_HOST' in self.META:
                host = self.META['HTTP_HOST']
            else:
                # Reconstruct the host using the algorithm from PEP 333.
                host = self.META['SERVER_NAME']
                server_port = self.get_port()
                if server_port != ('443' if self.is_secure() else '80'):
                    host = '%s:%s' % (host, server_port)
            return host
    
        def get_host(self):
            """Return the HTTP host using the environment or request headers."""
            host = self._get_raw_host()
    
            # Allow variants of localhost if ALLOWED_HOSTS is empty and DEBUG=True.
            allowed_hosts = settings.ALLOWED_HOSTS
            if settings.DEBUG and not allowed_hosts:
                allowed_hosts = ['localhost', '127.0.0.1', '[::1]']
    
            domain, port = split_domain_port(host)
            if domain and validate_host(domain, allowed_hosts):
                return host
            else:
                msg = "Invalid HTTP_HOST header: %r." % host
                if domain:
                    msg += " You may need to add %r to ALLOWED_HOSTS." % domain
                else:
                    msg += " The domain name provided is not valid according to RFC 1034/1035."
                raise DisallowedHost(msg)
    
        def get_port(self):
            """Return the port number for the request as a string."""
            if settings.USE_X_FORWARDED_PORT and 'HTTP_X_FORWARDED_PORT' in self.META:
                port = self.META['HTTP_X_FORWARDED_PORT']
            else:
                port = self.META['SERVER_PORT']
            return str(port)
    
        def get_full_path(self, force_append_slash=False):
            # RFC 3986 requires query string arguments to be in the ASCII range.
            # Rather than crash if this doesn't happen, we encode defensively.
            return '%s%s%s' % (
                escape_uri_path(self.path),
                '/' if force_append_slash and not self.path.endswith('/') else '',
                ('?' + iri_to_uri(self.META.get('QUERY_STRING', ''))) if self.META.get('QUERY_STRING', '') else ''
            )
    
        def get_signed_cookie(self, key, default=RAISE_ERROR, salt='', max_age=None):
            """
            Attempt to return a signed cookie. If the signature fails or the
            cookie has expired, raise an exception, unless the `default` argument
            is provided,  in which case return that value.
            """
            try:
                cookie_value = self.COOKIES[key]
            except KeyError:
                if default is not RAISE_ERROR:
                    return default
                else:
                    raise
            try:
                value = signing.get_cookie_signer(salt=key + salt).unsign(
                    cookie_value, max_age=max_age)
            except signing.BadSignature:
                if default is not RAISE_ERROR:
                    return default
                else:
                    raise
            return value
    
        def get_raw_uri(self):
            """
            Return an absolute URI from variables available in this request. Skip
            allowed hosts protection, so may return insecure URI.
            """
            return '{scheme}://{host}{path}'.format(
                scheme=self.scheme,
                host=self._get_raw_host(),
                path=self.get_full_path(),
            )
    
        def build_absolute_uri(self, location=None):
            """
            Build an absolute URI from the location and the variables available in
            this request. If no ``location`` is specified, bulid the absolute URI
            using request.get_full_path(). If the location is absolute, convert it
            to an RFC 3987 compliant URI and return it. If location is relative or
            is scheme-relative (i.e., ``//example.com/``), urljoin() it to a base
            URL constructed from the request variables.
            """
            if location is None:
                # Make it an absolute url (but schemeless and domainless) for the
                # edge case that the path starts with '//'.
                location = '//%s' % self.get_full_path()
            bits = urlsplit(location)
            if not (bits.scheme and bits.netloc):
                current_uri = '{scheme}://{host}{path}'.format(scheme=self.scheme,
                                                               host=self.get_host(),
                                                               path=self.path)
                # Join the constructed URL with the provided location, which will
                # allow the provided ``location`` to apply query strings to the
                # base path as well as override the host, if it begins with //
                location = urljoin(current_uri, location)
            return iri_to_uri(location)
    
        def _get_scheme(self):
            """
            Hook for subclasses like WSGIRequest to implement. Return 'http' by
            default.
            """
            return 'http'
    
        @property
        def scheme(self):
            if settings.SECURE_PROXY_SSL_HEADER:
                try:
                    header, value = settings.SECURE_PROXY_SSL_HEADER
                except ValueError:
                    raise ImproperlyConfigured(
                        'The SECURE_PROXY_SSL_HEADER setting must be a tuple containing two values.'
                    )
                if self.META.get(header) == value:
                    return 'https'
            return self._get_scheme()
    
        def is_secure(self):
            return self.scheme == 'https'
    
        def is_ajax(self):
            return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
    
        @property
        def encoding(self):
            return self._encoding
    
        @encoding.setter
        def encoding(self, val):
            """
            Set the encoding used for GET/POST accesses. If the GET or POST
            dictionary has already been created, remove and recreate it on the
            next access (so that it is decoded correctly).
            """
            self._encoding = val
            if hasattr(self, 'GET'):
                del self.GET
            if hasattr(self, '_post'):
                del self._post
    
        def _initialize_handlers(self):
            self._upload_handlers = [uploadhandler.load_handler(handler, self)
                                     for handler in settings.FILE_UPLOAD_HANDLERS]
    
        @property
        def upload_handlers(self):
            if not self._upload_handlers:
                # If there are no upload handlers defined, initialize them from settings.
                self._initialize_handlers()
            return self._upload_handlers
    
        @upload_handlers.setter
        def upload_handlers(self, upload_handlers):
            if hasattr(self, '_files'):
                raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
            self._upload_handlers = upload_handlers
    
        def parse_file_upload(self, META, post_data):
            """Return a tuple of (POST QueryDict, FILES MultiValueDict)."""
            self.upload_handlers = ImmutableList(
                self.upload_handlers,
                warning="You cannot alter upload handlers after the upload has been processed."
            )
            parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
            return parser.parse()
    
        @property
        def body(self):
            if not hasattr(self, '_body'):
                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.
                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:
                    raise UnreadablePostError(*e.args) from e
                self._stream = BytesIO(self._body)
            return self._body
    
        def _mark_post_parse_error(self):
            self._post = QueryDict()
            self._files = MultiValueDict()
            self._post_parse_error = True
    
        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()
    
        def close(self):
            if hasattr(self, '_files'):
                for f in chain.from_iterable(l[1] for l in self._files.lists()):
                    f.close()
    
        # File-like and iterator interface.
        #
        # Expects self._stream to be set to an appropriate source of bytes by
        # a corresponding request subclass (e.g. WSGIRequest).
        # Also when request data has already been read by request.POST or
        # request.body, self._stream points to a BytesIO instance
        # containing that data.
    
        def read(self, *args, **kwargs):
            self._read_started = True
            try:
                return self._stream.read(*args, **kwargs)
            except IOError as e:
                raise UnreadablePostError(*e.args) from e
    
        def readline(self, *args, **kwargs):
            self._read_started = True
            try:
                return self._stream.readline(*args, **kwargs)
            except IOError as e:
                raise UnreadablePostError(*e.args) from e
    
        def __iter__(self):
            while True:
                buf = self.readline()
                if not buf:
                    break
                yield buf
    
        def xreadlines(self):
            warnings.warn(
                'HttpRequest.xreadlines() is deprecated in favor of iterating the '
                'request.', RemovedInDjango30Warning, stacklevel=2,
            )
            yield from self
    
        def readlines(self):
            return list(self)
    HttpRequest源码
    from django.http import HttpResponse
    from django.views.decorators.csrf import csrf_exempt
    
    # views.py
    @csrf_exempt
    def page(request):
        # print(request.scheme)
        # print(request.GET)
        # print(request.POST)
        # print(request.method)
        # # print(request.encoding)
        # print(request.environ)
        # print(request.content_type)
        # print(request.content_params)
        # request.COOKIES["host"] = "whatever"
        # print(request.COOKIES)
        # print(request.FILES)
        # print(request.FILES.get("dog"))
        # print(request.META)
        # print(request.resolver_match)
        # request.session["username"] = "Jan"
        # print(request.session["username"])
        # print(request.get_signed_cookie("host"))
        # print(request.is_secure())
        # print(request.is_ajax())
    
        # print(request.POST.get("age", None))
        # print(request.get_port())
        # print(request.get_full_path())
        # print(request.path)
        # print(request.path_info)
        # print(request.get_host())
        # print(request.GET.get("name", None))
        return HttpResponse("OK!")

      使用httpie测试:

    # GET请求带参数
    http http://127.0.0.1:8000/page/ -- name=Jan
    # PSOT请求
    http -f POST 127.0.0.1:8000/page/ age=20
    # POST上传文件
    http -f POST http://127.0.0.1:8000/page/  dog@Desktop/dog.png

      2、queryDict对象

      queryDict类是python字典的子类,具有字典的所有方法,它放在django.http.QueryDict中。它用“&”分割字符传,用“=”生成键值对,从而将一个类似GET请求参数的字符串解析成一个类似字典的对象。源码如下:

    class QueryDict(MultiValueDict):
        """
        A specialized MultiValueDict which represents a query string.
    
        A QueryDict can be used to represent GET or POST data. It subclasses
        MultiValueDict since keys in such data can be repeated, for instance
        in the data from a form with a <select multiple> field.
    
        By default QueryDicts are immutable, though the copy() method
        will always return a mutable copy.
    
        Both keys and values set on this class are converted from the given encoding
        (DEFAULT_CHARSET by default) to str.
        """
    
        # These are both reset in __init__, but is specified here at the class
        # level so that unpickling will have valid values
        _mutable = True
        _encoding = None
    
        def __init__(self, query_string=None, mutable=False, encoding=None):
            super().__init__()
            if not encoding:
                encoding = settings.DEFAULT_CHARSET
            self.encoding = encoding
            query_string = query_string or ''
            parse_qsl_kwargs = {
                'keep_blank_values': True,
                'fields_limit': settings.DATA_UPLOAD_MAX_NUMBER_FIELDS,
                'encoding': encoding,
            }
            if isinstance(query_string, bytes):
                # query_string normally contains URL-encoded data, a subset of ASCII.
                try:
                    query_string = query_string.decode(encoding)
                except UnicodeDecodeError:
                    # ... but some user agents are misbehaving :-(
                    query_string = query_string.decode('iso-8859-1')
            for key, value in limited_parse_qsl(query_string, **parse_qsl_kwargs):
                self.appendlist(key, value)
            self._mutable = mutable
    
        @classmethod
        def fromkeys(cls, iterable, value='', mutable=False, encoding=None):
            """
            Return a new QueryDict with keys (may be repeated) from an iterable and
            values from value.
            """
            q = cls('', mutable=True, encoding=encoding)
            for key in iterable:
                q.appendlist(key, value)
            if not mutable:
                q._mutable = False
            return q
    
        @property
        def encoding(self):
            if self._encoding is None:
                self._encoding = settings.DEFAULT_CHARSET
            return self._encoding
    
        @encoding.setter
        def encoding(self, value):
            self._encoding = value
    
        def _assert_mutable(self):
            if not self._mutable:
                raise AttributeError("This QueryDict instance is immutable")
    
        def __setitem__(self, key, value):
            self._assert_mutable()
            key = bytes_to_text(key, self.encoding)
            value = bytes_to_text(value, self.encoding)
            super().__setitem__(key, value)
    
        def __delitem__(self, key):
            self._assert_mutable()
            super().__delitem__(key)
    
        def __copy__(self):
            result = self.__class__('', mutable=True, encoding=self.encoding)
            for key, value in self.lists():
                result.setlist(key, value)
            return result
    
        def __deepcopy__(self, memo):
            result = self.__class__('', mutable=True, encoding=self.encoding)
            memo[id(self)] = result
            for key, value in self.lists():
                result.setlist(copy.deepcopy(key, memo), copy.deepcopy(value, memo))
            return result
    
        def setlist(self, key, list_):
            self._assert_mutable()
            key = bytes_to_text(key, self.encoding)
            list_ = [bytes_to_text(elt, self.encoding) for elt in list_]
            super().setlist(key, list_)
    
        def setlistdefault(self, key, default_list=None):
            self._assert_mutable()
            return super().setlistdefault(key, default_list)
    
        def appendlist(self, key, value):
            self._assert_mutable()
            key = bytes_to_text(key, self.encoding)
            value = bytes_to_text(value, self.encoding)
            super().appendlist(key, value)
    
        def pop(self, key, *args):
            self._assert_mutable()
            return super().pop(key, *args)
    
        def popitem(self):
            self._assert_mutable()
            return super().popitem()
    
        def clear(self):
            self._assert_mutable()
            super().clear()
    
        def setdefault(self, key, default=None):
            self._assert_mutable()
            key = bytes_to_text(key, self.encoding)
            default = bytes_to_text(default, self.encoding)
            return super().setdefault(key, default)
    
        def copy(self):
            """Return a mutable copy of this object."""
            return self.__deepcopy__({})
    
        def urlencode(self, safe=None):
            """
            Return an encoded string of all query string arguments.
    
            `safe` specifies characters which don't require quoting, for example::
    
                >>> q = QueryDict(mutable=True)
                >>> q['next'] = '/a&b/'
                >>> q.urlencode()
                'next=%2Fa%26b%2F'
                >>> q.urlencode(safe='/')
                'next=/a%26b/'
            """
            output = []
            if safe:
                safe = force_bytes(safe, self.encoding)
    
                def encode(k, v):
                    return '%s=%s' % ((quote(k, safe), quote(v, safe)))
            else:
                def encode(k, v):
                    return urlencode({k: v})
            for k, list_ in self.lists():
                k = force_bytes(k, self.encoding)
                output.extend(encode(k, force_bytes(v, self.encoding))
                              for v in list_)
            return '&'.join(output)

      示例:

    from django.http import QueryDict
    
    # 添加django的环境配置
    import os, django
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "restful.settings")
    django.setup()
    
    get_vactors = "a=1&a=2&a=3&b=Jan&b=Li&c=20"
    query = QueryDict(get_vactors, mutable=True)
    # print(query)
    
    # 根据键生成新的QueryDict
    query_new = QueryDict.fromkeys(query, value="这个大傻子")
    # print(query_new)
    
    # 获取键列表、值列表、键值对列表(值列表中的最后一个元素)
    print(list(query.items()))    # 生成器,获取包含键和最后一个值元素的元组的列表
    print(list(query.keys()))     # dict_keys,获取键
    print(list(query.values()))   # 生成器,获取每个键的最后一个元素值
    # 键值对
    print(list(query.lists()))    # 获取键键值对列表
    print(dict(query))            # 转成字典,相当于query.dict()
    
    # 获取单个元素及列表
    alist = query.get("a")
    print(alist)  # 获取最后一个元素值
    alist = query.getlist("a")
    print(alist)  # 获取键对应的value,返回元素列表
    
    # 添加/修改键值对,必须将mutable设置为True
    query.setlist("a", [4, 5, 6])
    query.setlistdefault("d", [4,5,6])
    query.appendlist("e", ["this", "is", "append"])
    print(query)
    
    # 删除键值对
    query.pop("e")   # 删除指定键值对
    print(query)
    query.popitem()  # 删除最后一个键值对
    print(query)

      3、HttpResponse

      HttpResponse类用于设置响应头和响应内容,同样封装在django.http模块中。它分为实例化对象、填充设置以及返回三个部分。它同时也是其它请求响应类的父类。

    from django.http import HttpResponse
    
    # 添加django的环境配置
    import os, django
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "restful.settings")
    django.setup()
    
    
    # 实例化一:
    responseOne = HttpResponse("this is a http response")
    # 实例化二:
    responseTwo = HttpResponse()
    responseTwo.write("this is another http response")
    responseTwo.writelines(["this is second line", "this is third line"])
    # 设置响应头
    responseOne["Age"] = 20
    responseOne["app"] = "sample"
    del responseOne["app"]
    
    # 设置响应头
    responseOne["content_type"] = 'application/vnd.ms-excel'
    responseOne['Content-Disposition'] = 'attachment; filename="foo.xls"'
    
    
    responseOne.set_cookie("date", "2018-08-21", path="/page", )   # 设置cookie
    responseOne.delete_cookie("date")                              # 删除cookie
    # 有关对象
    print(responseOne)                  # HttpResponse
    print(responseOne.items())          # dict_values
    print(responseOne.cookies)          # cookie
    print(responseOne.content)          # 内容(字节)
    print(responseOne.charset)          # 编码
    print(responseOne.status_code)      # 状态码
    print(responseOne.streaming)        # 是否为流
    print(responseOne.closed)           # 是否已发送response
    print(responseOne.serialize())      # 序列化响应头和相应内容
    print(responseOne.serialize_headers())  # 序列化响应头
    print(responseOne.get("Age"))       # 获取响应头中的某个键值对
    print(responseTwo.getvalue())       # 获取相应的内容
    
    # 将response设置为流数据处理
    responseTwo.readable()
    responseTwo.seekable()
    responseTwo.write("...")

      其它继承HttpResponse的子类包括:

    HttpResponseRedirect                          # 重定向
    HttpResponsePermanentRedirect                 # 永久重定向
    HttpResponseNotModified                       # 304
    HttpResponseBadRequest                        # 400
    HttpResponseNotFound                          # 404
    HttpResponseForbidden                         # 403
    HttpResponseNotAllowed                        # 405
    HttpResponseGone                              # 410
    HttpResponseServerError                       # 500

      4、JsonResponse

      返回一个序列化的JSON对象。对于列表、字符串等,它会自动生成索引-元素JSON;对于字典,会直接生成相应的JSON。

    class JsonResponse(HttpResponse):
        """
        An HTTP response class that consumes data to be serialized to JSON.
    
        :param data: Data to be dumped into json. By default only ``dict`` objects
          are allowed to be passed due to a security flaw before EcmaScript 5. See
          the ``safe`` parameter for more information.
        :param encoder: Should be a json encoder class. Defaults to
          ``django.core.serializers.json.DjangoJSONEncoder``.
        :param safe: Controls if only ``dict`` objects may be serialized. Defaults
          to ``True``.
        :param json_dumps_params: A dictionary of kwargs passed to json.dumps().
        """
    
        def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
                     json_dumps_params=None, **kwargs):
            if safe and not isinstance(data, dict):
                raise TypeError(
                    'In order to allow non-dict objects to be serialized set the '
                    'safe parameter to False.'
                )
            if json_dumps_params is None:
                json_dumps_params = {}
            kwargs.setdefault('content_type', 'application/json')
            data = json.dumps(data, cls=encoder, **json_dumps_params)
            super().__init__(content=data, **kwargs)

      DjangoJSONEncoder是内置的JSON编码方式,也支持传入自己写的编码方式对数据进行序列化。

    # python manage.py shell
    from django.http import JsonResponse
    response = JsonResponse({'foo': 'bar'})
    response.content
    from django.http import  JsonResponse
    from django.views.decorators.csrf import csrf_exempt
    
    @csrf_exempt
    def page(request):
        # lis = list(range(10))
        # return JsonResponse(lis, safe=False)
        # dic = {"name": "Jan", "age": 20}
        # return JsonResponse(dic)
        string = "this is json response"
        return JsonResponse(string, safe=False)

      5、FileResponse

    from django.http import FileResponse
    response = FileResponse(open('myfile.png', 'rb'))

     

     

      

关键字