Django的cookies和headers
在Django(以及一般情况下),cookie也是一种头信息,就像比如说 User-Agent
一样吗?
也就是说,在Django中这两种方法是一样的吗?
使用 set_cookie
:
response.set_cookie('food', 'bread')
response.set_cookie('drink', 'water')
使用设置头信息的方法:
response['Cookie'] = ('food=bread; drink=water')
# I'm not sure whether 'Cookie' should be capitalized or not
另外,如果我们可以用第二种方法设置cookie,那我们怎么在字符串中包含额外的信息,
比如 path
、max_age
等等?我们是不是只需要用某个特殊的字符来分隔它们就可以了?
3 个回答
这是HttpResponse
类的一段代码:
class HttpResponse(object):
#...
def __init__(self, content='', mimetype=None, status=None,
#...
self.cookies = SimpleCookie()
#...
def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
domain=None, secure=False, httponly=False):
self.cookies[key] = value
#...
也就是说,每当调用response.set_cookie()
时,它要么在response.cookies[key]
位置放一个新的cookie值,要么如果这个位置已经有值了,就更新这个值。
这就解释了为什么会设置多个Set-Cookie
头部。
我在想,我们怎么才能用response['Set-Cookie']
来做到同样的事情呢?
当然可以,不过要把“Cookie”改成“Set-Cookie”,并加上“Path=/”这样就能让它在整个网站上都有效。
response["Set-Cookie"] = "food=bread; drink=water; Path=/"
补充:
我自己试了一下,发现一个有趣的情况,set_cookie
不会把相似的cookie(比如路径、过期时间、域名等相同的)放在同一个头部里。它只是会在响应中再加一个“Set-Cookie”。这样做是可以理解的,因为检查和处理字符串可能会比在HTTP头部多几个字节花费更多时间(而且这也只是微小的优化而已)。
response.set_cookie("food", "kabanosy")
response.set_cookie("drink", "ardbeg")
response.set_cookie("state", "awesome")
# result in these headers
# Set-Cookie: food=kabonosy; Path=/
# Set-Cookie: drink=ardbeg; Path=/
# Set-Cookie: state=awesome; Path=/
# not this
# Set-Cookie:food=kabanosy; drink=ardbeg; state=awesome; Path=/
如果你使用 set_cookie
,事情会简单很多。不过,没错,你也可以通过设置响应头来设置cookie:
response['Set-Cookie'] = ('food=bread; drink=water; Path=/; max_age=10')
但是,要注意的是,在 response
对象中重新设置 Set-Cookie
会清除掉之前的cookie,所以在Django中你不能有多个 Set-Cookie
头。我们来看看原因。
在 response.py 中观察一下 set_cookie
方法:
class HttpResponseBase:
def __init__(self, content_type=None, status=None, mimetype=None):
# _headers is a mapping of the lower-case name to the original case of
# the header (required for working with legacy systems) and the header
# value. Both the name of the header and its value are ASCII strings.
self._headers = {}
self._charset = settings.DEFAULT_CHARSET
self._closable_objects = []
# This parameter is set by the handler. It's necessary to preserve the
# historical behavior of request_finished.
self._handler_class = None
if mimetype:
warnings.warn("Using mimetype keyword argument is deprecated, use"
" content_type instead",
DeprecationWarning, stacklevel=2)
content_type = mimetype
if not content_type:
content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
self._charset)
self.cookies = SimpleCookie()
if status:
self.status_code = status
self['Content-Type'] = content_type
...
def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
domain=None, secure=False, httponly=False):
"""
Sets a cookie.
``expires`` can be:
- a string in the correct format,
- a naive ``datetime.datetime`` object in UTC,
- an aware ``datetime.datetime`` object in any time zone.
If it is a ``datetime.datetime`` object then ``max_age`` will be calculated.
"""
self.cookies[key] = value
if expires is not None:
if isinstance(expires, datetime.datetime):
if timezone.is_aware(expires):
expires = timezone.make_naive(expires, timezone.utc)
delta = expires - expires.utcnow()
# Add one second so the date matches exactly (a fraction of
# time gets lost between converting to a timedelta and
# then the date string).
delta = delta + datetime.timedelta(seconds=1)
# Just set max_age - the max_age logic will set expires.
expires = None
max_age = max(0, delta.days * 86400 + delta.seconds)
else:
self.cookies[key]['expires'] = expires
if max_age is not None:
self.cookies[key]['max-age'] = max_age
# IE requires expires, so set it if hasn't been already.
if not expires:
self.cookies[key]['expires'] = cookie_date(time.time() +
max_age)
if path is not None:
self.cookies[key]['path'] = path
if domain is not None:
self.cookies[key]['domain'] = domain
if secure:
self.cookies[key]['secure'] = True
if httponly:
self.cookies[key]['httponly'] = True
这里有两点值得注意:
set_cookie
方法会帮你处理expires
中的datetime
,如果你自己设置的话,就得自己处理了。self.cookie
是一个字典的字典。所以每个key
会在头部添加一个["Set-Cookie"]
,稍后你会看到。
然后,HttpResponse
中的 cookies
对象会被传递给 WSGIHandler
,并被添加到响应头中:
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
上面的代码也是为什么只有 set_cookie()
允许在响应头中有多个 Set-Cookie
,而直接将cookie设置到 Response
对象中只会返回一个 Set-Cookie
的原因。