forked from koajs/session
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontext.js
More file actions
289 lines (265 loc) · 7.66 KB
/
Copy pathcontext.js
File metadata and controls
289 lines (265 loc) · 7.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
'use strict';
const debug = require('debug')('koa-session:context');
const Session = require('./session');
const util = require('./util');
const ONE_DAY = 24 * 60 * 60 * 1000;
//TODO externalKey是指在redis中的key
class ContextSession {
/**
* context session constructor
* @api public
*/
constructor(ctx, opts) {
this.ctx = ctx;
this.opts = opts || {};
this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store;
}
/**
* internal logic of `ctx.session`
* @return {Session} session object
*
* @api public
*/
//TODO 每个请求进入中间件,就会执行这个,用于得到或创建session
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;
}
/**
* internal logic of `ctx.session=`
* @param {Object} val session object
*
* @api public
*/
//TODO 设置session
set(val) {
if (val === null) {
this.session = false;
return;
}
if (typeof val === 'object') {
// use the original `externalKey` if exists to avoid waste storage
this.create(val, this.externalKey);
return;
}
throw new Error('this.session can only be set as null or an object.');
}
/**
* init session from external store
* will be called in the front of session middleware
*
* @api public
*/
//TODO 创建带缓存层的session
async initFromExternal() {
debug('init from external');
const ctx = this.ctx;
const opts = this.opts;
//TODO 得到缓存层的key,有缓存层时session就是个key
const externalKey = ctx.cookies.get(opts.key, opts);
debug('get external key from cookie %s', externalKey);
//TODO 如果不存在key
if (!externalKey) {
// create a new `externalKey`
this.create();
return;
}
const json = await this.store.get(externalKey, opts.maxAge, { rolling: opts.rolling });
//TODO 如果已经过期
if (!this.valid(json)) {
// create a new `externalKey`
this.create();
return;
}
// create with original `externalKey`
this.create(json, externalKey);
this.prevHash = util.hash(this.session.toJSON());
}
/**
* init session from cookie
* @api private
*/
//TODO 创建没有缓存层的session
initFromCookie() {
debug('init from cookie');
const ctx = this.ctx;
const opts = this.opts;
//TODO 没有缓存层的session包含了所有数据,opts只是为了signed参数
const cookie = ctx.cookies.get(opts.key, opts);
//TODO 第一次请求没有session那就创建
if (!cookie) {
this.create();
return;
}
let json;
debug('parse %s', cookie);
try {
//TODO 解码得到数据
json = opts.decode(cookie);
} catch (err) {
// backwards compatibility:
// create a new session if parsing fails.
// new Buffer(string, 'base64') does not seem to crash
// when `string` is not base64-encoded.
// but `JSON.parse(string)` will crash.
debug('decode %j error: %s', cookie, err);
if (!(err instanceof SyntaxError)) {
//TODO 删除session
// clean this cookie to ensure next request won't throw again
ctx.cookies.set(opts.key, '', opts);
// ctx.onerror will unset all headers, and set those specified in err
err.headers = {
'set-cookie': ctx.response.get('set-cookie'),
};
throw err;
}
this.create();
return;
}
debug('parsed %j', json);
//TODO 验证是否过期
if (!this.valid(json)) {
this.create();
return;
}
//TODO 创建带数据的session
// support access `ctx.session` before session middleware
this.create(json);
this.prevHash = util.hash(this.session.toJSON());
}
/**
* verify session(expired or )
* @param {Object} json session object
* @return {Boolean} valid
* @api private
*/
//TODO 验证session是否过期
valid(json) {
if (!json) return false;
//TODO 如果session过期
if (json._expire && json._expire < Date.now()) {
debug('expired session');
return false;
}
//TODO 用户自己的验证规则
const valid = this.opts.valid;
if (typeof valid === 'function' && !valid(this.ctx, json)) {
// valid session value fail, ignore this session
debug('invalid session');
return false;
}
return true;
}
/**
* create a new session and attach to ctx.sess
*
* @param {Object} [val] session data
* @param {String} [externalKey] session external key
* @api private
*/
//TODO 创建session,没有参数时创建空session
create(val, externalKey) {
debug('create session with val: %j externalKey: %s', val, externalKey);
//TODO 如果使用缓存层,就添加缓存的key
if (this.store) this.externalKey = externalKey || this.opts.genid();
//TODO 创建session
this.session = new Session(this.ctx, val);
}
/**
* Commit the session changes or removal.
*
* @api public
*/
//TODO save的hook
async commit() {
const session = this.session;
const prevHash = this.prevHash;
const opts = this.opts;
const ctx = this.ctx;
// not accessed
if (undefined === session) return;
// removed
if (session === false) {
await this.remove();
return;
}
// force save session when `session._requireSave` set
let changed = true;
if (!session._requireSave) {
const json = session.toJSON();
// do nothing if new and not populated
if (!prevHash && !Object.keys(json).length) return;
changed = prevHash !== util.hash(json);
// do nothing if not changed and not in rolling mode
if (!this.opts.rolling && !changed) return;
}
if (typeof opts.beforeSave === 'function') {
debug('before save');
opts.beforeSave(ctx, session);
}
await this.save(changed);
}
/**
* remove session
* @api private
*/
//TODO 移除session
async remove() {
const opts = this.opts;
const ctx = this.ctx;
const key = opts.key;
const externalKey = this.externalKey;
//TODO 如果在缓存层当中就把缓存层的删除
if (externalKey) await this.store.destroy(externalKey);
//TODO cookie设置为空
ctx.cookies.set(key, '', opts);
}
/**
* save session
* @api private
*/
//TODO 把session返回浏览器
async save(changed) {
const opts = this.opts;
const key = opts.key;
const externalKey = this.externalKey;
let json = this.session.toJSON();
// set expire for check
//TODO 设置默认的过期水煎
const maxAge = opts.maxAge ? opts.maxAge : ONE_DAY;
//TODO 过期时间是session那就是maxAge = undefined
if (maxAge === 'session') {
// do not set _expire in json if maxAge is set to 'session'
// also delete maxAge from options
opts.maxAge = undefined;
} else {
// set expire for check
json._expire = maxAge + Date.now();
json._maxAge = maxAge;
}
//TODO 有缓存层的话就保存key
// save to external store
if (externalKey) {
debug('save %j to external key %s', json, externalKey);
await this.store.set(externalKey, json, maxAge, {
changed,
rolling: opts.rolling,
});
this.ctx.cookies.set(key, externalKey, opts);
return;
}
//TODO 没有缓存层就保存数据
// save to cookie
debug('save %j to cookie', json);
json = opts.encode(json);
debug('save %s', json);
this.ctx.cookies.set(key, json, opts);
}
}
module.exports = ContextSession;