diff --git a/demo/src/index.ts b/demo/src/index.ts index 9d9e281..764d511 100644 --- a/demo/src/index.ts +++ b/demo/src/index.ts @@ -36,8 +36,9 @@ const server = new Server({ assetsRoot: Config.publicDir }); -routerMap.get('/', async (ctx, next) => { - ctx.body = ''; +routerMap.get('/test-multi', async (ctx, next) => { + ctx.body = 'hi~'; + next && await next(ctx); next && await next(ctx); }); diff --git a/lerna.json b/lerna.json index 6cb6834..2c30a7e 100644 --- a/lerna.json +++ b/lerna.json @@ -8,5 +8,6 @@ "ignoreChanges": ["ignored-file", "*.md"], "message": "chore(release): publish", "registry": "https://registry.npmjs.org" - } + }, + "npmClient": "yarn" } diff --git a/packages/auf-typing/package.json b/packages/auf-typing/package.json index 7bb9cef..9743415 100644 --- a/packages/auf-typing/package.json +++ b/packages/auf-typing/package.json @@ -34,5 +34,8 @@ "rimraf": "^3.0.2", "typescript": "^4.6.2" }, - "gitHead": "3f74b38e9dc2a875da3de1a32d4c045acb975d7b" + "gitHead": "3f74b38e9dc2a875da3de1a32d4c045acb975d7b", + "resolutions": { + "@types/node": "15.6.2" + } } diff --git a/packages/auf-typing/src/index.ts b/packages/auf-typing/src/index.ts index ec5e43f..872bd06 100644 --- a/packages/auf-typing/src/index.ts +++ b/packages/auf-typing/src/index.ts @@ -9,12 +9,18 @@ export interface IServerOptions { errorHandler?: (e: Error) => void; } +export type ContextExtendInfoType = { + visMap: WeakSet; + handled: boolean; + [key: string]: any; +} + export interface IContext { req: http.IncomingMessage; res: http.ServerResponse; serverOptions: IServerOptions; body: string | Readable | Record; - extendInfo: Record; + extendInfo: ContextExtendInfoType; get reqBody(): Record; get query(): Record; get params(): Record; diff --git a/packages/auf/src/index.ts b/packages/auf/src/index.ts index 16c5dec..839006a 100644 --- a/packages/auf/src/index.ts +++ b/packages/auf/src/index.ts @@ -1,12 +1,4 @@ -export { - Server, - ClusterServer, - Router, - RouterMapFactory, - IClusterServerOptions, - CommonError, - isCommonError -} from '@vergiss/auf-core'; +export * from '@vergiss/auf-core'; export * as Middlewares from '@vergiss/auf-middlewares'; export { IContext, IMiddleWare, IServerOptions } from '@vergiss/auf-typing'; diff --git a/packages/core/package.json b/packages/core/package.json index 8b98530..73283fc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -39,5 +39,8 @@ "dependencies": { "@vergiss/auf-helpers": "^0.3.0" }, - "gitHead": "3f74b38e9dc2a875da3de1a32d4c045acb975d7b" + "gitHead": "3f74b38e9dc2a875da3de1a32d4c045acb975d7b", + "resolutions": { + "@types/node": "15.6.2" + } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7f37b59..6a76b24 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,13 +1,4 @@ -import { ClusterServer, Server, IClusterServerOptions } from './server'; -import { Router, RouterMapFactory } from './router'; +export { ClusterServer, Server, IClusterServerOptions } from './server'; +export { Router, RouterMapFactory } from './router'; -export { CommonError, isCommonError } from '@vergiss/auf-helpers'; -export { - Server, - ClusterServer, - Router, - RouterMapFactory, - - // declarations - IClusterServerOptions, -} \ No newline at end of file +export { CommonError, isCommonError, throwCommonError } from '@vergiss/auf-helpers'; \ No newline at end of file diff --git a/packages/core/src/router/prefix-tree/index.ts b/packages/core/src/router/prefix-tree/index.ts index 055ad61..35aa1a5 100644 --- a/packages/core/src/router/prefix-tree/index.ts +++ b/packages/core/src/router/prefix-tree/index.ts @@ -1,5 +1,5 @@ -import { CommonError } from '@vergiss/auf-helpers'; +import { throwCommonError } from '@vergiss/auf-helpers'; import { NTreeNode, RouterRegExpLeafNode, @@ -78,7 +78,7 @@ class NTree implements IRouterTree { const matchedRegExpNodes = regExpNodes.filter(node => node.exp.test(url)); if (matchedRegExpNodes.length === 0) { - throw new CommonError({ + throwCommonError({ message: 'Route not defined', statusCode: 404, statusMessage: 'Not Found' @@ -143,11 +143,11 @@ class NTree implements IRouterTree { const leafNode = p.quickMap.get(LEAF_SIGN); if (leafNode == null) { - throw new CommonError({ + throwCommonError({ message: 'Handler not defined', statusCode: 500, statusMessage: 'Handler Not Found' - }); + }) } res = (leafNode as RouterTreeLeafNode).value; diff --git a/packages/core/src/server/base-server.ts b/packages/core/src/server/base-server.ts index ad79ed4..09a88e1 100644 --- a/packages/core/src/server/base-server.ts +++ b/packages/core/src/server/base-server.ts @@ -13,10 +13,10 @@ class BaseServer implements IBaseServer { const { port } = this.options; http.createServer(async (_, res) => { const ctx = new Context(_, res, this.options); - if (handleContext) { - await handleContext(ctx); - } try { + if (handleContext) { + await handleContext(ctx); + } if (ctx.body == null) { res.end(''); return; diff --git a/packages/core/src/server/context.ts b/packages/core/src/server/context.ts index c76cee4..6fd71be 100644 --- a/packages/core/src/server/context.ts +++ b/packages/core/src/server/context.ts @@ -1,18 +1,21 @@ import * as http from 'http'; -import { IContext, IServerOptions } from '@vergiss/auf-typing'; +import { IContext, IServerOptions, ContextExtendInfoType } from '@vergiss/auf-typing'; class Context implements IContext { public res: http.ServerResponse; public req: http.IncomingMessage; public serverOptions: IServerOptions; - public extendInfo: any; + public extendInfo: ContextExtendInfoType; private _body: any; constructor (req: http.IncomingMessage, res: http.ServerResponse, serverOptions: IServerOptions) { this.res = res; this.req = req; this.serverOptions = serverOptions; this._body = {} as any; - this.extendInfo = {} as any; + this.extendInfo = { + visMap: new WeakSet(), + handled: false + }; } get body() { diff --git a/packages/core/src/server/interface.ts b/packages/core/src/server/interface.ts index 90598b6..714b556 100644 --- a/packages/core/src/server/interface.ts +++ b/packages/core/src/server/interface.ts @@ -16,6 +16,6 @@ export interface IClusterServerOptions extends IServerOptions { } export interface IMiddlewareAbility { - next(ctx: IContext): Promise; + next(ctx: IContext): void; applyMiddleware(middlewares: IMiddleWare[]): void } \ No newline at end of file diff --git a/packages/core/src/server/middlewares.ts b/packages/core/src/server/middlewares.ts index 3540fc5..be9a6e5 100644 --- a/packages/core/src/server/middlewares.ts +++ b/packages/core/src/server/middlewares.ts @@ -2,31 +2,54 @@ import { compose } from '@vergiss/auf-helpers'; import { IContext, IMiddleWare } from '@vergiss/auf-typing'; import { IMiddlewareAbility } from './interface'; +/** + * Check if a middleware is called multiple times. It has side effect. + * @param fn The middleware function + * @param ctx Context object + */ +function checkIfMultipleVisited (fn: IMiddleWare, ctx: IContext) { + if (ctx.extendInfo.visMap.has(fn)) { + throw new Error('The "next" method can\'t be called multiple times.'); + } + ctx.extendInfo.visMap.add(fn); +} + +/** + * Returns a resolved promise as the middlewares chain's tail. + * @param ctx Context object + */ +function wrapResolvedPromise (ctx: IContext) { + checkIfMultipleVisited(wrapResolvedPromise, ctx); + return Promise.resolve(); +} + class Middlewares { - public middlewares: Function[]; - constructor (middlewares: Function[]) { + private middlewares: Array; + private wrappedMiddlewares: Array<(fn?: IMiddleWare) => void>; + constructor (middlewares: IMiddleWare[]) { this.middlewares = middlewares } - applyMiddlewares(): (ctx: IContext) => Promise { - this.middlewares = this.middlewares.map(fn => { - return function (next = (() => Promise.resolve())) { + getMiddlewareChain(): (ctx: IContext) => void { + this.wrappedMiddlewares = this.middlewares.map(fn => { + return function (next = wrapResolvedPromise) { return async function (ctx: IContext) { + checkIfMultipleVisited(fn, ctx); await fn(ctx, next); } } - }) - const [lastActionGenerator, ...restActionGenerators] = this.middlewares.reverse(); + }); + const [lastActionGenerator, ...restActionGenerators] = this.wrappedMiddlewares.reverse(); return compose(restActionGenerators)(lastActionGenerator()); } } export class MiddlewaresAbility implements IMiddlewareAbility { - next: (ctx: IContext) => Promise; + next: (ctx: IContext) => void; private middleware: Middlewares; applyMiddleware(middlewares: IMiddleWare[]): void { this.middleware = new Middlewares(middlewares); - this.next = this.middleware.applyMiddlewares(); + this.next = this.middleware.getMiddlewareChain(); } } \ No newline at end of file diff --git a/packages/helpers/package.json b/packages/helpers/package.json index d866a35..32005ae 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -37,5 +37,8 @@ "ts-node": "^10.7.0", "typescript": "^4.3.2" }, - "gitHead": "3f74b38e9dc2a875da3de1a32d4c045acb975d7b" + "gitHead": "3f74b38e9dc2a875da3de1a32d4c045acb975d7b", + "resolutions": { + "@types/node": "15.6.2" + } } diff --git a/packages/helpers/src/error.ts b/packages/helpers/src/error.ts index 97483d0..ef14a9e 100644 --- a/packages/helpers/src/error.ts +++ b/packages/helpers/src/error.ts @@ -17,10 +17,15 @@ class CommonError extends Error { } } +function throwCommonError(options: ICommonErrorOptions): never { + throw new CommonError(options); +} + function isCommonError (value: any): value is CommonError { return value.type === commonErrorSymbol; } export { CommonError, - isCommonError + isCommonError, + throwCommonError } \ No newline at end of file diff --git a/packages/helpers/src/index.ts b/packages/helpers/src/index.ts index eaee46d..18bcc41 100644 --- a/packages/helpers/src/index.ts +++ b/packages/helpers/src/index.ts @@ -1,7 +1,7 @@ -export { compose, composePromise } from './functional'; -export { checkMimeTypes } from './mime-type'; -export { uuid } from './tools'; -export { getMD5 } from './md5'; -export { LRUCache } from './lru-cache'; -export { CommonError, isCommonError } from './error'; +export * from './functional'; +export * from './mime-type'; +export * from './tools'; +export * from './md5'; +export * from './lru-cache'; +export * from './error'; diff --git a/packages/middlewares/package.json b/packages/middlewares/package.json index a8927d2..7959fde 100644 --- a/packages/middlewares/package.json +++ b/packages/middlewares/package.json @@ -38,5 +38,8 @@ "@vergiss/auf-helpers": "^0.3.0", "@vergiss/auf-template-engine": "^0.4.0" }, - "gitHead": "3f74b38e9dc2a875da3de1a32d4c045acb975d7b" + "gitHead": "3f74b38e9dc2a875da3de1a32d4c045acb975d7b", + "resolutions": { + "@types/node": "15.6.2" + } } diff --git a/packages/template-engine/package.json b/packages/template-engine/package.json index f430cd8..365c31e 100644 --- a/packages/template-engine/package.json +++ b/packages/template-engine/package.json @@ -25,5 +25,8 @@ "ts-node": "^10.7.0", "typescript": "^4.3.2" }, - "gitHead": "3f74b38e9dc2a875da3de1a32d4c045acb975d7b" + "gitHead": "3f74b38e9dc2a875da3de1a32d4c045acb975d7b", + "resolutions": { + "@types/node": "15.6.2" + } }