From 558ecf35b8689af4ee84d4704f6aae63f4bcbaed Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Sat, 17 Jun 2017 00:17:08 +0800 Subject: [PATCH] feat: support rolling (#82) * feat: support rolling * f * feat: pass options to store --- Readme.md | 6 ++++-- lib/context.js | 17 +++++++++++------ test/cookie.test.js | 40 ++++++++++++++++++++++++++++++++++++++++ test/store.test.js | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index 74bda5b..3c2c043 100644 --- a/Readme.md +++ b/Readme.md @@ -54,7 +54,9 @@ var CONFIG = { overwrite: true, /** (boolean) can overwrite or not (default true) */ httpOnly: true, /** (boolean) httpOnly or not (default true) */ signed: true, /** (boolean) signed or not (default true) */ + rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. default is false **/ }; + app.use(session(CONFIG, app)); // or if you prefer all default config, just use => app.use(session(app)); @@ -111,8 +113,8 @@ app.use(convert(session(app))); You can store the session content in external stores(redis, mongodb or other DBs) by pass `options.store` with three methods(need to be generator function or async function): - - `get(key)`: get session object by key - - `set(key, sess, maxAge)`: set session object for key, with a `maxAge` (in ms) + - `get(key, maxAge, { rolling })`: get session object by key + - `set(key, sess, maxAge, { rolling, changed })`: set session object for key, with a `maxAge` (in ms) - `destroy(key)`: destroy session for key diff --git a/lib/context.js b/lib/context.js index ecd78e6..4f5f93e 100644 --- a/lib/context.js +++ b/lib/context.js @@ -79,7 +79,7 @@ class ContextSession { return; } - const json = yield this.store.get(externalKey); + const json = yield this.store.get(externalKey, opts.maxAge, { rolling: opts.rolling }); if (!this.valid(json)) { // create a new `externalKey` this.create(); @@ -203,19 +203,21 @@ class ContextSession { } // 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; - // do nothing if not changed - if (prevHash === util.hash(json)) 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); } - yield this.save(); + yield this.save(changed); } /** @@ -238,7 +240,7 @@ class ContextSession { * @api private */ - * save() { + * save(changed) { const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; @@ -258,7 +260,10 @@ class ContextSession { // save to external store if (externalKey) { debug('save %j to external key %s', json, externalKey); - yield this.store.set(externalKey, json, maxAge); + yield this.store.set(externalKey, json, maxAge, { + changed, + rolling: opts.rolling, + }); this.ctx.cookies.set(key, externalKey, opts); return; } diff --git a/test/cookie.test.js b/test/cookie.test.js index 56cfcf2..d306f26 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -755,6 +755,46 @@ describe('Koa Session Cookie', () => { }); }); }); + + describe('when rolling set to true', () => { + let app; + before(() => { + app = App({ rolling: true }); + + app.use(function* () { + console.log(this.path); + if (this.path === '/set') this.session = { foo: 'bar' }; + this.body = this.session; + }); + }); + + it('should not send set-cookie when session not exists', () => { + return request(app.callback()) + .get('/') + .expect({}) + .expect(res => { + should.not.exist(res.headers['set-cookie']); + }); + }); + + it('should send set-cookie when session exists and not change', done => { + request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }) + .end((err, res) => { + should.not.exist(err); + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect(res => { + res.headers['set-cookie'].should.have.length(2); + }) + .expect({ foo: 'bar' }, done); + }); + }); + }); }); function App(options) { diff --git a/test/store.test.js b/test/store.test.js index 85e588a..7d58df5 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -628,6 +628,44 @@ describe('Koa Session External Store', () => { }); }); + describe('when rolling set to true', () => { + let app; + before(() => { + app = App({ rolling: true }); + + app.use(function* () { + if (this.path === '/set') this.session = { foo: 'bar' }; + this.body = this.session; + }); + }); + + it('should not send set-cookie when session not exists', () => { + return request(app.callback()) + .get('/') + .expect({}) + .expect(res => { + should.not.exist(res.headers['set-cookie']); + }); + }); + + it('should send set-cookie when session exists and not change', done => { + request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }) + .end((err, res) => { + should.not.exist(err); + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect(res => { + res.headers['set-cookie'].should.have.length(2); + }) + .expect({ foo: 'bar' }, done); + }); + }); + }); }); function App(options) {