歡迎光臨
每天分享高質量文章

都 9102 年了,還問 Session 和 Cookie 的區別

1 前言

最近看了一些同學的面經,發現無論什麼技術崗位,還是會問到 Session 和 Cookie 的區別。

所有學技術的同學都知道 Session 和 Cookie 函式怎麼用,知道 Session 和 Cookie 的區別就是 Session 是儲存在服務端的,Cookie 是儲存在瀏覽器的。

但是實際上是什麼東西,一些剛學習技術的同學估計還是模糊,我剛學 PHP 的時候,這種感覺特別明顯。PHP 中 Session 和 Cookie 的操作只要操作 $_COOKIE 和 $_SESSION 陣列就可以了,而且操作方式和功能一模一樣,搞得我一臉懵逼。

最後,還是自己實現了一個 Session 操作類才恍然大悟,實質上就是兩個不同的儲存物件嘛。

2 Cookie

Cookie 的誕生是為了能讓無狀態的 HTTP 報文帶上一些特殊的資料,讓服務端能夠辨識請求的身份。

對於 Cookie 的概念就不多說了,Cookie 說簡單點就是瀏覽器上的一個 key-value 儲存物件,透過開發者工具直接看到 Cookie 的內容(F12)

寫入資料方式

Cookie 寫入資料的方式是透過 HTTP 傳回報文 Header 部分 Set-Cookie 欄位來設定,一個帶有寫 Cookie 指令的的 HTTP 傳回報文如下

HTTP/1.1 200 OK
Set-Cookie: SESSIONID=e13179a6-2378-11e9-ac30-fa163eeeaea1; Path=/
Transfer-Encoding: chunked
Date: Tue, 29 Jan 2019 07:12:09 GMT
Server: localhost

上述報文 Set-Cookie 指示瀏覽器設定 key 為 SESSIONIDvalue 為 e13179a6-2378-11e9-ac30-fa163eeeaea1 的 Cookie

獲取資料方式

瀏覽器在傳送請求的時候會檢查當前域已經設定的 Cookie,在 HTTP 請求報文 Header 部分的 Cookie 欄位裡面帶上 Cookie 的資訊。下麵捉取了一段 HTTP 報文

GET http://10.0.1.24:23333/ HTTP/1.1
Host: 10.0.1.24:23333
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: SESSIONID=e13179a6-2378-11e9-ac30-fa163eeeaea1

從最後的 Cookie 欄位看到,瀏覽器請求時帶上了 key 為 SESSIONIDvalue 為 e13179a6-2378-11e9-ac30-fa163eeeaea1 的資料,後端直接解析 HTTP 報文就能獲取 Cookie 的內容。

 

3 Session

Session 在程式碼裡面的語意是記錄客戶端狀態的一個儲存物件,是同一個客戶端請求共享的陣列。這個儲存物件可以是檔案、快取系統、資料庫。

現在假設要使用 redis 來實現 Session 功能,那麼就要求瀏覽器每次請求都要帶一個相同的字串作為身份資訊,對應 redis 的 key,redis value 則為 Session 陣列序列化的內容。

那麼如何讓瀏覽器每次請求都帶一個身份資訊呢,這就是 Session 和 Cookie 的關係,透過 Cookie 傳遞這個身份資訊。流程如下

  1. 客戶端請求
  2. 服務端檢查 Header,發現沒有 Cookie,於是生成一個 UUID
  3. 服務端處理資料,把部分資料(登入資訊)儲存到 redis 裡面,UUID 為 key,使用者 id 為 value
  4. 傳回報文中,增加 Set-Cookie 欄位,內容帶上 UUID
  5. 瀏覽器收到報文,把 UUID 寫進瀏覽器儲存裡面
  6. 瀏覽器再次請求,帶上當前的域的 Cookie,就是這個 UUID
  7. 服務端透過 Cookie 欄位獲取到該 UUID,去 redis 裡面獲取使用者的資訊

4 手動實現 Session

 

既然知道了 Session 的原理,我們手動實現一個 Session 操作類,採用檔案儲存的方式。http 框架採用 web.py,安裝方式如下

 

pip install web.py

 

Session類

 

我們要實現的這個類就叫 Session

 

class Session:

    def __init__(self):
        self.session_id = None
        # session 陣列
        self._items = dict()
        self._load()

 

我們所有 session 檔案放在 sessions 目錄下,檔案名為對應的 session id,內容為 Session 陣列序列化的字串。在初始化物件的時候透過獲取名為 SESSIONID 的 Cookie,如果沒有就生成一個新的。

 

def _load(self):
    SESSIONID = web.cookies().get('SESSIONID', None)
    if not SESSIONID:
        SESSIONID = uuid.uuid()
    self.session_id = SESSIONID

    self._loadFromDisk()

 

獲取到 SESSIONID 後,檢查 sessions 目錄下有沒有對應的檔案,如果有就讀取並反序列化

 

