国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

從koa-session源碼解讀session本質

remcarpediem / 546人閱讀

摘要:前言,又稱為會話控制,存儲特定用戶會話所需的屬性及配置信息。類先看構造函數居然啥屁事都沒干。由此基本得出推斷,并不是服務器原生支持,而是由服務程序自己創建管理。類老規矩,先看構造函數接收了實例傳來和,其他沒有做什么。

前言
Session,又稱為“會話控制”,存儲特定用戶會話所需的屬性及配置信息。存于服務器,在整個用戶會話中一直存在。

然而:

session 到底是什么?

session 是存在服務器內存里,還是web服務器原生支持?

http請求是無狀態的,為什么每次服務器能取到你的 session 呢?

關閉瀏覽器會過期嗎?

本文將從 koa-session(koa官方維護的session中間件) 的源碼詳細解讀 session 的機制原理。希望大家讀完后,會對 session 的本質,以及 session 和 cookie 的區別有個更清晰的認識。

基礎知識

相信大家都知道一些關于 cookie 和 session 的概念,最通常的解釋是 cookie 存于瀏覽器,session 存于服務器。

cookie 是由瀏覽器支持,并且http請求會在請求頭中攜帶 cookie 給服務器。也就是說,瀏覽器每次訪問頁面,服務器都能獲取到這次訪問者的 cookie 。

但對于 session 存在服務器哪里,以及服務器是通過什么對應到本次訪問者的 session ,其實問過一些后端同學,解釋得也都比較模糊。因為一般都是服務框架自帶就有這功能,都是直接用。背后的原理是什么,并不一定會去關注。

如果我們使用過koa框架,就知道koa自身是無法使用 session 的,這就似乎說明了 session 并不是服務器原生支持,必須由 koa-session 中間件去支持實現。

那它到底是怎么個實現機制呢,接下來我們就進入源碼解讀。

源碼解讀

koa-session:https://github.com/koajs/session

建議感興趣的同學可以下載代碼先看一眼

解讀過程中貼出的代碼,部分有精簡

koa-session結構

來看 koa-session 的目錄結構,非常簡單;主要邏輯集中在 context.js 。

├── index.js    // 入口
├── lib
│?? ├── context.js
│?? ├── session.js
│?? └── util.js
└── package.json

先給出一個 koa-session 主要模塊的腦圖,可以先看個大概:

屢一下流程

我們從 koa-session 的初始化,來一步步看下它的執行流程:

先看下 koa-sessin 的使用方法:

const session = require("koa-session");
const Koa = require("koa");
const app = new Koa();

app.keys = ["some secret hurr"];
const CONFIG = {
  key: "koa:sess",  // 默認值,自定義cookie中的key
  maxAge: 86400000
};

app.use(session(CONFIG, app));  // 初始化koa-session中間件

app.use(ctx => {
  let n = ctx.session.views || 0;   // 每次都可以取到當前用戶的session
  ctx.session.views = ++n;
  ctx.body = n + " views";
});

app.listen(3000);
初始化

初始化 koa-session 時,會要求傳入一個app實例。

實際上,正是在初始化的時候,往 app.context 上掛載了session對象,并且 session 對象是由 lib/context.js 實例化而來,所以我們使用的 ctx.session 就是 koa-session 自己構造的一個類。

我們打開koa-session/index.js

module.exports = function(opts, app) {
  opts = formatOpts(opts);  // 格式化配置項,設置一些默認值
  extendContext(app.context, opts); // 劃重點,給 app.ctx 定義了 session對象

  return async function session(ctx, next) {
    const sess = ctx[CONTEXT_SESSION];
    if (sess.store) await sess.initFromExternal();
    await next();
    if (opts.autoCommit) {
      await sess.commit();
    }
  };
};

通過內部的一次初始化,返回一個koa中間件函數。

一步一步的來看,formatOpts 是用來做一些默認參數處理,extendContext 的主要任務是對 ctx 做一個攔截器,如下:

