'use strict';
|
|
const delegate = require('delegates');
|
const { assign } = require('utility');
|
const eggUtils = require('egg-core').utils;
|
|
const HELPER = Symbol('Context#helper');
|
const LOCALS = Symbol('Context#locals');
|
const LOCALS_LIST = Symbol('Context#localsList');
|
const COOKIES = Symbol('Context#cookies');
|
const CONTEXT_LOGGERS = Symbol('Context#logger');
|
const CONTEXT_HTTPCLIENT = Symbol('Context#httpclient');
|
const CONTEXT_ROUTER = Symbol('Context#router');
|
|
const proto = module.exports = {
|
|
/**
|
* Get the current visitor's cookies.
|
*/
|
get cookies() {
|
if (!this[COOKIES]) {
|
this[COOKIES] = new this.app.ContextCookies(this, this.app.keys);
|
}
|
return this[COOKIES];
|
},
|
|
/**
|
* Get a wrapper httpclient instance contain ctx in the hold request process
|
*
|
* @return {ContextHttpClient} the wrapper httpclient instance
|
*/
|
get httpclient() {
|
if (!this[CONTEXT_HTTPCLIENT]) {
|
this[CONTEXT_HTTPCLIENT] = new this.app.ContextHttpClient(this);
|
}
|
return this[CONTEXT_HTTPCLIENT];
|
},
|
|
/**
|
* Shortcut for httpclient.curl
|
*
|
* @function Context#curl
|
* @param {String|Object} url - request url address.
|
* @param {Object} [options] - options for request.
|
* @return {Object} see {@link ContextHttpClient#curl}
|
*/
|
curl(url, options) {
|
return this.httpclient.curl(url, options);
|
},
|
|
/**
|
* Alias to {@link Application#router}
|
*
|
* @member {Router} Context#router
|
* @since 1.0.0
|
* @example
|
* ```js
|
* this.router.pathFor('post', { id: 12 });
|
* ```
|
*/
|
get router() {
|
if (!this[CONTEXT_ROUTER]) {
|
this[CONTEXT_ROUTER] = this.app.router;
|
}
|
return this[CONTEXT_ROUTER];
|
},
|
|
/**
|
* Set router to Context, only use on EggRouter
|
* @param {EggRouter} val router instance
|
*/
|
set router(val) {
|
this[CONTEXT_ROUTER] = val;
|
},
|
|
/**
|
* Get helper instance from {@link Application#Helper}
|
*
|
* @member {Helper} Context#helper
|
* @since 1.0.0
|
*/
|
get helper() {
|
if (!this[HELPER]) {
|
this[HELPER] = new this.app.Helper(this);
|
}
|
return this[HELPER];
|
},
|
|
/**
|
* Wrap app.loggers with context infomation,
|
* if a custom logger is defined by naming aLogger, then you can `ctx.getLogger('aLogger')`
|
*
|
* @param {String} name - logger name
|
* @return {Logger} logger
|
*/
|
getLogger(name) {
|
let cache = this[CONTEXT_LOGGERS];
|
if (!cache) {
|
cache = this[CONTEXT_LOGGERS] = {};
|
}
|
|
// read from cache
|
if (cache[name]) return cache[name];
|
|
// get no exist logger
|
const appLogger = this.app.getLogger(name);
|
if (!appLogger) return null;
|
|
// write to cache
|
cache[name] = new this.app.ContextLogger(this, appLogger);
|
return cache[name];
|
},
|
|
/**
|
* Logger for Application, wrapping app.coreLogger with context infomation
|
*
|
* @member {ContextLogger} Context#logger
|
* @since 1.0.0
|
* @example
|
* ```js
|
* this.logger.info('some request data: %j', this.request.body);
|
* this.logger.warn('WARNING!!!!');
|
* ```
|
*/
|
get logger() {
|
return this.getLogger('logger');
|
},
|
|
/**
|
* Logger for frameworks and plugins,
|
* wrapping app.coreLogger with context infomation
|
*
|
* @member {ContextLogger} Context#coreLogger
|
* @since 1.0.0
|
*/
|
get coreLogger() {
|
return this.getLogger('coreLogger');
|
},
|
|
/**
|
* locals is an object for view, you can use `app.locals` and `ctx.locals` to set variables,
|
* which will be used as data when view is rendering.
|
* The difference between `app.locals` and `ctx.locals` is the context level, `app.locals` is global level, and `ctx.locals` is request level. when you get `ctx.locals`, it will merge `app.locals`.
|
*
|
* when you set locals, only object is available
|
*
|
* ```js
|
* this.locals = {
|
* a: 1
|
* };
|
* this.locals = {
|
* b: 1
|
* };
|
* this.locals.c = 1;
|
* console.log(this.locals);
|
* {
|
* a: 1,
|
* b: 1,
|
* c: 1,
|
* };
|
* ```
|
*
|
* `ctx.locals` has cache, it only merges `app.locals` once in one request.
|
*
|
* @member {Object} Context#locals
|
*/
|
get locals() {
|
if (!this[LOCALS]) {
|
this[LOCALS] = assign({}, this.app.locals);
|
}
|
if (this[LOCALS_LIST] && this[LOCALS_LIST].length) {
|
assign(this[LOCALS], this[LOCALS_LIST]);
|
this[LOCALS_LIST] = null;
|
}
|
return this[LOCALS];
|
},
|
|
set locals(val) {
|
if (!this[LOCALS_LIST]) {
|
this[LOCALS_LIST] = [];
|
}
|
this[LOCALS_LIST].push(val);
|
},
|
|
/**
|
* alias to {@link Context#locals}, compatible with koa that use this variable
|
* @member {Object} state
|
* @see Context#locals
|
*/
|
get state() {
|
return this.locals;
|
},
|
|
set state(val) {
|
this.locals = val;
|
},
|
|
/**
|
* Run async function in the background
|
* @param {Function} scope - the first args is ctx
|
* ```js
|
* this.body = 'hi';
|
*
|
* this.runInBackground(async ctx => {
|
* await ctx.mysql.query(sql);
|
* await ctx.curl(url);
|
* });
|
* ```
|
*/
|
runInBackground(scope) {
|
// try to use custom function name first
|
/* istanbul ignore next */
|
const taskName = scope._name || scope.name || eggUtils.getCalleeFromStack(true);
|
scope._name = taskName;
|
this._runInBackground(scope);
|
},
|
|
// let plugins or frameworks to reuse _runInBackground in some cases.
|
// e.g.: https://github.com/eggjs/egg-mock/pull/78
|
_runInBackground(scope) {
|
const ctx = this;
|
const start = Date.now();
|
/* istanbul ignore next */
|
const taskName = scope._name || scope.name || eggUtils.getCalleeFromStack(true);
|
// use app.toAsyncFunction to support both generator function and async function
|
return ctx.app.toAsyncFunction(scope)(ctx)
|
.then(() => {
|
ctx.coreLogger.info('[egg:background] task:%s success (%dms)', taskName, Date.now() - start);
|
})
|
.catch(err => {
|
// background task process log
|
ctx.coreLogger.info('[egg:background] task:%s fail (%dms)', taskName, Date.now() - start);
|
|
// emit error when promise catch, and set err.runInBackground flag
|
err.runInBackground = true;
|
ctx.app.emit('error', err, ctx);
|
});
|
},
|
};
|
|
/**
|
* Context delegation.
|
*/
|
|
delegate(proto, 'request')
|
/**
|
* @member {Boolean} Context#acceptJSON
|
* @see Request#acceptJSON
|
* @since 1.0.0
|
*/
|
.getter('acceptJSON')
|
/**
|
* @member {Array} Context#queries
|
* @see Request#queries
|
* @since 1.0.0
|
*/
|
.getter('queries')
|
/**
|
* @member {Boolean} Context#accept
|
* @see Request#accept
|
* @since 1.0.0
|
*/
|
.getter('accept')
|
/**
|
* @member {string} Context#ip
|
* @see Request#ip
|
* @since 1.0.0
|
*/
|
.access('ip');
|
|
delegate(proto, 'response')
|
/**
|
* @member {Number} Context#realStatus
|
* @see Response#realStatus
|
* @since 1.0.0
|
*/
|
.access('realStatus');
|