From 65dc7a899ba72dd0851c35046562766d7f2b71b6 Mon Sep 17 00:00:00 2001 From: killa Date: Wed, 17 Aug 2022 00:16:17 +0800 Subject: [PATCH] feat: impl Host decorator (#48) --- core/controller-decorator/index.ts | 1 + .../src/decorator/http/Host.ts | 27 +++++++++ .../impl/http/HTTPControllerMetaBuilder.ts | 3 +- .../http/HTTPControllerMethodMetaBuilder.ts | 4 +- .../src/model/HTTPControllerMeta.ts | 10 ++++ .../src/model/HTTPMethodMeta.ts | 3 + .../src/util/ControllerInfoUtil.ts | 9 +++ .../src/util/MethodInfoUtil.ts | 11 ++++ .../test/fixtures/HostController.ts | 13 ++++ .../test/http/Host.test.ts | 16 +++++ plugin/controller/README.md | 20 +++++++ plugin/controller/lib/RootProtoManager.ts | 14 ++++- .../lib/impl/http/HTTPControllerRegister.ts | 15 ++++- .../lib/impl/http/HTTPMethodRegister.ts | 59 ++++++++++++++++--- .../app/controller/AppController.ts | 21 +++++++ .../app/controller/AppController2.ts | 20 +++++++ .../config/config.default.js | 18 ++++++ .../apps/host-controller-app/config/plugin.js | 16 +++++ .../apps/host-controller-app/package.json | 3 + .../app/controller/HostController1.ts | 20 +++++++ .../app/controller/HostController2.ts | 20 +++++++ plugin/controller/test/http/host.test.ts | 54 +++++++++++++++++ .../test/lib/HTTPMethodRegister.test.ts | 26 ++++++-- 23 files changed, 386 insertions(+), 17 deletions(-) create mode 100644 core/controller-decorator/src/decorator/http/Host.ts create mode 100644 core/controller-decorator/test/fixtures/HostController.ts create mode 100644 core/controller-decorator/test/http/Host.test.ts create mode 100644 plugin/controller/test/fixtures/apps/host-controller-app/app/controller/AppController.ts create mode 100644 plugin/controller/test/fixtures/apps/host-controller-app/app/controller/AppController2.ts create mode 100644 plugin/controller/test/fixtures/apps/host-controller-app/config/config.default.js create mode 100644 plugin/controller/test/fixtures/apps/host-controller-app/config/plugin.js create mode 100644 plugin/controller/test/fixtures/apps/host-controller-app/package.json create mode 100644 plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/HostController1.ts create mode 100644 plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/HostController2.ts create mode 100644 plugin/controller/test/http/host.test.ts diff --git a/core/controller-decorator/index.ts b/core/controller-decorator/index.ts index 7da82457..8492949c 100644 --- a/core/controller-decorator/index.ts +++ b/core/controller-decorator/index.ts @@ -7,6 +7,7 @@ export * from './src/decorator/Acl'; export * from './src/decorator/http/HTTPController'; export * from './src/decorator/http/HTTPMethod'; export * from './src/decorator/http/HTTPParam'; +export * from './src/decorator/http/Host'; export * from './src/builder/ControllerMetaBuilderFactory'; export * from './src/builder/ControllerMetaBuilder'; export * from './src/util/ControllerMetadataUtil'; diff --git a/core/controller-decorator/src/decorator/http/Host.ts b/core/controller-decorator/src/decorator/http/Host.ts new file mode 100644 index 00000000..58ac4c65 --- /dev/null +++ b/core/controller-decorator/src/decorator/http/Host.ts @@ -0,0 +1,27 @@ +import ControllerInfoUtil from '../../util/ControllerInfoUtil'; +import { EggProtoImplClass } from '@eggjs/core-decorator'; +import MethodInfoUtil from '../../util/MethodInfoUtil'; +import assert from 'assert'; + +export function Host(host: string) { + function classHost(constructor: EggProtoImplClass) { + ControllerInfoUtil.addControllerHost(host, constructor); + } + + function methodHOst(target: any, propertyKey: PropertyKey) { + assert(typeof propertyKey === 'string', + `[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`); + const controllerClazz = target.constructor as EggProtoImplClass; + const methodName = propertyKey as string; + + MethodInfoUtil.setMethodHost(host, controllerClazz, methodName); + } + + return function(target: any, propertyKey?: PropertyKey) { + if (propertyKey === undefined) { + classHost(target); + } else { + methodHOst(target, propertyKey); + } + }; +} diff --git a/core/controller-decorator/src/impl/http/HTTPControllerMetaBuilder.ts b/core/controller-decorator/src/impl/http/HTTPControllerMetaBuilder.ts index 40a32ffc..51acaf20 100644 --- a/core/controller-decorator/src/impl/http/HTTPControllerMetaBuilder.ts +++ b/core/controller-decorator/src/impl/http/HTTPControllerMetaBuilder.ts @@ -43,8 +43,9 @@ export class HTTPControllerMetaBuilder { const protoName = property!.name as string; const needAcl = ControllerInfoUtil.hasControllerAcl(this.clazz); const aclCode = ControllerInfoUtil.getControllerAcl(this.clazz); + const host = ControllerInfoUtil.getControllerHost(this.clazz); const metadata = new HTTPControllerMeta( - clazzName, protoName, controllerName, httpPath, httpMiddlewares, methods, needAcl, aclCode); + clazzName, protoName, controllerName, httpPath, httpMiddlewares, methods, needAcl, aclCode, host); ControllerMetadataUtil.setControllerMetadata(this.clazz, metadata); for (const method of metadata.methods) { const realPath = metadata.getMethodRealPath(method); diff --git a/core/controller-decorator/src/impl/http/HTTPControllerMethodMetaBuilder.ts b/core/controller-decorator/src/impl/http/HTTPControllerMethodMetaBuilder.ts index 4e8dc1f3..0ee8715b 100644 --- a/core/controller-decorator/src/impl/http/HTTPControllerMethodMetaBuilder.ts +++ b/core/controller-decorator/src/impl/http/HTTPControllerMethodMetaBuilder.ts @@ -105,11 +105,13 @@ export class HTTPControllerMethodMetaBuilder { const middlewares = MethodInfoUtil.getMethodMiddlewares(this.clazz, this.methodName); const needAcl = MethodInfoUtil.hasMethodAcl(this.clazz, this.methodName); const aclCode = MethodInfoUtil.getMethodAcl(this.clazz, this.methodName); + const host = MethodInfoUtil.getMethodHost(this.clazz, this.methodName); const realPath = parentPath ? path.posix.join(parentPath, httpPath) : httpPath; const paramTypeMap = this.buildParamType(realPath); const priority = this.getPriority(); - return new HTTPMethodMeta(this.methodName, httpPath!, httpMethod!, middlewares, contextIndex, paramTypeMap, priority, needAcl, aclCode); + return new HTTPMethodMeta( + this.methodName, httpPath!, httpMethod!, middlewares, contextIndex, paramTypeMap, priority, needAcl, aclCode, host); } } diff --git a/core/controller-decorator/src/model/HTTPControllerMeta.ts b/core/controller-decorator/src/model/HTTPControllerMeta.ts index 78fde4f5..a0786a4f 100644 --- a/core/controller-decorator/src/model/HTTPControllerMeta.ts +++ b/core/controller-decorator/src/model/HTTPControllerMeta.ts @@ -14,6 +14,7 @@ export class HTTPControllerMeta implements ControllerMetadata { public readonly methods: readonly HTTPMethodMeta[]; public readonly needAcl: boolean; public readonly aclCode?: string; + public readonly host?: string; constructor( className: string, @@ -24,6 +25,7 @@ export class HTTPControllerMeta implements ControllerMetadata { methods: HTTPMethodMeta[], needAcl: boolean, aclCode: string | undefined, + host: string | undefined, ) { this.protoName = protoName; this.controllerName = controllerName; @@ -33,6 +35,7 @@ export class HTTPControllerMeta implements ControllerMetadata { this.methods = methods; this.needAcl = needAcl; this.aclCode = aclCode; + this.host = host; } getMethodRealPath(method: HTTPMethodMeta) { @@ -42,6 +45,13 @@ export class HTTPControllerMeta implements ControllerMetadata { return method.path; } + getMethodHost(method: HTTPMethodMeta): string | undefined { + if (this.host) { + return this.host; + } + return method.host; + } + getMethodName(method: HTTPMethodMeta) { return `${method.method} ${this.controllerName}.${method.name}`; } diff --git a/core/controller-decorator/src/model/HTTPMethodMeta.ts b/core/controller-decorator/src/model/HTTPMethodMeta.ts index 5255e089..efc95e36 100644 --- a/core/controller-decorator/src/model/HTTPMethodMeta.ts +++ b/core/controller-decorator/src/model/HTTPMethodMeta.ts @@ -73,6 +73,7 @@ export class HTTPMethodMeta implements MethodMeta { public readonly priority: number; public readonly needAcL: boolean; public readonly aclCode: string | undefined; + public readonly host: string | undefined; constructor( name: string, @@ -84,6 +85,7 @@ export class HTTPMethodMeta implements MethodMeta { priority: number, needAcl: boolean, aclCode: string | undefined, + host: string | undefined, ) { this.name = name; this.path = path; @@ -94,6 +96,7 @@ export class HTTPMethodMeta implements MethodMeta { this.priority = priority; this.needAcL = needAcl; this.aclCode = aclCode; + this.host = host; } } diff --git a/core/controller-decorator/src/util/ControllerInfoUtil.ts b/core/controller-decorator/src/util/ControllerInfoUtil.ts index 470f4a53..f947557d 100644 --- a/core/controller-decorator/src/util/ControllerInfoUtil.ts +++ b/core/controller-decorator/src/util/ControllerInfoUtil.ts @@ -3,6 +3,7 @@ import { EggProtoImplClass, MetadataUtil } from '@eggjs/core-decorator'; export const CONTROLLER_TYPE = Symbol.for('EggPrototype#controllerType'); export const CONTROLLER_NAME = Symbol.for('EggPrototype#controllerName'); +export const CONTROLLER_HOST = Symbol.for('EggPrototype#controllerHost'); export const CONTROLLER_MIDDLEWARES = Symbol.for('EggPrototype#controller#middlewares'); export const CONTROLLER_ACL = Symbol.for('EggPrototype#controller#acl'); @@ -43,4 +44,12 @@ export default class ControllerInfoUtil { static getControllerAcl(clazz: EggProtoImplClass): string | undefined { return MetadataUtil.getMetaData(CONTROLLER_ACL, clazz); } + + static addControllerHost(host: string, clazz: EggProtoImplClass) { + MetadataUtil.defineMetaData(CONTROLLER_HOST, host, clazz); + } + + static getControllerHost(clazz: EggProtoImplClass): string | undefined { + return MetadataUtil.getMetaData(CONTROLLER_HOST, clazz); + } } diff --git a/core/controller-decorator/src/util/MethodInfoUtil.ts b/core/controller-decorator/src/util/MethodInfoUtil.ts index 990a5111..251204b2 100644 --- a/core/controller-decorator/src/util/MethodInfoUtil.ts +++ b/core/controller-decorator/src/util/MethodInfoUtil.ts @@ -3,6 +3,7 @@ import { ControllerTypeLike, MiddlewareFunc } from '../model'; import { MapUtil } from '@eggjs/tegg-common-util'; const METHOD_CONTROLLER_TYPE_MAP = Symbol.for('EggPrototype#controller#mthods'); +const METHOD_CONTROLLER_HOST = Symbol.for('EggPrototype#controller#mthods#host'); const METHOD_CONTEXT_INDEX = Symbol.for('EggPrototype#controller#method#context'); const METHOD_MIDDLEWARES = Symbol.for('EggPrototype#method#middlewares'); const METHOD_ACL = Symbol.for('EggPrototype#method#acl'); @@ -58,4 +59,14 @@ export default class MethodInfoUtil { const methodAclMap: MethodAclMap | undefined = MetadataUtil.getMetaData(METHOD_ACL, clazz); return methodAclMap?.get(methodName); } + + static setMethodHost(host: string, clazz: EggProtoImplClass, methodName: string) { + const methodControllerMap: METHOD_MAP = MetadataUtil.initOwnMapMetaData(METHOD_CONTROLLER_HOST, clazz, new Map()); + methodControllerMap.set(methodName, host); + } + + static getMethodHost(clazz: EggProtoImplClass, methodName: string): string | undefined { + const methodControllerMap: METHOD_MAP | undefined = MetadataUtil.getMetaData(METHOD_CONTROLLER_HOST, clazz); + return methodControllerMap?.get(methodName); + } } diff --git a/core/controller-decorator/test/fixtures/HostController.ts b/core/controller-decorator/test/fixtures/HostController.ts new file mode 100644 index 00000000..8afaa51c --- /dev/null +++ b/core/controller-decorator/test/fixtures/HostController.ts @@ -0,0 +1,13 @@ +import { Host } from '../../src/decorator/http/Host'; + +@Host('foo.eggjs.com') +export class HostController { + async hello(): Promise { + return; + } + + @Host('bar.eggjs.com') + async bar(): Promise { + return; + } +} diff --git a/core/controller-decorator/test/http/Host.test.ts b/core/controller-decorator/test/http/Host.test.ts new file mode 100644 index 00000000..966f41a2 --- /dev/null +++ b/core/controller-decorator/test/http/Host.test.ts @@ -0,0 +1,16 @@ +import assert from 'assert'; +import { HostController } from '../fixtures/HostController'; +import ControllerInfoUtil from '../../src/util/ControllerInfoUtil'; +import MethodInfoUtil from '../../src/util/MethodInfoUtil'; + +describe('test/Host.test.ts', () => { + it('controller Host work', () => { + const controllerHost = ControllerInfoUtil.getControllerHost(HostController); + assert(controllerHost === 'foo.eggjs.com'); + }); + + it('method Host work', () => { + const methodHost = MethodInfoUtil.getMethodHost(HostController, 'bar'); + assert(methodHost === 'bar.eggjs.com'); + }); +}); diff --git a/plugin/controller/README.md b/plugin/controller/README.md index 48c3dafb..8c61a2fb 100644 --- a/plugin/controller/README.md +++ b/plugin/controller/README.md @@ -193,3 +193,23 @@ export class FooController { // 具体 name 值可以查看 path-to-regexp @HTTPParam({ name: '0' }) id: string ``` + +### Host +Host 注解,用于指定 HTTP 方法仅在 host 匹配时执行。 +可以添加在类/方法上。添加在类上时,对类上所有方法生效,添加在方法上时,只对当前方法生效。方法上的注解可以覆盖类上的注解 + +```ts +// app/controller/FooController.ts +import { Host } from '@eggjs/tegg'; +@Host('foo.eggjs.com') +export class FooController { + // 仅能通过 foo.eggjs.com 访问 + async hello() { + } + + // 仅能通过 bar.eggjs.com 访问 + @Host('bar.eggjs.com') + async bar() { + } +} +``` diff --git a/plugin/controller/lib/RootProtoManager.ts b/plugin/controller/lib/RootProtoManager.ts index 5f8f4450..d4db89b6 100644 --- a/plugin/controller/lib/RootProtoManager.ts +++ b/plugin/controller/lib/RootProtoManager.ts @@ -8,12 +8,22 @@ export class RootProtoManager { // protoMap: Map = new Map(); - registerRootProto(method: string, cb: GetRootProtoCallback) { - const cbList = MapUtil.getOrStore(this.protoMap, method, []); + registerRootProto(method: string, host: string, cb: GetRootProtoCallback) { + const cbList = MapUtil.getOrStore(this.protoMap, method + host, []); cbList.push(cb); } getRootProto(ctx: EggContext): EggPrototype | undefined { + const hostCbList = this.protoMap.get(ctx.method + ctx.host); + if (hostCbList) { + for (const cb of hostCbList) { + const proto = cb(ctx); + if (proto) { + return proto; + } + } + } + const cbList = this.protoMap.get(ctx.method); if (!cbList) { return; diff --git a/plugin/controller/lib/impl/http/HTTPControllerRegister.ts b/plugin/controller/lib/impl/http/HTTPControllerRegister.ts index 0945b8e0..6778c5a9 100644 --- a/plugin/controller/lib/impl/http/HTTPControllerRegister.ts +++ b/plugin/controller/lib/impl/http/HTTPControllerRegister.ts @@ -18,6 +18,7 @@ export class HTTPControllerRegister implements ControllerRegister { static instance?: HTTPControllerRegister; private readonly router: KoaRouter; + private readonly checkRouters: Map>; private readonly eggContainerFactory: typeof EggContainerFactory; private controllerProtos: EggPrototype[] = []; @@ -32,6 +33,8 @@ export class HTTPControllerRegister implements ControllerRegister { constructor(router: KoaRouter, eggContainerFactory: typeof EggContainerFactory) { this.router = router; + this.checkRouters = new Map(); + this.checkRouters.set('default', router); this.eggContainerFactory = eggContainerFactory; } @@ -43,6 +46,7 @@ export class HTTPControllerRegister implements ControllerRegister { static clean() { if (this.instance) { this.instance.controllerProtos = []; + this.instance.checkRouters.clear(); } this.instance = undefined; } @@ -57,11 +61,20 @@ export class HTTPControllerRegister implements ControllerRegister { } const allMethods = Array.from(methodMap.keys()) .sort((a, b) => b.priority - a.priority); + + for (const method of allMethods) { + const controllerProto = methodMap.get(method)!; + const controllerMeta = controllerProto.getMetaData(CONTROLLER_META_DATA) as HTTPControllerMeta; + const methodRegister = new HTTPMethodRegister( + controllerProto, controllerMeta, method, this.router, this.checkRouters, this.eggContainerFactory); + methodRegister.checkDuplicate(); + } + for (const method of allMethods) { const controllerProto = methodMap.get(method)!; const controllerMeta = controllerProto.getMetaData(CONTROLLER_META_DATA) as HTTPControllerMeta; const methodRegister = new HTTPMethodRegister( - controllerProto, controllerMeta, method, this.router, this.eggContainerFactory); + controllerProto, controllerMeta, method, this.router, this.checkRouters, this.eggContainerFactory); methodRegister.register(rootProtoManager); } } diff --git a/plugin/controller/lib/impl/http/HTTPMethodRegister.ts b/plugin/controller/lib/impl/http/HTTPMethodRegister.ts index 2a60dc1d..17e7827b 100644 --- a/plugin/controller/lib/impl/http/HTTPMethodRegister.ts +++ b/plugin/controller/lib/impl/http/HTTPMethodRegister.ts @@ -6,6 +6,7 @@ import { HTTPControllerMeta, HTTPMethodMeta, HTTPParamType, + Next, PathParamMeta, QueriesParamMeta, QueryParamMeta, @@ -18,9 +19,15 @@ import pathToRegexp from 'path-to-regexp'; import { aclMiddlewareFactory } from './Acl'; import { RouterConflictError } from '../../errors'; import { FrameworkErrorFormater } from 'egg-errors'; +import { EggRouter } from '@eggjs/router'; + +const noop = () => { + // ... +}; export class HTTPMethodRegister { private readonly router: KoaRouter; + private readonly checkRouters: Map>; private readonly controllerMeta: HTTPControllerMeta; private readonly methodMeta: HTTPMethodMeta; private readonly proto: EggPrototype; @@ -31,22 +38,28 @@ export class HTTPMethodRegister { controllerMeta: HTTPControllerMeta, methodMeta: HTTPMethodMeta, router: KoaRouter, + checkRouters: Map>, eggContainerFactory: typeof EggContainerFactory, ) { this.proto = proto; this.controllerMeta = controllerMeta; this.router = router; this.methodMeta = methodMeta; + this.checkRouters = checkRouters; this.eggContainerFactory = eggContainerFactory; } - private createHandler(methodMeta: HTTPMethodMeta) { + private createHandler(methodMeta: HTTPMethodMeta, host: string | undefined) { const argsLength = methodMeta.paramMap.size; const hasContext = methodMeta.contextParamIndex !== undefined; const contextIndex = methodMeta.contextParamIndex; const methodArgsLength = argsLength + (hasContext ? 1 : 0); const self = this; - return async function(ctx: Context) { + return async function(ctx: Context, next: Next) { + // if hosts is not empty and host is not matched, not execute + if (host && host !== ctx.host) { + return await next(); + } // HTTP decorator core implement // use controller metadata map http request to function arguments const eggObj = self.eggContainerFactory.getEggObject(self.proto, self.proto.name, (ctx as any)[TEGG_CONTEXT]); @@ -99,25 +112,55 @@ export class HTTPMethodRegister { }; } - register(rootProtoManager: RootProtoManager) { - // 1. check if method + PATH has registered + checkDuplicate() { + // 1. check duplicate with egg controller + this.checkDuplicateInRouter(this.router); + + // 2. check duplicate with host tegg controller + let hostRouter; + const host = this.controllerMeta.getMethodHost(this.methodMeta); + if (host) { + hostRouter = this.checkRouters.get(host); + if (!hostRouter) { + hostRouter = new EggRouter({ sensitive: true }, {}); + this.checkRouters.set(host, hostRouter!); + } + } + if (hostRouter) { + this.checkDuplicateInRouter(hostRouter); + this.registerToRouter(hostRouter); + } + } + + private registerToRouter(router: KoaRouter) { + const routerFunc = router[this.methodMeta.method.toLowerCase()]; const methodRealPath = this.controllerMeta.getMethodRealPath(this.methodMeta); - const matched = this.router.match(methodRealPath, this.methodMeta.method); + const methodName = this.controllerMeta.getMethodName(this.methodMeta); + Reflect.apply(routerFunc, router, [ methodName, methodRealPath, noop ]); + } + + private checkDuplicateInRouter(router: KoaRouter) { + const methodRealPath = this.controllerMeta.getMethodRealPath(this.methodMeta); + const matched = router.match(methodRealPath, this.methodMeta.method); const methodName = this.controllerMeta.getMethodName(this.methodMeta); if (matched.route) { const [ layer ] = matched.path; const err = new RouterConflictError(`register http controller ${methodName} failed, ${this.methodMeta.method} ${methodRealPath} is conflict with exists rule ${layer.path}`); throw FrameworkErrorFormater.format(err); } + } - // 2. do register + register(rootProtoManager: RootProtoManager) { + const methodRealPath = this.controllerMeta.getMethodRealPath(this.methodMeta); + const methodName = this.controllerMeta.getMethodName(this.methodMeta); const routerFunc = this.router[this.methodMeta.method.toLowerCase()]; const methodMiddlewares = this.controllerMeta.getMethodMiddlewares(this.methodMeta); const aclMiddleware = aclMiddlewareFactory(this.controllerMeta, this.methodMeta); if (aclMiddleware) { methodMiddlewares.push(aclMiddleware); } - const handler = this.createHandler(this.methodMeta); + const host = this.controllerMeta.getMethodHost(this.methodMeta); + const handler = this.createHandler(this.methodMeta, host); Reflect.apply(routerFunc, this.router, [ methodName, methodRealPath, ...methodMiddlewares, handler ]); @@ -125,7 +168,7 @@ export class HTTPMethodRegister { const regExp = pathToRegexp(methodRealPath, { sensitive: true, }); - rootProtoManager.registerRootProto(this.methodMeta.method, (ctx: EggContext) => { + rootProtoManager.registerRootProto(this.methodMeta.method, host || '', (ctx: EggContext) => { if (regExp.test(ctx.path)) { return this.proto; } diff --git a/plugin/controller/test/fixtures/apps/host-controller-app/app/controller/AppController.ts b/plugin/controller/test/fixtures/apps/host-controller-app/app/controller/AppController.ts new file mode 100644 index 00000000..382790eb --- /dev/null +++ b/plugin/controller/test/fixtures/apps/host-controller-app/app/controller/AppController.ts @@ -0,0 +1,21 @@ +import { + HTTPController, + HTTPMethod, + HTTPMethodEnum, + Host, +} from '@eggjs/tegg'; + +@Host('foo.eggjs.com') +@HTTPController({ + controllerName: 'AppController', + path: '/apps', +}) +export class AppController { + @HTTPMethod({ + method: HTTPMethodEnum.GET, + path: '/:id', + }) + async get() { + return 'foo'; + } +} diff --git a/plugin/controller/test/fixtures/apps/host-controller-app/app/controller/AppController2.ts b/plugin/controller/test/fixtures/apps/host-controller-app/app/controller/AppController2.ts new file mode 100644 index 00000000..b915ce0d --- /dev/null +++ b/plugin/controller/test/fixtures/apps/host-controller-app/app/controller/AppController2.ts @@ -0,0 +1,20 @@ +import { + HTTPController, + HTTPMethod, + HTTPMethodEnum, + Host, +} from '@eggjs/tegg'; + +@Host('bar.eggjs.com') +@HTTPController({ + path: '/apps', +}) +export class AppController2 { + @HTTPMethod({ + method: HTTPMethodEnum.GET, + path: '/:id', + }) + async get() { + return 'bar'; + } +} diff --git a/plugin/controller/test/fixtures/apps/host-controller-app/config/config.default.js b/plugin/controller/test/fixtures/apps/host-controller-app/config/config.default.js new file mode 100644 index 00000000..0be9874a --- /dev/null +++ b/plugin/controller/test/fixtures/apps/host-controller-app/config/config.default.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports = function() { + const config = { + keys: 'test key', + security: { + csrf: { + ignoreJSON: false, + } + }, + }; + return config; +}; + +exports.teggConfig = { + package: '@eggjs/tegg-config', + enable: true, +}; diff --git a/plugin/controller/test/fixtures/apps/host-controller-app/config/plugin.js b/plugin/controller/test/fixtures/apps/host-controller-app/config/plugin.js new file mode 100644 index 00000000..828aebbf --- /dev/null +++ b/plugin/controller/test/fixtures/apps/host-controller-app/config/plugin.js @@ -0,0 +1,16 @@ +'use strict'; + +exports.tracer = { + package: 'egg-tracer', + enable: true, +}; + +exports.tegg = { + package: '@eggjs/tegg-plugin', + enable: true, +}; + +exports.teggConfig = { + package: '@eggjs/tegg-config', + enable: true, +}; diff --git a/plugin/controller/test/fixtures/apps/host-controller-app/package.json b/plugin/controller/test/fixtures/apps/host-controller-app/package.json new file mode 100644 index 00000000..3b566110 --- /dev/null +++ b/plugin/controller/test/fixtures/apps/host-controller-app/package.json @@ -0,0 +1,3 @@ +{ + "name": "controller-app" +} diff --git a/plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/HostController1.ts b/plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/HostController1.ts new file mode 100644 index 00000000..e7d5578b --- /dev/null +++ b/plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/HostController1.ts @@ -0,0 +1,20 @@ +import { + HTTPController, + HTTPMethod, + HTTPMethodEnum, + Host, +} from '@eggjs/tegg'; + +@HTTPController({ + path: '/foo', +}) +@Host('foo.eggjs.com') +export class AppController1 { + @HTTPMethod({ + method: HTTPMethodEnum.GET, + path: '/:id', + }) + async get() { + return 'hello'; + } +} diff --git a/plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/HostController2.ts b/plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/HostController2.ts new file mode 100644 index 00000000..7f3a73dd --- /dev/null +++ b/plugin/controller/test/fixtures/apps/http-conflict-app/app/controller/HostController2.ts @@ -0,0 +1,20 @@ +import { + HTTPController, + HTTPMethod, + HTTPMethodEnum, + Host, +} from '@eggjs/tegg'; + +@HTTPController({ + path: '/foo', +}) +@Host('foo.eggjs.com') +export class AppController2 { + @HTTPMethod({ + method: HTTPMethodEnum.GET, + path: '/:id', + }) + async get() { + return 'hello'; + } +} diff --git a/plugin/controller/test/http/host.test.ts b/plugin/controller/test/http/host.test.ts new file mode 100644 index 00000000..8f617817 --- /dev/null +++ b/plugin/controller/test/http/host.test.ts @@ -0,0 +1,54 @@ +import mm from 'egg-mock'; +import path from 'path'; +import assert from 'assert'; + +describe('test/host.test.ts', () => { + let app; + + beforeEach(() => { + mm(process.env, 'EGG_TYPESCRIPT', true); + }); + + afterEach(() => { + mm.restore(); + }); + + before(async () => { + mm(process.env, 'EGG_TYPESCRIPT', true); + mm(process, 'cwd', () => { + return path.join(__dirname, '../..'); + }); + app = mm.app({ + baseDir: path.join(__dirname, '../fixtures/apps/host-controller-app'), + framework: require.resolve('egg'), + }); + await app.ready(); + }); + + after(() => { + return app.close(); + }); + + it('global host should work', async () => { + app.mockCsrf(); + await app.httpRequest() + .get('/apps/1') + .set('host', 'foo.eggjs.com') + .expect(200) + .expect(res => { + console.log('res: ', res.text, res.body); + assert(res.text === 'foo'); + }); + }); + + it('method host should work', async () => { + app.mockCsrf(); + await app.httpRequest() + .get('/apps/2') + .set('host', 'bar.eggjs.com') + .expect(200) + .expect(res => { + assert(res.text === 'bar'); + }); + }); +}); diff --git a/plugin/controller/test/lib/HTTPMethodRegister.test.ts b/plugin/controller/test/lib/HTTPMethodRegister.test.ts index 2f409cf4..96c6ba79 100644 --- a/plugin/controller/test/lib/HTTPMethodRegister.test.ts +++ b/plugin/controller/test/lib/HTTPMethodRegister.test.ts @@ -54,8 +54,8 @@ describe('test/lib/HTTPControllerRegister.test.ts', () => { const controllerMeta = proto.getMetaData(CONTROLLER_META_DATA)!; await assert.rejects(async () => { for (const methodMeta of controllerMeta.methods) { - const register = new HTTPMethodRegister(proto, controllerMeta, methodMeta, router, EggContainerFactory); - await register.register(); + const register = new HTTPMethodRegister(proto, controllerMeta, methodMeta, router, new Map(), EggContainerFactory); + await register.checkDuplicate(); } }, /RouterConflictError: register http controller GET AppController.get failed, GET \/apps\/:id is conflict with exists rule \/apps\/:id/); }); @@ -68,10 +68,28 @@ describe('test/lib/HTTPControllerRegister.test.ts', () => { name: 'test', method: 'GET', path: '/123', - } as any, router, EggContainerFactory); + } as any, router, new Map(), EggContainerFactory); - await register.register(); + await register.checkDuplicate(); }, /RouterConflictError: register http controller GET AppController.test failed, GET \/apps\/123 is conflict with exists rule \/apps\/:id/); }); + + it('should throw error with same rule with host', async () => { + const proto1 = loadUnit.getEggPrototype('appController1', [])[0]; + const proto2 = loadUnit.getEggPrototype('appController2', [])[0]; + const controllerMeta1 = proto1.getMetaData(CONTROLLER_META_DATA)!; + const controllerMeta2 = proto2.getMetaData(CONTROLLER_META_DATA)!; + await assert.rejects(async () => { + const routerMap = new Map(); + for (const methodMeta of controllerMeta1.methods) { + const register = new HTTPMethodRegister(proto1, controllerMeta1, methodMeta, router, routerMap, EggContainerFactory); + await register.checkDuplicate(); + } + for (const methodMeta of controllerMeta2.methods) { + const register = new HTTPMethodRegister(proto2, controllerMeta2, methodMeta, router, routerMap, EggContainerFactory); + await register.checkDuplicate(); + } + }, /RouterConflictError: register http controller GET AppController2\.get failed, GET \/foo\/:id is conflict with exists rule \/foo\/:id/); + }); }); });