function extendContext(context, opts) {
  Object.defineProperties(context, {
    [CONTEXT_SESSION]: {
      get() {
        if (this[_CONTEXT_SESSION]) return this[_CONTEXT_SESSION];
        this[_CONTEXT_SESSION] = new ContextSession(this, opts);
        return this[_CONTEXT_SESSION];
      },
    },
    session: {
      get() {
        return this[CONTEXT_SESSION].get();
      },
      set(val) {
        this[CONTEXT_SESSION].set(val);
      },
      configurable: true,
    }
  });
}

走到上面這段代碼時,事實上就是給 app.context 下掛載了一個“私有”的 ContextSession 對象 ctx[CONTEXT_SESSION] ,有一些方法用來初始化它(如initFromExternal、initFromCookie)。然后又掛載了一個“公共”的 session 對象。

為什么說到“私有”、“公共”呢,這里比較細節。用到了 Symbol 類型,使得外部不可訪問到 ctx[CONTEXT_SESSION] 。只通過 ctx.session 對外暴露了 (get/set) 方法。

再來看下 index.js 導出的中間件函數

return async function session(ctx, next) {
  const sess = ctx[CONTEXT_SESSION];
  if (sess.store) await sess.initFromExternal();
  await next();
  if (opts.autoCommit) {
    await sess.commit();
  }
};

這里,將 ctx[CONTEXT_SESSION] 實例賦值給了 sess ,然后根據是否有 opts.store ,調用了 sess.initFromExternal ,字面意思是每次經過中間件,都會去調一個外部的東西來初始化 session ,我們后面會提到。

接著看是執行了如下代碼,也即執行我們的業務邏輯。

await next()

然后就是下面這個了,看樣子應該是類似保存 session 的操作。

sess.commit();

經過上面的代碼分析,我們看到了 koa-session 中間件的主流程以及保存操作。

那么 session 在什么時候被創建呢?回到上面提到的攔截器 extendContext ,它會在接到http請求的時候,從 ContextSession類 實例化出 session 對象。

也就是說,session 是中間件自己創建并管理的,并非由web服務器產生。

我們接著看核心功能 ContextSession

ContextSession類

先看構造函數:

constructor(ctx, opts) {
  this.ctx = ctx;
  this.app = ctx.app;
  this.opts = Object.assign({}, opts);
  this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store;
}

居然啥屁事都沒干。往下看 get() 方法:

get() {
  const session = this.session;
  // already retrieved
  if (session) return session;
  
  // unset
  if (session === false) return null;

  // cookie session store
  if (!this.store) this.initFromCookie();
  return this.session;
}

噢,原來是一個單例模式(等到使用時候再生成對象,多次調用會直接使用第一次的對象)。

這里有個判斷,是否傳入了 opts.store 參數,如果沒有則是用 initFromCookie() 來生成 session 對象。

那如果傳了 opts.store 呢,又啥都不干嗎,WTF?

顯然不是,還記得初始化里提到的那句 initFromExternal 函數調用么。

if (sess.store) await sess.initFromExternal();

所以,這里是根據是否有 opts.store ,來選擇兩種方式不同的生成 session 方式。

問:store是什么呢?

答:store可以在initFromExternal中看到,它其實是一個外部存儲。

問:什么外部存儲,存哪里的?

答:同學莫急,先往后看。

initFromCookie
initFromCookie() {
  const ctx = this.ctx;
  const opts = this.opts;

  const cookie = ctx.cookies.get(opts.key, opts);
  if (!cookie) {  
    this.create();
    return;
  }

  let json = opts.decode(cookie); // 打印json的話,會發現居然就是你的session對象!

  if (!this.valid(json)) {  // 判斷cookie過期等
    this.create();
    return;
  }

  this.create(json);
}

在這里,我們發現了一個很重要的信息,session 居然是加密后直接存在 cookie 中的。

我們 console.log 一下 json 變量,來驗證下:

