Web Session 淺入淺出

使用過幾種Web App開發語言和框架,都會接觸到Session的概念。即便是一個簡單站點訪問計數的功能,也經常使用Session來實現的。其餘經常使用的領域還有購物車,登陸用戶等。可是,對Session一直是隻知其一;不知其二,知其然而不知其因此然。php

在認真的研究了HTTP協議,以及nodejs開發棧的express和express-session後,我終於比較有把握深刻淺出的說清楚Session了,也算是知足了多年來開發過程當中,經常浮現的對Session的好奇心吧。node

本文使用nodejs v9.5.0做爲技術驗證工具。閱讀本文前須要瞭解基礎的HTTP知識和Cookie知識。詳細須要參考rfc6265,或者閱讀《HTTP小書》的最後一章。mysql

會話的概念

用戶在網站的一組相互關聯的的請求和響應,就是一次會話。簡而言之是這樣的:redis

  1. 會話 = 一組訪問
  2. 訪問 = 一次請求和響應

好比一個最簡單的nodejs HTTP程序:算法

var http = require('http')
http.createServer(function(req,res){
    res.end('hello')    
}).listen(3000)

每一個請求都會進入到此處理函數:function(req,res){res.end('hello') },在此函數內得到請求,處理響應,完成後發給客戶端,就是一次訪問。經過瀏覽器的developer tools,能夠看到這次會話的請求內容和響應內容。sql

以站點計數應用爲案例來講明的話,就是這些來自於一樣訪問者的屢次訪問,均可以得到當前站點的訪問計數。mongodb

引入會話

咱們從一個案例開始引入會話的概念。當咱們須要訪問站點計數一類的功能時,咱們但願用戶訪問此站點時:chrome

  1. 第一次訪問時,顯示你的訪問次數爲1
  2. 之後每次訪問時,訪問計數加1

此種狀況下,咱們須要有一個地方存儲當前計數,這樣才能在同一個客戶在此訪問時,能夠取出當前計數,加一後返回給客戶。固然也所以須要識別此用戶(瀏覽器),爲每一個用戶單獨計數。就是說,不一樣的用戶訪問時,須要去取對應用戶的當前計數。數據庫

識別客戶的問題,經常使用的方法就是使用Cookie。Cookie是HTTP協議的一部分。HTTP能夠經過頭字段Set-Cookie爲來訪客戶作一個標記,這個標記經常就是一個ID,下一次訪問此站點時,HTTP會經過Cookie頭字段,發送此ID到站點,由此站點知道此客戶的身份和這個身份關聯的狀態信息,好比當前訪問計數,或者此身份當前的購物車的內容等等。express

識別了客戶後,就能夠在Web服務器內,爲此客戶創建它的獨特的狀態信息。

實現一個會話

基於nodejs HTTP模塊,咱們實現一個極爲簡單的Session服務。只是爲了展現概念,而不是爲了實用的目的。此服務能夠實現一個共享於同一站點的屢次訪問的req.session變量,此變量爲一個對象,能夠在此變量內寫入新的成員,或者修改現存的成員變量的值,每次訪問後會保存req.session,以便下次訪問能夠獲得當前的值:

var http = require('http')
var sessionkey = "sessionkey3"
http.createServer(function(req,res){
    if (req.url =="/"){
        session(req,res)
        req.session.count = (req.session.count+1) || 1
        res.end('hi'+req.session.count)
    }else
        res.end('')    
}).listen(3000)
console.log('listen on 3000')
function session(req,res){
    if (req.session)
        return
    var answer ,id
    if(isSessionOk(req)){
        id = getCookie(req)
        answer = getSessionById(id)
    }else{
        answer=  {}
        id = createSession(answer)
        setCookie(res,id)
    }
    req.session = answer
    res.on('finish', function() {
        saveSession(id,req.session)
    });
}
function hasCookie(req){
  return (getCookie(req)!='') 
}
function getCookie(req){
  try{
      var c = req.headers['cookie']
      var arr = c.split(';')
      for (var i = 0; i < arr.length; i++) {
          var kv = arr[i]
          var a = kv.split('=')
          if (a[0].trim() == sessionkey)
              return a[1]
      }
  }catch(error){
      return ''
  }
  return ''
}
function setCookie(res,id){
  res.setHeader("set-cookie",sessionkey +"="+id)
}
var sessions = {}
var sid = 0  
function getSessionById(sid){
    return sessions[sid]
}
function getSessionByReq(req){
    var sid = getCookie(req)
    return sessions[sid]
}
function createSession(session){
    sessions[sid++,session]
    return sid
}
function saveSession(sid,session){
    sessions[sid] = session
}
function isSessionOk(req){
    return hasCookie(req) && getSessionByReq(req) !== undefined
}

程序代碼比較簡單,讀者能夠保持它到index.js,而後執行此程序,驗證概念:

node index.js

而後,啓動chrome,訪問站點localhost:3000,而後屢次刷新,你能夠看到每次刷新,返回的訪問次數逐步累加。在打開另外一個瀏覽器,好比safari,在此訪問此站點,你會發現返回的訪問計數從1開始,另外計數。由於是兩個不一樣的瀏覽器內器,這就保證的它們是不一樣的訪問客戶,在站點內的代碼,會區別二者,分別記錄它們的狀態信息。

代碼使用了HTTP Cookie,基本算法很簡單:

  1. 若是Session沒有準備好,那麼建立一個Session,獲得Session的ID,把此ID經過Set-Cookie發送給瀏覽器。瀏覽器會在下一次訪問此站點時,發送此ID。
  2. 若是Session已經準備好了,也就是說,瀏覽器經過Cookie發來了ID,而且經過此ID,能夠在站點內獲取到Session
  3. 把建立或者獲取的Session賦值給req對象
  4. 在請求處理函數生命週期內,能夠獲取和修改Session對象
  5. 在請求處理完後,保存此Session變量

可能你們看到sessionkey這個變量,感受有些莫名其妙。緣由是每次cookie發送,一樣的站點可能有多個框架須要使用此cookie頭字段,好比php,aspx,jsp等都是須要使用了,爲了好像不要衝突,你們各自使用cookie頭字段內各自的key/value對便可。好比php的key默認是phpsessid,express-session默認的是connect.sid。

總結

此代碼演示了最基礎的Session的概念,可是遠遠不是一個可用的模塊,想要真實世界中使用的Session模塊,能夠考慮express-session。

實現一個真正能夠的會話,還須要考慮不少問題:

  1. 本文中使用是Session Id其實就是一個自增的整數。這會致使客戶端欺騙,黑客能夠猜到SessionID,使用僞造的SessionID得到服務器內對應的狀態數據,或者僞造登陸從而得到更高權限。真實的產品,通常是建立一個保證惟一的,不易猜想出來的字符串。
  2. 本文中的Session每次end後會必然保存,而無論此Session是否修改。實際的產品,是須要考慮此優化的。同時,也須要考慮到Session的失效期,到了失效期就會銷燬,由於有些客戶可能來一次兩次也後不再來訪問了,沒有必要爲他們保存狀態信息,若是再來了,不妨從新建立會話便可。
  3. 本文的Session保存在內存中,一旦重啓,全部會話都會丟失。實際產品中,是須要支持持久化的保存的,好比保存到mysql數據庫內,redis內,mongodb內等等。所以須要數據持久化的多提供者的方案。

更多的考量,能夠去經過閱讀express-session來得到。本文閱讀完畢,自己就是能夠成爲閱讀express-session的基礎材料的。

相關文章
相關標籤/搜索