29 操作 Cookie 和 Session
上一節介紹了 Cookie 和 Session 的相關概念,本節就要在 Django 中操作 Cookie 和 Session,同時我也會繼續帶領大家追蹤相關的程式碼,這樣可以更好的理解相關操作。
1. Django 中操作 Cookie
操作 Cookie 同樣是考察4個基本動作:增刪改查。現在分別從這4個角度看 Django 如何操作 Cookie :
增:對於檢視函式或者檢視類的三種返回 Response 響應 (HttpResponse、render、redircet),之前的做法是直接 return,現在可以在 return 之前,使用 set_cookie()
或者 set_signed_cookied()
def xxxx(request, *args, **kwargs):
# ...
rep = HttpResponse(...)
# 或者
rep = render(request, ...)
# 或者
rep = redirect( ...)
# 兩種設定cookie的方法,一種不加salt,另一個加salt
rep.set_cookie(key, value,...)
rep.set_signed_cookie( key, value, salt='加密鹽', max_age=None, ...)
return rep
查:查詢 cookie 是在傳送過來的 HTTP 請求中的,因此對應的查詢 Cookie 方法封裝在 HttpRequest 類中,對應的操作語句如下:
request.COOKIES['key']
request.COOKIES.get['key']
# 對應前面使用前面加密的cookie
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
改:呼叫前面的 set_cookie()
set_signed_cookie()
方法修改 Cookie 即可;
刪:直接使用 HttpReponse 類的 delete_cookie()
刪除 cookie 中對應 key 值。
案例1:Django 中 Cookie 實操。我們在前面的登入表單功能上改造檢視函式,保證一次登入後,後續再次 GET 請求時能自動識別登入使用者。此外還設定一個 Cookie 過期時間,過期之後再次 GET 請求時又回到登入頁面。
調整登入表單的檢視類:
class TestFormView2(TemplateView):
template_name = 'test_form2.html'
def get(self, request, *args, **kwargs):
success = False
form = LoginForm()
print("[{}] cookies:{}".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), request.COOKIES))
if request.get_signed_cookie('user', default='anonymous', salt=default_salt) == 'spyinx':
success = True
return self.render_to_response(context={'success': success, 'form': form})
def post(self, request, *args, **kwargs):
form = LoginForm(request.POST)
success = True
err_msg = ""
rep = self.render_to_response(context={'success': success, 'err_msg': err_msg, 'form': form})
if form.is_valid():
login_data = form.clean()
name = login_data['name']
password = login_data['password']
if name != 'spyinx' or password != 'SPYinx123456':
success = False
err_msg = "使用者名稱密碼不正確"
else:
print('設定cookie')
rep.set_signed_cookie('user', 'spyinx', salt=default_salt, max_age=10)
else:
success = False
err_msg = form.errors['password'][0]
return rep
可以看到,在 get()
方法中我們通過 get_signed_cookie()
方法獲取 cookie 中的 user
資訊,判斷是否為 spyinx。若正確則返回成功登入的頁面,否則返回登入頁面。在 post()
方法中,對於登入成功的情況我們通過 set_signed_cookie()
方法頒發了一個 cookie 給客戶端,並設定過期時間為10s,後續客戶端的請求中都會自動帶上這個 cookie。
2. Django 中操作 Session
2.1 Session 的相關配置
由於 Session 的資料是儲存在伺服器端的,所以很多工作是需要在伺服器端來完成的,所以 Django 中 Session 的操作相比 Cookie 操作會略顯複雜。首先需要介紹 Django 中和 Session 相關的配置,同樣是在 settings.py 檔案中:
啟用 Session:需要在 MIDDLEWARE
值中新增相應的 Session 中介軟體,去對 Session 攔截和處理。另外,還需要再INSTALLED_APPS
中註冊 Session 應用。Django 中預設是有這個配置的。
MIDDLEWARE = [
# ...
'django.contrib.sessions.middleware.SessionMiddleware',
# ...
]
INSTALLED_APPS = [
# 啟用 sessions 應用
'django.contrib.sessions',
]
配置 Session 引擎:主要是配置 Session 儲存方式,比如資料庫儲存、記憶體儲存、檔案系統儲存等。
# 資料庫Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 預設引擎
# 快取Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 使用的快取別名(預設記憶體快取,也可以是memcache),此處別名依賴快取的設定
SESSION_CACHE_ALIAS = 'default'
# 檔案Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
# 快取檔案路徑,如果為None,則使用tempfile模組獲取一個臨時地址tempfile.gettempdir()
SESSION_FILE_PATH = None
# 快取+資料庫
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
# 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
其他配置如下:
# Session的cookie儲存在瀏覽器上時的key,即:sessionid=隨機字串(預設)
SESSION_COOKIE_NAME = "sessionid"
# Session的cookie儲存的路徑(預設)
SESSION_COOKIE_PATH = "/"
# Session的cookie儲存的域名(預設)
SESSION_COOKIE_DOMAIN = None
# 是否Https傳輸cookie(預設)
SESSION_COOKIE_SECURE = False
# 是否Session的cookie只支援http傳輸(預設)
SESSION_COOKIE_HTTPONLY = True
# Session的cookie失效日期(2周)(預設)
SESSION_COOKIE_AGE = 1209600
# 是否關閉瀏覽器使得Session過期(預設)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# 是否每次請求都儲存Session,預設修改之後才儲存(預設)
SESSION_SAVE_EVERY_REQUEST = False
關於上述這些未出現在 settings.py 中的配置,預設的值都會在 django/conf/global_settings.py
中找到,如下圖所示:
2.2 操作 Session
在 Django 中,如果我們配置好了 Session 中介軟體並註冊了 Session應用 ,那麼任何檢視函式的第一個引數,也就是 HttpRequest
物件,將會包含一個 session
屬性,我們可以在檢視函式的任何位置使用 request.session
讀取和寫 Session 資訊。這個 session
屬性是前面配置的 SESSION_ENGINE 模組中 SessionStore
類的例項,而SessionStore
類又繼承自 django.contrib.sessions.backends.base.SessionBase
類,它的具有如下操作方法:
- get(self, key, default=None):獲取 Session 中的 key 值對應的 value。這種方式比較推薦。因為在 key 不存在時可以取預設值,而
request.session[key]
這樣的寫法在 key 不存在時會丟擲異常; - pop(key, default=__not_given):返回 Session 中 key 對應的 value 值,並將其從 Session 中刪除;
- keys():返回 Session 中所有的 key 值
- setdefault(key, value):給某個 key 設定對應的預設 value
- set_expiry(value):給 Session 設定對應的過期時間;
還有很多的方法可以在原始碼中找到,我們會在第3部分詳細介紹這些程式碼。可以看到,目前我們操作 Session 就像操作字典那樣簡單,只需要使用 SessionBase
提供的各種方法即可隨意操作 Session,這些都是 Django 在背後給我們做了許多工作,我們也會在第3部分詳細介紹這些工作。
實驗2:使用 Session 完成一個簡單的登入操作,和上面 Cookie 實驗類似。我們的模板檔案不變,同樣只需要改造下檢視函式即可。
改造檢視函式,使用 Session 儲存登入資訊
class TestFormView2(TemplateView):
template_name = 'test_form2.html'
def get(self, request, *args, **kwargs):
success = False
form = LoginForm()
if request.session.get('has_login', False):
return HttpResponse('已經登陸成功,無需再次登陸')
return self.render_to_response(context={'success': success, 'form': form})
def post(self, request, *args, **kwargs):
form = LoginForm(request.POST)
success = True
err_msg = ""
if form.is_valid():
login_data = form.clean()
name = login_data['name']
password = login_data['password']
if name != 'spyinx' or password != 'SPYinx123456':
success = False
err_msg = "使用者名稱密碼不正確"
else:
print('設定session')
request.session['has_login'] = True
# 設定10s後過期
request.session.set_expiry(10)
else:
success = False
err_msg = form.errors['password'][0]
return self.render_to_response(context={'success': success, 'err_msg': err_msg, 'form': form})
我們執行服務,來看相應的演示效果:
上面也可以看到,這裡實現了和之前 Cookie 一樣的效果。不過不同的是,在 Django 中我們預設的使用資料庫的方式儲存 Session 資料,這種方式在使用者量大時頻繁讀寫資料庫會拖累 Web 服務的響應時間。
3. Django 中操作 Cookie 和 Session 的原始碼分析
3.1 Django 中 Cookie 操作相關原始碼
從前面的操作 Cookie 講解中,我們只用到了和增和查兩部分的方法,分別對應 HttpResponse 和 HttpRequest 兩個類。接下來,我們去對應的原始碼中查詢所涉及的和 Cookie 相關的程式碼。
request.COOKIES['xxx']
request.COOKIES.get('xxx', None)
# 原始碼位置:django/core/handlers/wsgi.py
class WSGIRequest(HttpRequest):
# ...
@cached_property
def COOKIES(self):
raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
return parse_cookie(raw_cookie)
# ...
# 原始碼位置:django/http/cookie.py
from http import cookies
# For backwards compatibility in Django 2.1.
SimpleCookie = cookies.SimpleCookie
# Add support for the SameSite attribute (obsolete when PY37 is unsupported).
cookies.Morsel._reserved.setdefault('samesite', 'SameSite')
def parse_cookie(cookie):
"""
Return a dictionary parsed from a `Cookie:` header string.
"""
cookiedict = {}
for chunk in cookie.split(';'):
if '=' in chunk:
key, val = chunk.split('=', 1)
else:
# Assume an empty name per
# https://bugzilla.mozilla.org/show_bug.cgi?id=169091
key, val = '', chunk
key, val = key.strip(), val.strip()
if key or val:
# unquote using Python's algorithm.
cookiedict[key] = cookies._unquote(val)
return cookiedict
上面的程式碼並不複雜,在 WSGIRequest
類中的 COOKIES 屬性是先從客戶端請求中取出 Cookie 資訊,呼叫 get_str_from_wsgi()
方法是從 WSGI 中拿到對應的 Cookie 字串。接下來用 parse_cookie()
方法將原始 Cookie 字串中的 key=value
解析出來做成字典形式並返回。這就是為什麼我們能像操作字典一樣操作 request.COOKIES
的原因。下面的方法是實驗1中呼叫的 get_signed_cookie()
的原始碼,也不復雜,同樣是從self.COOKIES
中取出對應 key 的 value 值,然後使用對應的 salt 解密即可。
# 原始碼位置:django/http/request.py
class HttpRequest:
# ...
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
# ...
接下來是涉及到建立 Cookie 的方法,我們需要查詢 HttpResponse 類或者相關的父類:
# 原始碼位置:django/http/response.py
class HttpResponseBase:
# ...
def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
domain=None, secure=False, httponly=False, samesite=None):
"""
Set 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 calculate ``max_age``.
"""
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
else:
self.cookies[key]['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'] = http_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
if samesite:
if samesite.lower() not in ('lax', 'strict'):
raise ValueError('samesite must be "lax" or "strict".')
self.cookies[key]['samesite'] = samesite
def set_signed_cookie(self, key, value, salt='', **kwargs):
value = signing.get_cookie_signer(salt=key + salt).sign(value)
return self.set_cookie(key, value, **kwargs)
def delete_cookie(self, key, path='/', domain=None):
# Most browsers ignore the Set-Cookie header if the cookie name starts
# with __Host- or __Secure- and the cookie doesn't use the secure flag.
secure = key.startswith(('__Secure-', '__Host-'))
self.set_cookie(
key, max_age=0, path=path, domain=domain, secure=secure,
expires='Thu, 01 Jan 1970 00:00:00 GMT',
)
# ...
從上面的程式碼可以看到,最核心的方法是 set_cookie()
,而刪除 cookie 和 設定加鹽的 cookie 方法最後都是呼叫 set_cookie()
這個方法。而這個方法也比較簡單,就是將對應的傳遞過來的引數值加到 self.cookies
這個字典中。最後我們思考下,難道就這樣就完了嗎?是不是還需要有一步是需要將 self.cookies
中的所有 key-value 值組成字串,放到頭部中,然後才返回給前端?事實上,肯定是有這一步的,程式碼如下。在用 “#” 號包圍起來的那一段程式碼正是將 self.cookies
中的所有 key-value 值組成字串形式,然後放到頭部的 “Set-Cookie” 中,正是有了這一步的動作,我們前面設定的 self.cookie
內部的 key-value 值才能真正生效。
# 原始碼位置:django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_middleware()
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
response = self.get_response(request)
response._handler_class = self.__class__
status = '%d %s' % (response.status_code, response.reason_phrase)
##############################################################################
response_headers = [
*response.items(),
*(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
]
#############################################################################
start_response(status, response_headers)
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
response = environ['wsgi.file_wrapper'](response.file_to_stream)
return response
3.2 Django 中 Session 操作相關原始碼
Django 中和 Session 相關的程式碼較多,我們不去深入追究原始碼細節,主要是程式的執行過程。比如在哪裡設定的 request.session
值以及 session 相關資訊如何儲存到資料庫中的等等。我們先整體看下 Session 相關的程式碼位置:
這裡有幾個比較重要的地方,一個是 backends
目錄,下面是不同儲存 Session 資料方式的程式碼,如使用資料庫儲存、快取儲存或者檔案系統儲存等,每種儲存方式對應著一個程式碼檔案,裡面定義的類和方法都是一致的,這樣就可以無縫切換儲存引擎。
第二個是 middleware.py
檔案,我們先要了解下 Django 中 MIDDLEWARE 的工作流程,可以如下圖所示:
由於在 settings.py 中間設定了 Session 的中介軟體,所以 request 和 response 也會經歷 Session 中介軟體的這個流程,看 session 目錄下的 middleware.py
檔案的程式碼:
class SessionMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
self.get_response = get_response
engine = import_module(settings.SESSION_ENGINE)
self.SessionStore = engine.SessionStore
def process_request(self, request):
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)
def process_response(self, request, response):
"""
If request.session was modified, or if the configuration is to save the
session every time, save the changes and set a session cookie or delete
the session cookie if the session has been emptied.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
pass
else:
# First check if we need to delete this cookie.
# The session should be deleted only if the session is entirely empty
if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
response.delete_cookie(
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
)
else:
if accessed:
patch_vary_headers(response, ('Cookie',))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = http_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
try:
request.session.save()
except UpdateError:
raise SuspiciousOperation(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
response.set_cookie(
# 預設值便是session_id
settings.SESSION_COOKIE_NAME,
request.session.session_key, max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
return response
首先看 __init__()
方法中的設定了對應 Session 的儲存引擎,接下來的 process_request()
方法中我們可以看到 request.session = self.SessionStore(session_key)
這行語句正是給 request 的 session 屬性賦值,而這個值正是儲存引擎模組下的 SessionStore
類的例項。而對於 process_response()
方法,從程式碼中可以看到,它完成了 Session 資料的儲存以及將 session_id 值寫到 cookie 中去並返回給客戶端,呼叫的方法正是我們前面介紹到的 set_cookie()
方法。
接下來,我們看看其他幾個 python 檔案中的程式碼。例如下面的 models.py 定義了 Session 的 model 模型,包括欄位以及管理器:
# 原始碼位置:django/contrib/sessions/models.py
from django.contrib.sessions.base_session import (
AbstractBaseSession, BaseSessionManager,
)
class SessionManager(BaseSessionManager):
use_in_migrations = True
class Session(AbstractBaseSession):
objects = SessionManager()
@classmethod
def get_session_store_class(cls):
from django.contrib.sessions.backends.db import SessionStore
return SessionStore
class Meta(AbstractBaseSession.Meta):
db_table = 'django_session'
從這段程式碼可以看到 Session 如果使用資料庫儲存資料的話,其建立的表名為:django_session,其欄位型別並不在這裡定義,而是繼承的父類 BaseSessionManager
,這個類定義就在 base_session.py
檔案中:
class BaseSessionManager(models.Manager):
def encode(self, session_dict):
"""
Return the given session dictionary serialized and encoded as a string.
"""
session_store_class = self.model.get_session_store_class()
return session_store_class().encode(session_dict)
def save(self, session_key, session_dict, expire_date):
s = self.model(session_key, self.encode(session_dict), expire_date)
if session_dict:
s.save()
else:
s.delete() # Clear sessions with no data.
return s
class AbstractBaseSession(models.Model):
session_key = models.CharField(_('session key'), max_length=40, primary_key=True)
session_data = models.TextField(_('session data'))
expire_date = models.DateTimeField(_('expire date'), db_index=True)
objects = BaseSessionManager()
class Meta:
abstract = True
verbose_name = _('session')
verbose_name_plural = _('sessions')
def __str__(self):
return self.session_key
@classmethod
def get_session_store_class(cls):
raise NotImplementedError
def get_decoded(self):
session_store_class = self.get_session_store_class()
return session_store_class().decode(self.session_data)
從這裡就可以看到 django_session 表有3個欄位,分別是session_key
、session_data
和 expire_date
。繼承這個基類必須要實現 get_session_store_class()
這個方法。另外 app.py
用於指明 session 應用名稱,exceptions.py
定義了兩個簡單的異常,serializers.py
的內容也比較簡單,僅僅使用 pickle 模組封裝了一個用於序列化的類:
import pickle
from django.core.signing import JSONSerializer as BaseJSONSerializer
class PickleSerializer:
"""
Simple wrapper around pickle to be used in signing.dumps and
signing.loads.
"""
protocol = pickle.HIGHEST_PROTOCOL
def dumps(self, obj):
return pickle.dumps(obj, self.protocol)
def loads(self, data):
return pickle.loads(data)
JSONSerializer = BaseJSONSerializer
我們也可以簡單使用下這個類,其實就是熟悉 pickle 模組的 dumps()
和 loads()
方法。
(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.sessions.serializers import PickleSerializer
>>> from hello_app.views import Member
>>> Member.objects.all().get(name='spyinx-1')
<Member: <spyinx-1, 18017715080>>
>>> m = Member.objects.all().get(name='spyinx-1')
>>> PickleSerializer().dumps(m)
b'\x80\x05\x95o\x01\x00\x00\x00\x00\x00\x00\x8c\x15django.db.models.base\x94\x8c\x0emodel_unpickle\x94\x93\x94\x8c\thello_app\x94\x8c\x06Member\x94\x86\x94\x85\x94R\x94}\x94(\x8c\x06_state\x94h\x00\x8c\nModelState\x94\x93\x94)\x81\x94}\x94(\x8c\x06adding\x94\x89\x8c\x02db\x94\x8c\x07default\x94ub\x8c\x02id\x94K\x05\x8c\x04name\x94\x8c\x08spyinx-1\x94\x8c\x03age\x94\x8c\x0225\x94\x8c\x03sex\x94K\x00\x8c\noccupation\x94\x8c\x07teacher\x94\x8c\tphone_num\x94\x8c\x0b18017715080\x94\x8c\x05email\x94\x8c\[email protected]\x94\x8c\x04city\x94\x8c\x08shenzhen\x94\x8c\rregister_date\x94\x8c\x08datetime\x94\x8c\x08datetime\x94\x93\x94C\n\x07\xe4\x04\t\x10\x18\n\x00\x00\x00\x94\x85\x94R\x94\x8c\x0cvip_level_id\x94N\x8c\x0f_django_version\x94\x8c\x062.2.11\x94ub.'
>>> m1 = PickleSerializer().loads(s)
>>> type(m1)
<class 'hello_app.models.Member'>
>>> m1.name
'spyinx-1'
最後,關於 Session 的引擎不用過多細究,裡面預設用到的是 backends/db.py
。如果使用的是快取引擎,程式碼內容也是大同小異的。主要認真研究兩個類:
backends/base.py
中的SessionBase
類。這個是所有SessionStore
的基類,它具有的方法正是我們操作 session 的方法;backends/db.py
中的SessionStore
類。前面的request.session
便是該類的一個例項,它的程式碼內容也是不復雜的,主要針對該種儲存方式需要完成特定的儲存(save()
)、刪除(delete()
) 以及匯入(load()
)等方法。
到此為止,Django 中關於 Session 的程式碼就這麼多了。剩下的程式碼細節還需要各位去慢慢專研,也希望大家能認真鑽研 Django 的原始碼,多多思考,然後多多實踐。
4. 小結
本小節中我們講解了在 Django 中如何操作 Cookie 和 Session,並各給出了一個實戰案例。接下來我們學習了 Django 中操作 Cookie 和 Session 的方法的原始碼,在熟悉了這些程式碼之後,對於我們操作 Cookie 和 Session 會更加得心應手 。