initFromeExternal
async initFromExternal() {
  const ctx = this.ctx;
  const opts = this.opts;

  let externalKey;
  if (opts.externalKey) {
    externalKey = opts.externalKey.get(ctx);
  } else {
    externalKey = ctx.cookies.get(opts.key, opts);
  }


  if (!externalKey) {
    // create a new `externalKey`
    this.create();
    return;
  }

  const json = await this.store.get(externalKey, opts.maxAge, { rolling: opts.rolling });
  if (!this.valid(json, externalKey)) {
    // create a new `externalKey`
    this.create();
    return;
  }

  // create with original `externalKey`
  this.create(json, externalKey);
}

可以看到 store.get() ,有一串信息是存在 store 中,可以 get 到的。

而且也是在不斷地要求調用 create()

create

create()到底做了什么呢?

create(val, externalKey) {
  if (this.store) this.externalKey = externalKey || this.opts.genid();
  this.session = new Session(this, val);
}

它判斷了 store ,如果有 store ,就會設置上 externalKey ,或者生成一個隨機id。

基本可以看出,是在 sotre 中存儲一些信息,并且可以通過 externalKey 去用來獲取。

由此基本得出推斷,session 并不是服務器原生支持,而是由web服務程序自己創建管理。

存放在哪里呢?不一定要在服務器,可以像 koa-session 一樣騷氣地放在 cookie 中!

接著看最后一個 Session 類。

Session類

老規矩,先看構造函數:

constructor(sessionContext, obj) {
  this._sessCtx = sessionContext;
  this._ctx = sessionContext.ctx;
  if (!obj) {
    this.isNew = true;
  } else {
    for (const k in obj) {
      // restore maxAge from store
      if (k === "_maxAge") this._ctx.sessionOptions.maxAge = obj._maxAge;
      else if (k === "_session") this._ctx.sessionOptions.maxAge = "session";
      else this[k] = obj[k];
    }
  }
}

接收了 ContextSession 實例傳來 sessionContext 和 obj ,其他沒有做什么。

Session 類僅僅是用于存儲 session 的值,以及_maxAge,并且提供了toJSON方法用來獲取過濾了_maxAge等字段的,session對象的值。

session如何持久化保存

看完以上代碼,我們大致知道了 session 可以從外部或者 cookie 中取值,那它是如何保存的呢,我們回到 koa-session/index.js 中提到的 commit 方法,可以看到:

await next();

if (opts.autoCommit) {
  await sess.commit();
}

思路立馬就清晰了,它是在中間件結束 next() 后,進行了一次 commit()

commit()方法,可以在 lib/context.js 中找到:

async commit() {
  // ...省略n個判斷,包括是否有變更,是否需要刪除session等

  await this.save(changed);
}

再來看save()方法:

async save(changed) {
  const opts = this.opts;
  const key = opts.key;
  const externalKey = this.externalKey;
  let json = this.session.toJSON();

  // save to external store
  if (externalKey) {
    await this.store.set(externalKey, json, maxAge, {
      changed,
      rolling: opts.rolling,
    });
    if (opts.externalKey) {
      opts.externalKey.set(this.ctx, externalKey);
    } else {
      this.ctx.cookies.set(key, externalKey, opts);
    }
    return;
  }

  json = opts.encode(json);

  this.ctx.cookies.set(key, json, opts);
}

豁然開朗了,實際就是默認把數據 json ,塞進了 cookie ,即 cookie 來存儲加密后的 session 信息。

然后,如果設置了外部 store ,會調用 store.set() 去保存 session 。具體的保存邏輯,保存到哪里,由 store 對象自己決定!

小結

koa-session 的做法說明了,session 僅僅是一個對象信息,可以存到 cookie ,也可以存到任何地方(如內存,數據庫)。存到哪,可以開發者自己決定,只要實現一個 store 對象,提供 set,get 方法即可。

延伸擴展

通過以上源碼分析,我們已經得到了我們文章開頭那些疑問的答案。

koa-session 中還有哪些值得我們思考呢?

插件設計

不得不說,store 的插件式設計非常優秀。koa-session 不必關心數據具體是如何存儲的,只要插件提供它所需的存取方法。

這種插件式架構,反轉了模塊間的依賴關系,使得 koa-session 非常容易擴展。

