perf: lazy init cookies and ip when first time use it (#1216)

This commit is contained in:
Yiyu He 2018-07-11 11:18:39 +08:00 committed by fengmk2
parent 74170caf0b
commit 162a5b3e78
5 changed files with 134 additions and 66 deletions

View file

@ -14,7 +14,6 @@ const isJSON = require('koa-is-json');
const context = require('./context'); const context = require('./context');
const request = require('./request'); const request = require('./request');
const statuses = require('statuses'); const statuses = require('statuses');
const Cookies = require('cookies');
const Emitter = require('events'); const Emitter = require('events');
const util = require('util'); const util = require('util');
const Stream = require('stream'); const Stream = require('stream');
@ -169,11 +168,6 @@ module.exports = class Application extends Emitter {
request.response = response; request.response = response;
response.request = request; response.request = request;
context.originalUrl = request.originalUrl = req.url; context.originalUrl = request.originalUrl = req.url;
context.cookies = new Cookies(req, res, {
keys: this.keys,
secure: request.secure
});
request.ip = request.ips[0] || req.socket.remoteAddress || '';
context.state = {}; context.state = {};
return context; return context;
} }

View file

@ -10,6 +10,9 @@ const createError = require('http-errors');
const httpAssert = require('http-assert'); const httpAssert = require('http-assert');
const delegate = require('delegates'); const delegate = require('delegates');
const statuses = require('statuses'); const statuses = require('statuses');
const Cookies = require('cookies');
const COOKIES = Symbol('context#cookies');
/** /**
* Context prototype. * Context prototype.
@ -151,6 +154,20 @@ const proto = module.exports = {
this.status = err.status; this.status = err.status;
this.length = Buffer.byteLength(msg); this.length = Buffer.byteLength(msg);
this.res.end(msg); this.res.end(msg);
},
get cookies() {
if (!this[COOKIES]) {
this[COOKIES] = new Cookies(this.req, this.res, {
keys: this.app.keys,
secure: this.request.secure
});
}
return this[COOKIES];
},
set cookies(_cookies) {
this[COOKIES] = _cookies;
} }
}; };

View file

@ -17,6 +17,8 @@ const fresh = require('fresh');
const only = require('only'); const only = require('only');
const util = require('util'); const util = require('util');
const IP = Symbol('context#ip');
/** /**
* Prototype. * Prototype.
*/ */
@ -439,6 +441,26 @@ module.exports = {
: []; : [];
}, },
/**
* Return request's remote address
* When `app.proxy` is `true`, parse
* the "X-Forwarded-For" ip address list and return the first one
*
* @return {String}
* @api public
*/
get ip() {
if (!this[IP]) {
this[IP] = this.ips[0] || this.socket.remoteAddress || '';
}
return this[IP];
},
set ip(_ip) {
this[IP] = _ip;
},
/** /**
* Return subdomains as an array. * Return subdomains as an array.
* *

View file

@ -5,51 +5,13 @@ const assert = require('assert');
const request = require('supertest'); const request = require('supertest');
const Koa = require('../..'); const Koa = require('../..');
describe('ctx.cookies.set()', () => { describe('ctx.cookies', () => {
it('should set an unsigned cookie', async () => { describe('ctx.cookies.set()', () => {
const app = new Koa(); it('should set an unsigned cookie', async () => {
app.use((ctx, next) => {
ctx.cookies.set('name', 'jon');
ctx.status = 204;
});
const server = app.listen();
const res = await request(server)
.get('/')
.expect(204);
const cookie = res.headers['set-cookie'].some(cookie => /^name=/.test(cookie));
assert.equal(cookie, true);
});
describe('with .signed', () => {
describe('when no .keys are set', () => {
it('should error', () => {
const app = new Koa();
app.use((ctx, next) => {
try {
ctx.cookies.set('foo', 'bar', { signed: true });
} catch (err) {
ctx.body = err.message;
}
});
return request(app.callback())
.get('/')
.expect('.keys required for signed cookies');
});
});
it('should send a signed cookie', async () => {
const app = new Koa(); const app = new Koa();
app.keys = ['a', 'b'];
app.use((ctx, next) => { app.use((ctx, next) => {
ctx.cookies.set('name', 'jon', { signed: true }); ctx.cookies.set('name', 'jon');
ctx.status = 204; ctx.status = 204;
}); });
@ -59,36 +21,99 @@ describe('ctx.cookies.set()', () => {
.get('/') .get('/')
.expect(204); .expect(204);
const cookies = res.headers['set-cookie']; const cookie = res.headers['set-cookie'].some(cookie => /^name=/.test(cookie));
assert.equal(cookie, true);
});
assert.equal(cookies.some(cookie => /^name=/.test(cookie)), true); describe('with .signed', () => {
assert.equal(cookies.some(cookie => /(,|^)name\.sig=/.test(cookie)), true); describe('when no .keys are set', () => {
it('should error', () => {
const app = new Koa();
app.use((ctx, next) => {
try {
ctx.cookies.set('foo', 'bar', { signed: true });
} catch (err) {
ctx.body = err.message;
}
});
return request(app.callback())
.get('/')
.expect('.keys required for signed cookies');
});
});
it('should send a signed cookie', async () => {
const app = new Koa();
app.keys = ['a', 'b'];
app.use((ctx, next) => {
ctx.cookies.set('name', 'jon', { signed: true });
ctx.status = 204;
});
const server = app.listen();
const res = await request(server)
.get('/')
.expect(204);
const cookies = res.headers['set-cookie'];
assert.equal(cookies.some(cookie => /^name=/.test(cookie)), true);
assert.equal(cookies.some(cookie => /(,|^)name\.sig=/.test(cookie)), true);
});
});
describe('with secure', () => {
it('should get secure from request', async () => {
const app = new Koa();
app.proxy = true;
app.keys = ['a', 'b'];
app.use(ctx => {
ctx.cookies.set('name', 'jon', { signed: true });
ctx.status = 204;
});
const server = app.listen();
const res = await request(server)
.get('/')
.set('x-forwarded-proto', 'https') // mock secure
.expect(204);
const cookies = res.headers['set-cookie'];
assert.equal(cookies.some(cookie => /^name=/.test(cookie)), true);
assert.equal(cookies.some(cookie => /(,|^)name\.sig=/.test(cookie)), true);
assert.equal(cookies.every(cookie => /secure/.test(cookie)), true);
});
}); });
}); });
describe('with secure', () => { describe('ctx.cookies=', () => {
it('should get secure from request', async () => { it('should override cookie work', async () => {
const app = new Koa(); const app = new Koa();
app.proxy = true; app.use((ctx, next) => {
app.keys = ['a', 'b']; ctx.cookies = {
set(key, value){
app.use(ctx => { ctx.set(key, value);
ctx.cookies.set('name', 'jon', { signed: true }); }
};
ctx.cookies.set('name', 'jon');
ctx.status = 204; ctx.status = 204;
}); });
const server = app.listen(); const server = app.listen();
const res = await request(server) await request(server)
.get('/') .get('/')
.set('x-forwarded-proto', 'https') // mock secure .expect('name', 'jon')
.expect(204); .expect(204);
const cookies = res.headers['set-cookie'];
assert.equal(cookies.some(cookie => /^name=/.test(cookie)), true);
assert.equal(cookies.some(cookie => /(,|^)name\.sig=/.test(cookie)), true);
assert.equal(cookies.every(cookie => /secure/.test(cookie)), true);
}); });
}); });
}); });

View file

@ -39,11 +39,21 @@ describe('req.ip', () => {
}); });
}); });
it('should be cached', () => { it('should be lazy inited and cached', () => {
const req = { socket: new Stream.Duplex() }; const req = { socket: new Stream.Duplex() };
req.socket.remoteAddress = '127.0.0.2'; req.socket.remoteAddress = '127.0.0.2';
const request = Request(req); const request = Request(req);
assert.equal(request.ip, '127.0.0.2');
req.socket.remoteAddress = '127.0.0.1'; req.socket.remoteAddress = '127.0.0.1';
assert.equal(request.ip, '127.0.0.2'); assert.equal(request.ip, '127.0.0.2');
}); });
it('should reset ip work', () => {
const req = { socket: new Stream.Duplex() };
req.socket.remoteAddress = '127.0.0.2';
const request = Request(req);
assert.equal(request.ip, '127.0.0.2');
request.ip = '127.0.0.1';
assert.equal(request.ip, '127.0.0.1');
});
}); });