def _loadFromDisk(self):
    """ 從檔案載入 SESSION """
    file = './sessions/%s' % self.session_id
    if os.path.exists(file):
        f = open(file, 'rb')
        self._items = pickle.load(f)
        f.close()

 

獲取 Session 部分完成了,接下來就是儲存 Session,我們要把 SESSIONID 寫進 Cookie 裡面,這樣才能在下次請求時獲取到對應的 SESSIONID

 

def _setSessionCookie(self):
    """ Session id 寫入 cookie """
    web.setcookie('SESSIONID', self.session_id)

 

最後把 Session 的內容儲存到檔案裡面

 

def _saveToDisk(self):
    """ 儲存 SESSION 到檔案 """
    f = open('./sessions/%s' % self.session_id, 'wb')
    pickle.dump(self._items, f)
    f.close()

 

功能測試

 

我們新建一個檔案,叫 server.py,寫入測試程式碼

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 測試 session

import web
import time
from session import Session

urls = (
    '/', 'index'
)

app = web.application(urls, globals())

class index:
    def GET(self):
        session = Session()
        if 'login_time' not in session:
            session['login_time'] = int(time.time())
        return 'login time: %s' % session['login_time']

if __name__ == "__main__":
    app.run()

 

在同一目錄新建 session.py 檔案,寫入 Session 類程式碼

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Session

import os
import web
import uuid
try:
    import cPickle as pickle
except ImportError:
    import pickle

class Session:
    __instance = None

    def __new__(cls):
        """ 單例樣式 """
        if cls.__instance is None:
            cls.__instance = object.__new__(cls)
            return cls.__instance
        else:
            return cls.__instance

    def __init__(self):
        self.session_id = None
        # session 陣列
        self._items = dict()
        self._load()

    def __contains__(self, key):
        return key in self._items

    def __getitem__(self, key):
        return self._items.get(key, None)

    def __setitem__(self, key, value):
        self._items[key] = value
        return True

    def __delitem__(self, key):
        if key in self._items:
            del self._items[key]
        return True

    def __del__(self):
        """ 解構式,結束請求時執行 """
        self._setSessionCookie()
        self._saveToDisk()

    def _load(self):
        SESSIONID = web.cookies().get('SESSIONID', None)
        if not SESSIONID or SESSIONID is None:
            SESSIONID = uuid.uuid()
        self.session_id = SESSIONID

        self._loadFromDisk()

    def _loadFromDisk(self):
        """ 從檔案載入 SESSION """
        file = './sessions/%s' % self.session_id
        if os.path.exists(file):
            f = open(file, 'rb')
            self._items = pickle.load(f)
            f.close()

    def _setSessionCookie(self):
        """ Session id 寫入 cookie """
        web.setcookie('SESSIONID', self.session_id)

    def _saveToDisk(self):
        """ 儲存 SESSION 到檔案 """
        f = open('./sessions/%s' % self.session_id, 'wb')
        pickle.dump(self._items, f)
        f.close()

 

再在同一目錄新建 sessions 目錄,存放我們的 Session 檔案

 

mkdir sessions

 

啟動服務

 

[service@chengqm mysession]$ python server.py 23333
http://0.0.0.0:23333/

 

瀏覽器發起請求

檢視 Cookie

 

 

檢視 Session 檔案內容

[service@chengqm mysession]$ cat sessions/e13179a6-2378-11e9-ac30-fa163eeeaea1
(dp1
S'login_time'
p2
I1548749002
s.

可以多次掃清和更換瀏覽器測試,測試結果是符合我們對 Session 的預期,簡陋版 Session 類功能就算實現了。

 

5 如果禁止 Cookie 是否可以獲取 Session

這是一道面試題,當年竟然能用這個問題問倒過一些朋友,還是有些意思的

從前面可以知道,SESSIONID 是透過 Cookie 來傳遞,如果 Cookie 禁止了,還能獲取 SESSIONID 嗎? 答案是可以的

既然 Cookie 禁止了,那麼我們就可以用引數的方法傳遞 SESSIONID,後端傳回的時候,增加一個傳回引數,叫 SESSIONID,然後前端儲存到 localstorage 裡面

前端請求的時候,去 localstorage 獲取SESSIONID,在請求引數裡面增加這一個引數

後端 Session 處理,先嘗試從 Cookie 中獲取 SESSIONID,如果獲取不到,再嘗試從請求引數中獲取 SESSIONID

這樣,就算禁止 Cookie 也是能獲取 Session 的。

6 總結

最後,我們得出 Session 和 Cookie 區別和聯絡

區別

  • Cookie 是瀏覽器端的儲存物件,有容量限制,透過 HTTP 報文與後端互動
  • Session 是服務端的儲存物件,實現的方式可以有檔案系統、快取系統、資料庫

聯絡

  • Session 和 Cookie 都是為了實現 HTTP 請求帶上客戶端狀態的方法
  • Session 大多數情況下都是依賴 Cookie 來傳遞 Session Id

    贊(0)

    分享創造快樂