context.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. 'use strict';
  2. const { performance } = require('perf_hooks');
  3. const delegate = require('delegates');
  4. const { assign } = require('utility');
  5. const eggUtils = require('egg-core').utils;
  6. const HELPER = Symbol('Context#helper');
  7. const LOCALS = Symbol('Context#locals');
  8. const LOCALS_LIST = Symbol('Context#localsList');
  9. const COOKIES = Symbol('Context#cookies');
  10. const CONTEXT_LOGGERS = Symbol('Context#logger');
  11. const CONTEXT_HTTPCLIENT = Symbol('Context#httpclient');
  12. const CONTEXT_ROUTER = Symbol('Context#router');
  13. const proto = module.exports = {
  14. /**
  15. * Get the current visitor's cookies.
  16. */
  17. get cookies() {
  18. if (!this[COOKIES]) {
  19. this[COOKIES] = new this.app.ContextCookies(this, this.app.keys, this.app.config.cookies);
  20. }
  21. return this[COOKIES];
  22. },
  23. /**
  24. * Get a wrapper httpclient instance contain ctx in the hold request process
  25. *
  26. * @return {ContextHttpClient} the wrapper httpclient instance
  27. */
  28. get httpclient() {
  29. if (!this[CONTEXT_HTTPCLIENT]) {
  30. this[CONTEXT_HTTPCLIENT] = new this.app.ContextHttpClient(this);
  31. }
  32. return this[CONTEXT_HTTPCLIENT];
  33. },
  34. /**
  35. * Shortcut for httpclient.curl
  36. *
  37. * @function Context#curl
  38. * @param {String|Object} url - request url address.
  39. * @param {Object} [options] - options for request.
  40. * @return {Object} see {@link ContextHttpClient#curl}
  41. */
  42. curl(url, options) {
  43. return this.httpclient.curl(url, options);
  44. },
  45. /**
  46. * Alias to {@link Application#router}
  47. *
  48. * @member {Router} Context#router
  49. * @since 1.0.0
  50. * @example
  51. * ```js
  52. * this.router.pathFor('post', { id: 12 });
  53. * ```
  54. */
  55. get router() {
  56. if (!this[CONTEXT_ROUTER]) {
  57. this[CONTEXT_ROUTER] = this.app.router;
  58. }
  59. return this[CONTEXT_ROUTER];
  60. },
  61. /**
  62. * Set router to Context, only use on EggRouter
  63. * @param {EggRouter} val router instance
  64. */
  65. set router(val) {
  66. this[CONTEXT_ROUTER] = val;
  67. },
  68. /**
  69. * Get helper instance from {@link Application#Helper}
  70. *
  71. * @member {Helper} Context#helper
  72. * @since 1.0.0
  73. */
  74. get helper() {
  75. if (!this[HELPER]) {
  76. this[HELPER] = new this.app.Helper(this);
  77. }
  78. return this[HELPER];
  79. },
  80. /**
  81. * Wrap app.loggers with context infomation,
  82. * if a custom logger is defined by naming aLogger, then you can `ctx.getLogger('aLogger')`
  83. *
  84. * @param {String} name - logger name
  85. * @return {Logger} logger
  86. */
  87. getLogger(name) {
  88. let cache = this[CONTEXT_LOGGERS];
  89. if (!cache) {
  90. cache = this[CONTEXT_LOGGERS] = {};
  91. }
  92. // read from cache
  93. if (cache[name]) return cache[name];
  94. // get no exist logger
  95. const appLogger = this.app.getLogger(name);
  96. if (!appLogger) return null;
  97. // write to cache
  98. cache[name] = new this.app.ContextLogger(this, appLogger);
  99. return cache[name];
  100. },
  101. /**
  102. * Logger for Application, wrapping app.coreLogger with context infomation
  103. *
  104. * @member {ContextLogger} Context#logger
  105. * @since 1.0.0
  106. * @example
  107. * ```js
  108. * this.logger.info('some request data: %j', this.request.body);
  109. * this.logger.warn('WARNING!!!!');
  110. * ```
  111. */
  112. get logger() {
  113. return this.getLogger('logger');
  114. },
  115. /**
  116. * Logger for frameworks and plugins,
  117. * wrapping app.coreLogger with context infomation
  118. *
  119. * @member {ContextLogger} Context#coreLogger
  120. * @since 1.0.0
  121. */
  122. get coreLogger() {
  123. return this.getLogger('coreLogger');
  124. },
  125. /**
  126. * locals is an object for view, you can use `app.locals` and `ctx.locals` to set variables,
  127. * which will be used as data when view is rendering.
  128. * 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`.
  129. *
  130. * when you set locals, only object is available
  131. *
  132. * ```js
  133. * this.locals = {
  134. * a: 1
  135. * };
  136. * this.locals = {
  137. * b: 1
  138. * };
  139. * this.locals.c = 1;
  140. * console.log(this.locals);
  141. * {
  142. * a: 1,
  143. * b: 1,
  144. * c: 1,
  145. * };
  146. * ```
  147. *
  148. * `ctx.locals` has cache, it only merges `app.locals` once in one request.
  149. *
  150. * @member {Object} Context#locals
  151. */
  152. get locals() {
  153. if (!this[LOCALS]) {
  154. this[LOCALS] = assign({}, this.app.locals);
  155. }
  156. if (this[LOCALS_LIST] && this[LOCALS_LIST].length) {
  157. assign(this[LOCALS], this[LOCALS_LIST]);
  158. this[LOCALS_LIST] = null;
  159. }
  160. return this[LOCALS];
  161. },
  162. set locals(val) {
  163. if (!this[LOCALS_LIST]) {
  164. this[LOCALS_LIST] = [];
  165. }
  166. this[LOCALS_LIST].push(val);
  167. },
  168. /**
  169. * alias to {@link Context#locals}, compatible with koa that use this variable
  170. * @member {Object} state
  171. * @see Context#locals
  172. */
  173. get state() {
  174. return this.locals;
  175. },
  176. set state(val) {
  177. this.locals = val;
  178. },
  179. /**
  180. * Run async function in the background
  181. * @param {Function} scope - the first args is ctx
  182. * ```js
  183. * this.body = 'hi';
  184. *
  185. * this.runInBackground(async ctx => {
  186. * await ctx.mysql.query(sql);
  187. * await ctx.curl(url);
  188. * });
  189. * ```
  190. */
  191. runInBackground(scope) {
  192. // try to use custom function name first
  193. /* istanbul ignore next */
  194. const taskName = scope._name || scope.name || eggUtils.getCalleeFromStack(true);
  195. scope._name = taskName;
  196. this._runInBackground(scope);
  197. },
  198. // let plugins or frameworks to reuse _runInBackground in some cases.
  199. // e.g.: https://github.com/eggjs/egg-mock/pull/78
  200. _runInBackground(scope) {
  201. const ctx = this;
  202. const start = performance.now();
  203. /* istanbul ignore next */
  204. const taskName = scope._name || scope.name || eggUtils.getCalleeFromStack(true);
  205. // use setImmediate to ensure all sync logic will run async
  206. return new Promise(resolve => setImmediate(resolve))
  207. // use app.toAsyncFunction to support both generator function and async function
  208. .then(() => ctx.app.toAsyncFunction(scope)(ctx))
  209. .then(() => {
  210. ctx.coreLogger.info('[egg:background] task:%s success (%dms)',
  211. taskName, Math.floor((performance.now() - start) * 1000) / 1000);
  212. })
  213. .catch(err => {
  214. // background task process log
  215. ctx.coreLogger.info('[egg:background] task:%s fail (%dms)',
  216. taskName, Math.floor((performance.now() - start) * 1000) / 1000);
  217. // emit error when promise catch, and set err.runInBackground flag
  218. err.runInBackground = true;
  219. ctx.app.emit('error', err, ctx);
  220. });
  221. },
  222. };
  223. /**
  224. * Context delegation.
  225. */
  226. delegate(proto, 'request')
  227. /**
  228. * @member {Boolean} Context#acceptJSON
  229. * @see Request#acceptJSON
  230. * @since 1.0.0
  231. */
  232. .getter('acceptJSON')
  233. /**
  234. * @member {Array} Context#queries
  235. * @see Request#queries
  236. * @since 1.0.0
  237. */
  238. .getter('queries')
  239. /**
  240. * @member {Boolean} Context#accept
  241. * @see Request#accept
  242. * @since 1.0.0
  243. */
  244. .getter('accept')
  245. /**
  246. * @member {string} Context#ip
  247. * @see Request#ip
  248. * @since 1.0.0
  249. */
  250. .access('ip');
  251. delegate(proto, 'response')
  252. /**
  253. * @member {Number} Context#realStatus
  254. * @see Response#realStatus
  255. * @since 1.0.0
  256. */
  257. .access('realStatus');