koa-session對安全的考慮

這種默認把用戶信息存儲在 cookie 中的方式,始終是不安全的。

所以,現在我們知道使用的時候要做一些其他措施了。比如實現自己的 store ,把 session 存到 redis 等。

這種session的登錄方式,和token有什么區別呢

這其實要從 token 的使用方式來說了,用途會更靈活,這里就先不多說了。

后面會寫一下各種登錄策略的原理和比較,有興趣的同學可以關注我一下。

總結

回顧下文章開頭的幾個問題,我們已經有了明確的答案。

session 是一個概念,是一個數據對象,用來存儲訪問者的信息。

session 的存儲方式由開發者自己定義,可存于內存,redis,mysql,甚至是 cookie 中。

用戶第一次訪問的時候,我們就會給用戶創建一個他的 session ,并在 cookie 中塞一個他的 “鑰匙key” 。所以即使 http請求 是無狀態的,但通過 cookie 我們就可以拿到訪問者的 “鑰匙key” ,便可以從所有訪問者的 session 集合中取出對應訪問者的 session。

關閉瀏覽器,服務端的 session 是不會馬上過期的。session 中間件自己實現了一套管理方式,當訪問間隔超過 maxAge 的時候,session 便會失效。

那么除了 koa-session 這種方式來實現用戶登錄,還有其他方法嗎?

其實還有很多,可以存儲 cookie 實現,也可以用 token 方式。另外關于登錄還有單點登錄,第三方登錄等。如果大家有興趣,可以在后面的文章繼續給大家剖析。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100094.html

相關文章

  • koa-session中間件源碼學習cookie與session

    摘要:從中間件學習與原文鏈接關于和是什么網上有很多介紹,但是具體的用法自己事實上一直不是很清楚,通過中間件的源碼自己也算是對和大致搞明白了。對應于中間件,當我們沒有寫的時候,默認即利用實現。 從koa-session中間件學習cookie與session 原文鏈接 關于cookie和session是什么網上有很多介紹,但是具體的用法自己事實上一直不是很清楚,通過koa-session中間件的...

    Charles 評論0 收藏0
  • cookie?session?jwt

    cookie?session?jwt 寫在前面 PS:已經有很多文章寫過這些東西了,我寫的目的是為了自己的學習。所學只是為了更好地了解用戶登錄鑒權問題。 我們都知道HTTP是一個無狀態的協議 什么是無狀態? 用http協議進行兩臺計算機交互時,無論是服務器還是瀏覽器端,http協議只負責規定傳輸格式,你怎么傳輸,我怎么接受怎么返回。它并沒有記錄你上次訪問的內容,你上次傳遞的參數是什么,它不管的。 ...

    sarva 評論0 收藏0
  • 基于Node.js的微信小程序cookie解決方案

    摘要:踩過微信小程序坑的人都知道,微信小程序是不支持的。微信小程序采用的是獲取,通過開發者服務器端同微信服務器進行數據交互實現登錄。具體參考微信相關文檔,這里不贅述。而且萬一哪天微信小程序支持了呢,采用方式,還是和以前一樣操作數據。 ?????????踩過微信小程序坑的人都知道,微信小程序是不支持cookie的。微信小程序采用的是wx.login獲取code,通過開發者服務器端同微信服務器進...

    meteor199 評論0 收藏0
  • Node.js中Koa2如何使用Session完成登錄狀態保持?

    摘要:使用的中間件是一個簡潔的框架,把許多小功能都拆分成了中間件,用一個洋蔥模型保證了中間件豐富的可拓展性,我們要使用來保持登錄狀態,就需要引用中間件。默認是過期時間,以毫秒為單位計算。自動提交到響應頭。默認是是否在快過期時刷新的有效期。 項目要用到登錄注冊,就需要使用到Cookie和Session來保持登錄狀態,于是就簡單研究了一下 Cookie和Session的工作原理 前面已經專門發過...

    VincentFF 評論0 收藏0

發表評論

0條評論

remcarpediem

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<