0.概述
(1)為什么要有cookies和session
在網站中,http請求是無狀態的。也就是說即使第一次和服務器連接后並且登錄成功后,第二次請求服務器依然不能知道當前請求是哪個用戶。而在現實網站中,那淘寶網來舉例,用戶將商品加入購物車和用戶付款是不同的請求,但這個這連個請求需要是同一個用戶,而這樣的使用場景很多。總結來說,cookies和session一種用戶識別用戶身份的解決方案。
(2)cookies
第一次登錄后服務器返回一些數據(cookie)給瀏覽器,然后瀏覽器保存在本地,當該用戶發送第二次請求的時候,就會自動的把上次請求存儲的 cookie 數據自動的攜帶給服務器,服務器通過瀏覽器攜帶的數據就能判斷當前用戶是哪個了。 cookie 存儲的數據量有限,不同的瀏覽器有不同的存儲大小,但一般不超過4KB。因此使用 cookie 只能存儲一些小量的數據。
(3)session
session和cookie的作用有點類似,都是為了存儲用戶相關的信息。不同的是, cookie 是存儲在本地瀏覽器, session 是一個思路、一個概念、一個服務器存儲授權信息的解決方案,不同的服務器,不同的框架,不同的語言有不同的實現。雖然實現不一樣,但是他們的目的都是服務器為了方便存儲數據的。 session 的出現,是為了解決 cookie 存儲數據不安全的問題的。
(4)cookies與session對比
1.存儲位置。cookies存儲在服務端,session存儲在服務端。 2.安全性。session因為存儲在服務端,安全性更高。 3.存儲容量。cookies一般不能超過4kb,而session沒有限制。
1.操作cookies
cookies本質上是返回值給瀏覽器,而在Django上是通過調用response對象的方法來實現cookies的操作
(1)設置cookies
通過set_cookies方法來實現的。具體參數如下:
1. key :這個 cookie 的 key 。 2. value :這個 cookie 的 value 。 3. max_age :最長的生命周期。單位是秒。 4. expires :過期時間。跟 max_age 是類似的,只不過這個參數需要傳遞一個具體的日期,比如 datetime 或者是符合日期格式的字符串。如果同時設置了 expires 和 max_age ,那么將 會使用 expires 的值作為過期時間。 5. path :對域名下哪個路徑有效。默認是對域名下所有路徑都有效。 6. domain :針對哪個域名有效。默認是針對主域名下都有效,如果只要針對某個子域名才有效,那么可以設置這個屬性. 7. secure :是否是安全的,如果設置為 True ,那么只能在 https 協議下才可用。 8. httponly :默認是 False 。如果為 True ,那么在客戶端不能通過 JavaScript 進行操作。
(2)刪除cookie:
通過 delete_cookie方法即可刪除 cookie 。實際上刪除 cookie 就是將指定的 cookie 的值設置為空的字符串,然后使用將他的過期時間設置為 0 ,也就是瀏覽器關閉后就過期。
(3)獲取cookie:
獲取瀏覽器發送過來的 cookie 信息。可以通過 request.COOKIES(最終對象是一個字典類型,可以通過遍歷來獲取所需要的數據)。
2.操作sessions
(1)session存儲方案介紹
存儲在服務端:通過 cookie 存儲一個 sessionid ,然后具體的數據則是保存在 session 中。如果用戶已經登錄,則服務器會在 cookie 中保存一個 sessionid,
下次再次請求的時候,會把該 sessionid 攜帶上來,服務器根據 sessionid 在 session 庫中獲取用戶的 session 數據。就能知道該用戶到底是誰,以及之前保存的一些狀態信息。
這種專業術語叫做 server side session 。Django 把 session 信息默認存儲到數據庫中,當然也可以存儲到其他地方,比如緩存中,文件系統中等。
存儲在服務器的數據會更加的安全,不容易被竊取。但存儲在服務器也有一定的弊端,就是會占用服務器的資源,但現在服務器已經發展至今,一些 session 信息還是綽綽有余的。
存儲在瀏覽器:將 session 數據加密,然后存儲在 cookie 中。這種專業術語叫做 client side session。flask框架默認采用的就是這種方式,但是也可以替換成其他形式。
(2)sessionss操作流程:
django 中的 session 默認情況下是存儲在服務器的數據庫中的,通過 request.session 即可操作。常用方法如下:
1. get :用來從 session 中獲取指定值。 2. pop :從 session 中刪除一個值。 3. keys :從 session 中獲取所有的鍵。 4. items :從 session 中獲取所有的值。 5. clear :清除當前這個用戶的 session 數據。 6. flush :刪除 session 並且刪除在瀏覽器中存儲的 session_id ,一般在注銷的時候用得比較多。 7. set_expiry(value) :設置過期時間。 整形:代表秒數,表示多少秒后過期。 0 :代表只要瀏覽器關閉, session 就會過期。 None :會使用全局的 session 配置。在 settings.py 中可以設置 SESSION_COOKIE_AGE 來配置全局的過期時間。默認是 1209600 秒,也就是2周的時間。 8. clear_expired:清除過期的session。 Django並不會清除過期的session,需要定期手動的清理,或者是在終端,使用命令行 python manage.py clearsessions來清除過期 的 session 。
(3)session存儲機制修改
默認情況下, session 數據是存儲到數據庫中的。當然也可以將 session 數據存儲到其他地方。可以通過設置 SESSION_ENGINE 來更改 session 的存儲位置,這個可以配置為以下幾種方案:
1. django.contrib.sessions.backends.db :使用數據庫。默認就是這種方案。 2. django.contrib.sessions.backends.file :使用文件來存儲session。 3. django.contrib.sessions.backends.cache :使用緩存來存儲session。想要將數據存儲到緩存中,前提是你必須要在 settings.py 中配置好 CACHES ,
並且是需要使用 Memcached ,而不能使用純內存作為緩存。 4. django.contrib.sessions.backends.cached_db :在存儲數據的時候,會將數據先存到緩存中,再存到數據庫中。這樣就可以保證萬一緩存系統出現問題,session數據也不會丟失。在 獲取數據的時候,會先從緩存中獲取,如果緩存中沒有,那么就會從數據庫中獲取。 5. django.contrib.sessions.backends.signed_cookies :將 session 信息加密后存儲到瀏覽器的 cookie 中。這種方式要注意安全,
建議設置SESSION_COOKIE_HTTPONLY=True ,那么在瀏覽器中不能通過 js 來操作 session 數據,並且還需要對 settings.py 中的 SECRET_KEY 進行保密,
因為一旦別人知道這個 SECRET_KEY ,那么就可以進行解密。另外還有就是在 cookie 中,存儲的數據不能超過 4k 。
3.總結
一個好的程序員不應該只是知道不要重復造輪子,但是應該知道輪子是如何造的,了解一個功能背后的原理,能夠設計出適合自己的輪子,這才是優秀的程序員。所謂舉一反三,從設計者的角度來思考。要知道代碼只是結局問題的一種表達方式,你可以有自己的表達方式。
3.1django中cookies實現原理
(1)cookies的設置流程:
1.客戶端發起一個請求連接(如HTTP GET)。 2.服務器在http響應頭上加上Set-Cookie,里面存放字符串的鍵值對。 3.客戶端隨后的http請求頭加上Cookie首部,它包含了之前服務器響應中設置cookie的信息。
(2)利用Python實現cookies設置,代碼如下:
from BaseHTTPServer import HTTPServer from SimpleHTTPServer import SimpleHTTPRequestHandler import Cookie class MyRequestHandler(SimpleHTTPRequestHandler): def do_GET(self): content = "Path is: %s" % self.path self.send_response(200) self.send_header('Content-type', 'text/html') self.send_header('Content-length', str(len(content))) cookie = Cookie.SimpleCookie() cookie['id'] = 'some_value_42' self.wfile.write(cookie.output()) self.wfile.write('\r\n') self.end_headers() self.wfile.write(content) server = HTTPServer(('', 59900), MyRequestHandler) server.serve_forever()
(3)Django通過一系列的包裝使得封裝Cookie的操作變得更加簡單,僅僅使用set_cookies就可以操作,那么它在其中是怎么實現cookie的讀取的呢,下面來窺探原理。
def _get_cookies(self): if not hasattr(self, '_cookies'): self._cookies = http.parse_cookie(self.environ.get('HTTP_COOKIE', '')) return self._cookies
可以看出,獲取cookie的操作用了Lazy initialization(延遲加載)的技術,因為如果客戶端不需要用到cookie,這個過程只會浪費不必要的操作。
再來看parse_cookie的實現:
def parse_cookie(cookie): if cookie == '': return {} if not isinstance(cookie, Cookie.BaseCookie): try: c = SimpleCookie() c.load(cookie, ignore_parse_errors=True) except Cookie.CookieError: # 無效cookie return {} else: c = cookie cookiedict = {} for key in c.keys(): cookiedict[key] = c.get(key).value return cookiedict
它負責解析Cookie並把結果集成到一個dict(字典)對象中,並返回字典。而設置cookie的操作則會被WSGIHandler執行。
3.2session原理分析
(1)Django中的session實現
class SessionBase(object): """ Base class for all Session classes. """ TEST_COOKIE_NAME = 'testcookie' TEST_COOKIE_VALUE = 'worked' def __init__(self, session_key=None): self._session_key = session_key self.accessed = False self.modified = False self.serializer = import_string(settings.SESSION_SERIALIZER)
其實django中的session就是一個模擬dict的對象,並實現了一系列的hash和序列化方法,默認持久化在數據庫中(有時候也可能由於為了提高性能,用redis之類的內存數據庫來緩存session)。
(2)session的操作機制
session操作是通過request.session再加上系列的方法實現的,那么其背后的原理本質上是什么呢?
其實是用了Django的中間件部分,可以參考后續博客內容:django的中間件和上下文處理器。這里先簡單介紹,中間件可以在請求到來之前和響應返回的時候做一些處理。
我們看傳統的django視圖模式一般是這樣的:http請求->view->http響應,而加入中間件框架后,則變為:http請求->中間件處理->app->中間件處理->http響應。而在django中這兩個處理分別對應process_request和process_response函數,這兩個鈎子函數將會在特定的時候被觸發。
下面結合session中間件具體分析:
class SessionMiddleware(object): def __init__(self): 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, 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 = cookie_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: # The user is now logged out; redirecting to same # page will result in a redirect to the login page # if required. return redirect(request.path) response.set_cookie(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) return response
在請求到來后,SessionMiddleware的process_request在請求取出session_key,並把一個新的session對象賦給request.session,而在返回響應時,process_response則判斷session是否被修改或過期,來更新session的信息。
(3)dajngo認證中的session
其實request.user的實現也借助到了session。當用戶通過login(request,user)登陸的時候,會默認在數據庫中保存session數據。
跟上面提到的Session中間件相似,用戶驗證也有一個中間件:AuthenticationMiddleware,在process_request中,通過request.class.user = LazyUser()在request設置了一個全局的可緩存的用戶對象。
class LazyUser(object): def __get__(self, request, obj_type=None): if not hasattr(request, '_cached_user'): from django.contrib.auth import get_user request._cached_user = get_user(request) return request._cached_user class AuthenticationMiddleware(object): def process_request(self, request): request.__class__.user = LazyUser() return None
在get_user里,會在檢查session中是否存放了當前用戶對應的user_id,如果有,則通過id在model查找相應的用戶返回,否則返回一個匿名的用戶對象(AnonymousUser)。
def get_user(request): from django.contrib.auth.models import AnonymousUser try: user_id = request.session[SESSION_KEY] backend_path = request.session[BACKEND_SESSION_KEY] backend = load_backend(backend_path) user = backend.get_user(user_id) or AnonymousUser() except KeyError: user = AnonymousUser() return user