diff --git a/.gitignore b/.gitignore index 750cef5..155f053 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ build/Release # Dependency directories node_modules jspm_packages +package-lock.json # Optional npm cache directory .npm diff --git a/.vscode/settings.json b/.vscode/settings.json index eccc2aa..f9efc50 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,5 +26,12 @@ "url": "./schema/services.json" } ], - "vsicons.presets.angular": false + "vsicons.presets.angular": false, + "cSpell.words": [ + "Proxable", + "Remoting", + "chevrotain" + ], + "typescript.extension.sortImports.quoteStyle": "double", + "typescript.extension.sortImports.maxNamedImportsInSingleLine": 6 } \ No newline at end of file diff --git a/cSpell.json b/cSpell.json index da5b14f..fddec80 100644 --- a/cSpell.json +++ b/cSpell.json @@ -1,16 +1,12 @@ -// cSpell Settings { - // Version of the setting file. Always 0.1 "version": "0.1", - // language - current active spelling language "language": "en", - // words - list of words to be always considered correct "words": [ - "kanro" + "chevrotain", + "kanro", + "Proxable", + "Remoting" ], - // flagWords - list of words to be always considered incorrect - // This is useful for offensive words and common spelling errors. - // For example "hte" should be "the" "flagWords": [ "hte" ] diff --git a/config/banner b/config/banner new file mode 100644 index 0000000..2dafb96 --- /dev/null +++ b/config/banner @@ -0,0 +1,13 @@ + + + 8 8888 ,88' .8. b. 8 8 888888888o. ,o888888o. + 8 8888 ,88' .888. 888o. 8 8 8888 `88. . 8888 `88. + 8 8888 ,88' :88888. Y88888o. 8 8 8888 `88 ,8 8888 `8b + 8 8888 ,88' . `88888. .`Y888888o. 8 8 8888 ,88 88 8888 `8b + 8 8888 ,88' .8. `88888. 8o. `Y888888o. 8 8 8888. ,88' 88 8888 88 + 8 8888 88' .8`8. `88888. 8`Y8o. `Y88888o8 8 888888888P' 88 8888 88 + 8 888888< .8' `8. `88888. 8 `Y8o. `Y8888 8 8888`8b 88 8888 ,8P + 8 8888 `Y8. .8' `8. `88888. 8 `Y8o. `Y8 8 8888 `8b. `8 8888 ,8P + 8 8888 `Y8. .888888888. `88888. 8 `Y8o.` 8 8888 `8b. ` 8888 ,88' + 8 8888 `Y8. .8' `8. `88888. 8 `Yo 8 8888 `88. `8888888P' + diff --git a/config/kanro.json b/config/kanro.json index 0943e3d..2fa50ff 100644 --- a/config/kanro.json +++ b/config/kanro.json @@ -4,7 +4,7 @@ "registry": "http://localhost:4873", "resource": "./resource", "entryPoint": { - "name": "Router", + "name": "UrlRouter", "module": { "name": "kanro", "version": "*" @@ -40,6 +40,38 @@ } } }, + "/remote": { + "name": "RemoteServiceHolder", + "module": { + "name": "kanro", + "version": "*" + }, + "dependencies": { + "target": { + "name": "TestService", + "module": { + "name": "test", + "version": "*" + } + } + } + }, + "/test": { + "name": "TestResponder", + "module": { + "name": "test", + "version": "*" + }, + "dependencies": { + "target": { + "name": "TestService", + "module": { + "name": "test", + "version": "*" + } + } + } + }, "exceptionHandlers": [ { "name": "HttpExceptionRenderer", diff --git a/package.json b/package.json index 444c22d..16dd37b 100644 --- a/package.json +++ b/package.json @@ -11,19 +11,18 @@ "license": "MIT", "devDependencies": { "@types/file-type": "^0.0.2", - "@types/node": "^7.0.5", + "@types/node": "^10.5.2", "@types/npm": "^2.0.28", - "@types/parseurl": "^1.3.0", - "@types/request": "^0.0.45" + "@types/parseurl": "^1.3.0" }, "dependencies": { "ajv": "^4.11.4", + "chevrotain": "^3.7.0", "file-type": "^4.1.0", "mime-types": "^2.1.15", - "npm": "^4.3.0", + "npm": "^6.1.0", "parseurl": "^1.3.1", - "read-chunk": "^2.0.0", - "request": "^2.81.0" + "read-chunk": "^2.0.0" }, "repository": { "type": "github", diff --git a/src/AppLogger.ts b/src/AppLogger.ts deleted file mode 100644 index 62b5b46..0000000 --- a/src/AppLogger.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { LoggerManager } from "./LoggerManager"; -import { AnsiStyle, Colors, ILogger } from "./Logging/index"; - -export let AppLogger = LoggerManager.current.registerLogger("App", AnsiStyle.create().foreground(Colors.magenta)); \ No newline at end of file diff --git a/src/Application.ts b/src/Application.ts index 36cdc7c..6d2b16b 100644 --- a/src/Application.ts +++ b/src/Application.ts @@ -1,22 +1,41 @@ -import * as Http from "http"; import * as Cluster from "cluster"; +import * as Http from "http"; import * as OS from "os"; - -import { RequestMirror, Request, Response } from "./Http"; -import { NotFoundException, NonstandardNodeException, NodeNotSupportedException } from "./Exceptions"; -import { UnexpectedNodeException } from "./Exceptions/UnexpectedNodeException"; -import { LoggerManager } from "./LoggerManager"; -import { ModuleManager } from "./ModuleManager"; -import { INodeContainer, Node, RequestHandler, RequestDiverter, RequestReplicator, Responder, ResponseHandler, ExceptionHandler, Fuse, Module } from "./Core"; -import { IAppConfig } from "./IAppConfig"; -import { Colors, Style, AnsiStyle, LogLevel, Logger } from "./Logging"; +import { AnsiStyle, Colors, ILogger, Logger, LogLevel, Style } from "./Logging"; import { ConfigBuilder } from "./ConfigBuilder"; +import { + ExceptionHandler, + Fuse, + IModuleInfo, + INodeContainer, + Module, + Node, + RequestDiverter, + RequestHandler, + RequestReplicator, + Responder, + ResponseHandler, + Service +} from "./Core"; +import { File, Path } from "./IO"; +import { HttpServer } from "./HttpServer"; +import { IAppConfig } from "./IAppConfig"; +import { KanroInternalModule } from "./KanroInternalModule"; import { KanroModule } from "./KanroModule"; -import { AppLogger } from "./AppLogger"; +import { LoggerManager } from "./LoggerManager"; import { Master } from "./Cluster/Master"; +import { ModuleManager } from "./ModuleManager"; +import { NodeHandler } from "./NodeHandler"; +import { NodeNotSupportedException, NonstandardNodeException, NotFoundException } from "./Exceptions"; +import { NpmClient } from "./NpmClient"; +import { ObjectUtils } from "./Utils"; +import { Request, RequestContext, RequestMirror, Response } from "./Http"; +import { UnexpectedNodeException } from "./Exceptions/UnexpectedNodeException"; +import { version } from "punycode"; import { Worker } from "./Cluster/Worker"; -let clusterLogger = LoggerManager.current.registerLogger("Cluster", AnsiStyle.create().foreground(Colors.cyan)); + +let projectDir: string = Path.resolve(__dirname, ".."); export enum HttpMethod { get = Colors.green, @@ -26,101 +45,182 @@ export enum HttpMethod { patch = Colors.yellow } -export class Application { - private httpServer: Http.Server; +export class Application extends Service { + dependencies = { + loggerManager: { + name: LoggerManager.name, + module: KanroInternalModule.moduleInfo + }, + configBuilder: { + name: ConfigBuilder.name, + module: KanroInternalModule.moduleInfo + }, + moduleManager: { + name: ModuleManager.name, + module: KanroInternalModule.moduleInfo + }, + httpServer: { + name: HttpServer.name, + module: KanroInternalModule.moduleInfo + }, + npmClient: { + name: NpmClient.name, + module: KanroInternalModule.moduleInfo + } + }; + + constructor(config?: IAppConfig, localModules: { module: Module, name: string, version: string }[] = []) { + super(undefined); + this.configMeta = config; + this.localModules = localModules; + } - public die(error: Error, module: String) { - let stackInfo = error.stack; + private isBooted: boolean = false; + private configMeta: IAppConfig; + private runtimeContext: IAppConfig; + private localModules: { module: Module, name: string, version: string }[]; + private configBuilder: ConfigBuilder; + private moduleManager: ModuleManager; + private npmClient: NpmClient; + private httpServer: HttpServer; + + async onLoaded(): Promise { + this.configBuilder = await this.getDependedService("configBuilder"); + this.moduleManager = await this.getDependedService("moduleManager"); + this.npmClient = await this.getDependedService("npmClient"); + this.httpServer = await this.getDependedService("httpServer"); + + this.appLogger = await (await this.getDependedService("loggerManager")) + .registerLogger("App", AnsiStyle.create().foreground(Colors.magenta)); + this.clusterLogger = await (await this.getDependedService("loggerManager")) + .registerLogger("Cluster", AnsiStyle.create().foreground(Colors.cyan)); + } + private appLogger: ILogger; + private clusterLogger: ILogger; + + die(error: Error, module: String): void { + let stackInfo: string = error.stack; - while (error['innerException'] != undefined) { - error = error['innerException']; + while (error.hasOwnProperty("innerException")) { + // tslint:disable-next-line:no-string-literal + error = error["innerException"]; stackInfo += `\n With inner exception '${error.name}'\n ${error.stack}`; } - AppLogger.error(`A catastrophic failure occurred in 'Kanro:${module}'\n ${stackInfo}`) + this.appLogger.error(`A catastrophic failure occurred in 'Kanro:${module}'\n ${stackInfo}`); process.exit(-1); } - private helloKanro() { - console.log(""); - console.log(""); - console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${" 8 8888 ,88' .8. b. 8 8 888888888o. ,o888888o. "}`); - console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${" 8 8888 ,88' .888. 888o. 8 8 8888 `88. . 8888 `88. "}`); - console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${" 8 8888 ,88' :88888. Y88888o. 8 8 8888 `88 ,8 8888 `8b "}`); - console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${" 8 8888 ,88' . `88888. .`Y888888o. 8 8 8888 ,88 88 8888 `8b "}`); - console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${" 8 8888 ,88' .8. `88888. 8o. `Y888888o. 8 8 8888. ,88' 88 8888 88 "}`); - console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${" 8 8888 88' .8`8. `88888. 8`Y8o. `Y88888o8 8 888888888P' 88 8888 88 "}`); - console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${" 8 888888< .8' `8. `88888. 8 `Y8o. `Y8888 8 8888`8b 88 8888 ,8P "}`); - console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${" 8 8888 `Y8. .8' `8. `88888. 8 `Y8o. `Y8 8 8888 `8b. `8 8888 ,8P "}`); - console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${" 8 8888 `Y8. .888888888. `88888. 8 `Y8o.` 8 8888 `8b. ` 8888 ,88' "}`); - console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${" 8 8888 `Y8. .8' `8. `88888. 8 `Yo 8 8888 `88. `8888888P' "}`); - console.log(""); - console.log(""); - } + readonly isProxable: boolean = false; - async run(config?: IAppConfig, localModules: { module: Module, name: string, version: string }[] = []) { - try { - if (Cluster.isMaster) { - this.helloKanro(); - AppLogger.info("Booting..."); - - AppLogger.info("Create application context..."); - await ConfigBuilder.initialize(); - - if (config == undefined) { - config = await ConfigBuilder.readConfig(config); - } + private async helloKanro(): Promise { + let bannerFile: string = `${projectDir}/config/banner`; + if (await File.exists(`${process.cwd()}/banner`)) { + bannerFile = `${process.cwd()}/banner`; + } - await Master.current.run(config, localModules); - } - else { - await Worker.current.run(config, localModules); - } + let banner: string[] = (await File.readFile(bannerFile)).toString().replace("\r\n", "\n").replace("\r", "\n").split("\n"); + for (const line of banner) { + console.log(Style`${AnsiStyle.create().foreground(Colors.blue)}${line}`); + } + } + async run(): Promise { + try { + await this.boot(); } catch (error) { this.die(error, "App"); } } - async reloadConfigs(config?: IAppConfig) { + async reloadConfigs(config?: IAppConfig): Promise { + if (!this.isBooted) { + await this.workerBoot(config); + return this; + } + try { if (Cluster.isMaster) { - AppLogger.info("Reload configs..."); - - AppLogger.info("Create application context..."); - if (config == undefined) { - config = await ConfigBuilder.readConfig(); - } - - await Master.current.reloadConfig(config); - } - else { - if (config == undefined) { - config = await ConfigBuilder.readConfig(); - } - process.send({ type: 'config', config: config }); + this.appLogger.info("Rebuild application context..."); } + + let newApp: Application = new Application(config, this.localModules); + await newApp.boot(this); + return newApp; } catch (error) { - AppLogger.error(`An exception occurred in reload config, operation have been cancelled, message: '${error.message}'`); + this.appLogger.error(`An exception occurred in reload config, operation have been cancelled, message: '${error.message}'`); } } - public get config(): Readonly { - if (Cluster.isMaster) { - return Master.current.config; - } - return Worker.current.config; + get config(): Readonly { + return this.configMeta; + } + + private async initializeInternalModule(): Promise { + let internalModule: KanroInternalModule = new KanroInternalModule(this); + await internalModule.moduleManager.initialize(internalModule); + await internalModule.configBuilder.initialize(); } - private constructor() { + private registerLocalModules(localModules: { module: Module, name: string, version: string }[] = []): void { + for (const localModule of localModules) { + this.moduleManager.registerLocalModule(localModule.name, localModule.version, localModule.module); + } } - private static instance: Application; + private async entryPoint(context: RequestContext): Promise { + context = await NodeHandler(context, this.runtimeContext.entryPoint); + context = await NodeHandler(context, this.runtimeContext.exitPoint); + return context; + } - static get current(): Application { - if (Application.instance == undefined) { - Application.instance = new Application(); + private async boot(application?: Application): Promise { + if (Cluster.isMaster) { + if (application == null) { + await this.helloKanro(); + } + this.isBooted = true; + await this.initializeInternalModule(); + this.appLogger.info("Booting..."); + + this.appLogger.info("Load application config..."); + this.configMeta = await this.configBuilder.readConfig(this.configMeta); + this.runtimeContext = ObjectUtils.copy(this.config); + + this.appLogger.info("Register local modules..."); + this.registerLocalModules(this.localModules); + + this.appLogger.info("Install modules and fill nodes..."); + await this.moduleManager.loadConfig(this.runtimeContext); + + if (this.config.cluster) { + this.appLogger.warning("Cluster mode is experimental in current kanro version, some feature may be instability."); + await (new Master(this, this.clusterLogger, this.appLogger)).run(); + this.appLogger.info("Kanro is ready."); + } else { + let oldHttpServer: HttpServer = ObjectUtils.getValueFormKeys(application, "httpServer"); + await this.httpServer.initialize(this.config.port, async (context) => { + return await this.entryPoint(context); + }, oldHttpServer); + + this.appLogger.info("Kanro is ready."); + } + } else { + await this.initializeInternalModule(); + this.appLogger.info("Booting worker..."); + await (new Worker(this, this.clusterLogger, this.appLogger)).run(); } - return Application.instance; + } + + async workerBoot(config: IAppConfig): Promise { + this.isBooted = true; + this.configMeta = await this.configBuilder.readConfig(config); + this.runtimeContext = ObjectUtils.copy(this.config); + this.registerLocalModules(this.localModules); + await this.moduleManager.loadConfig(this.runtimeContext); + await this.httpServer.initialize(this.config.port, async (context) => { + return await this.entryPoint(context); + }); + this.appLogger.info(`Worker ${Cluster.worker.id} is ready.`); } } \ No newline at end of file diff --git a/src/Cluster/Master.ts b/src/Cluster/Master.ts index b750cd7..bf41001 100644 --- a/src/Cluster/Master.ts +++ b/src/Cluster/Master.ts @@ -1,137 +1,112 @@ import * as Cluster from "cluster"; import * as OS from "os"; -import { LoggerManager } from "../LoggerManager"; import { AnsiStyle, Colors, ILogger, LogLevel } from "../Logging/index"; +import { Application } from "../Application"; import { ConfigBuilder } from "../ConfigBuilder"; +import { CoreLogger } from "../Logging/CoreLogger"; import { IAppConfig } from "../IAppConfig"; -import { RequestContext } from "../RequestContext"; -import { NodeHandler } from "../NodeHandler"; -import { ObjectUtils, LoggerUtils, TimeUtils } from "../Utils/index"; -import { ModuleManager } from "../ModuleManager"; +import { IConfigMessage, ILogMessage, IMessage } from "./Message"; import { KanroModule } from "../KanroModule"; -import { Application } from "../Application"; -import { CoreLogger } from "../Logging/CoreLogger"; -import { Worker } from "./Worker"; -import { AppLogger } from "../AppLogger"; +import { LoggerManager } from "../LoggerManager"; +import { LoggerUtils, ObjectUtils, TimeUtils } from "../Utils/index"; import { Module } from "../Core/index"; - -let clusterLogger = LoggerManager.current.registerLogger("Cluster", AnsiStyle.create().foreground(Colors.cyan)); +import { ModuleManager } from "../ModuleManager"; +import { NodeHandler } from "../NodeHandler"; +import { Worker } from "./Worker"; export class Master { - private constructor() { + constructor(application: Application, clusterLogger: ILogger, appLogger: ILogger) { + this.application = application; + this.clusterLogger = clusterLogger; + this.appLogger = appLogger; } - private static instance: Master; - private configMeta: IAppConfig; + private application: Application; + private clusterLogger: ILogger; + private appLogger: ILogger; + private workLogger: { [id: string]: Colors } = {}; - static get current(): Master { - if (Master.instance == undefined) { - Master.instance = new Master(); - } - return Master.instance; - } + async run(): Promise { + let color: number = 0; - private createWorkers() { - let workLogger: { [id: string]: Colors } = {}; - let color = 0; + this.clusterLogger.info("Running with cluster mode, forking workers."); - clusterLogger.info("Running with cluster mode, forking workers."); - for (var i = 0; i < OS.cpus().length; i++) { + let workerCount: number = OS.cpus().length - Object.keys(Cluster.workers).length; + for (let i: number = 0; i < workerCount; i++) { Cluster.fork(); } - Cluster.on("exit", function (worker, code, signal) { - workLogger[worker.id] = undefined; - clusterLogger.warning(`Worker(${worker.id}) has exited, creating new worker.`); - Cluster.fork(); + Cluster.on("exit", function (worker: Cluster.Worker, code: number, signal: string): void { + if (this.workLogger[worker.id] != null) { + this.workLogger[worker.id] = undefined; + this.clusterLogger.warning(`Worker(${worker.id}) has exited, creating new worker.`); + Cluster.fork(); + } }); - Cluster.on('fork', worker => { - workLogger[worker.id] = ++color; + Cluster.on("fork", worker => { + this.workLogger[worker.id] = ++color; if (color >= 7) { color = 0; } }); - Cluster.on('message', (worker, message: { type: string }, handle) => { + Cluster.on("message", (worker, message: IMessage, handle) => { switch (message.type) { - case 'online': - worker.send({ type: 'config', config: this.configMeta }); + case "online": + worker.send({ type: "config", config: this.application.config }); break; - case 'log': - switch (message['level']) { + case "log": + let logMessage: ILogMessage = message; + switch (logMessage.level) { case LogLevel.info: - CoreLogger.current.log(LoggerUtils.buildLogString(message['namespace'], LogLevel.info, message['message'], TimeUtils.getTimePassed(CoreLogger.time), AnsiStyle.create(message['style']))); + CoreLogger.current.log(LoggerUtils.buildLogString( + logMessage.namespace, + LogLevel.info, logMessage.message, + TimeUtils.getTimePassed(CoreLogger.time), + AnsiStyle.create(logMessage.style))); break; case LogLevel.success: - CoreLogger.current.log(LoggerUtils.buildLogString(message['namespace'], LogLevel.success, message['message'], TimeUtils.getTimePassed(CoreLogger.time), AnsiStyle.create(message['style']))); + CoreLogger.current.log(LoggerUtils.buildLogString( + logMessage.namespace, + LogLevel.success, + logMessage.message, + TimeUtils.getTimePassed(CoreLogger.time), + AnsiStyle.create(logMessage.style))); break; case LogLevel.warning: - CoreLogger.current.error(LoggerUtils.buildLogString(message['namespace'], LogLevel.warning, message['message'], TimeUtils.getTimePassed(CoreLogger.time), AnsiStyle.create(message['style']))); + CoreLogger.current.error(LoggerUtils.buildLogString( + logMessage.namespace, + LogLevel.warning, + logMessage.message, + TimeUtils.getTimePassed(CoreLogger.time), + AnsiStyle.create(logMessage.style))); break; case LogLevel.error: - CoreLogger.current.error(LoggerUtils.buildLogString(message['namespace'], LogLevel.error, message['message'], TimeUtils.getTimePassed(CoreLogger.time), AnsiStyle.create(message['style']))); + CoreLogger.current.error(LoggerUtils.buildLogString( + logMessage.namespace, + LogLevel.error, + logMessage.message, + TimeUtils.getTimePassed(CoreLogger.time), + AnsiStyle.create(logMessage.style))); break; } break; - case 'config': - Application.current.reloadConfigs(message['config']); + case "config": + let configMessage: IConfigMessage = message; + this.application.reloadConfigs(configMessage.config); break; default: break; } }); - - AppLogger.success("Kanro is ready."); - } - - get config(): Readonly { - return this.configMeta; - } - - async run(config: IAppConfig, localModules: { module: Module, name: string, version: string }[]) { - this.configMeta = ObjectUtils.copy(config); - - if (config.cluster != undefined && config.cluster) { - AppLogger.info("Pre-initialize module manager..."); - await ModuleManager.initialize(config); - ModuleManager.current.registerLocalModule('kanro', '*', new KanroModule()); - for (let localModule of localModules) { - ModuleManager.current.registerLocalModule(localModule.name, localModule.version, localModule.module); - } - - AppLogger.info("Install module and fill nodes..."); - await ModuleManager.current.loadConfig(config); - - this.createWorkers(); - return; - } - else { - AppLogger.info("Booting worker..."); - await Worker.current.run(config, localModules); - } } - async reloadConfig(config: IAppConfig) { - let metaConfig = ObjectUtils.copy(config); - - if (this.config.cluster != undefined && this.config.cluster) { - AppLogger.info("Pre-initialize module manager..."); - await ModuleManager.initialize(config); - ModuleManager.current.registerLocalModule('kanro', '*', new KanroModule()); - - AppLogger.info("Install module and fill nodes..."); - await ModuleManager.current.loadConfig(config); - - for (let id in Cluster.workers) { - Cluster.workers[id].send({ type: 'config', config: metaConfig }); + async reloadConfig(config: IAppConfig): Promise { + for (let id in Cluster.workers) { + if (Cluster.workers.hasOwnProperty(id)) { + Cluster.workers[id].send({ type: "config", config: config }); } } - else { - await ModuleManager.current.reloadConfig(config); - Worker.current.initialize(ObjectUtils.copy(metaConfig)); - } - this.configMeta = metaConfig; - return; } } \ No newline at end of file diff --git a/src/Cluster/Message.ts b/src/Cluster/Message.ts new file mode 100644 index 0000000..5f38d23 --- /dev/null +++ b/src/Cluster/Message.ts @@ -0,0 +1,19 @@ +import { IAppConfig } from "../IAppConfig"; +import { LogLevel } from "../Logging"; + +export interface IMessage { + type: string; +} + +export interface ILogMessage extends IMessage { + type : "log"; + level: LogLevel; + namespace: string; + message: string; + style: string; +} + +export interface IConfigMessage extends IMessage { + type : "log"; + config: IAppConfig; +} \ No newline at end of file diff --git a/src/Cluster/Worker.ts b/src/Cluster/Worker.ts index fa69507..86c387b 100644 --- a/src/Cluster/Worker.ts +++ b/src/Cluster/Worker.ts @@ -1,87 +1,38 @@ import * as Cluster from "cluster"; -import { RequestContext } from "../RequestContext"; -import { NodeHandler } from "../NodeHandler"; import { Application } from "../Application"; import { HttpServer } from "../HttpServer"; import { IAppConfig } from "../IAppConfig"; -import { AppLogger } from "../AppLogger"; -import { ModuleManager } from "../ModuleManager"; -import { ObjectUtils } from "../Utils/index"; +import { ILogger } from "../Logging"; import { KanroModule } from "../KanroModule"; import { Module } from "../Core/index"; +import { ModuleManager } from "../ModuleManager"; +import { NodeHandler } from "../NodeHandler"; +import { ObjectUtils } from "../Utils/index"; +import { IMessage, IConfigMessage } from "./Message"; export class Worker { - private constructor() { + constructor(application: Application, clusterLogger: ILogger, appLogger: ILogger) { + this.application = application; + this.clusterLogger = clusterLogger; + this.appLogger = appLogger; } - private configMeta: IAppConfig; - private appConfig: IAppConfig; - private server: HttpServer; - private static instance: Worker; - private localModules: { module: Module, name: string, version: string }[]; - - static get current(): Worker { - if (Worker.instance == undefined) { - Worker.instance = new Worker(); - } - return Worker.instance; - } - - get config(): Readonly { - return this.configMeta; - } - - async run(config: IAppConfig, localModules: { module: Module, name: string, version: string }[]) { - if (Cluster.isWorker) { - process.on('message', data => { - this.messageReceived(data); - }); - process.send({ type: 'online' }); - } - else { - await this.initialize(config); - } - } - - async initialize(config: IAppConfig) { - this.configMeta = ObjectUtils.copy(config); - this.appConfig = config; - - if (ModuleManager.current == undefined) { - await ModuleManager.initialize(config); - ModuleManager.current.registerLocalModule('kanro', '*', new KanroModule()); - - await ModuleManager.current.loadConfig(config); - } - else { - await ModuleManager.current.reloadConfig(config); - } - - if (this.server == undefined) { - await HttpServer.initialize(config.port, async r => { - return await this.entryPointe(r); - }); - } - - if (Cluster.isWorker) { - AppLogger.success(`Worker(${Cluster.worker.id}) is ready.`); - } - else { - AppLogger.success('Kanro is ready.'); - } - } + private application: Application; + private clusterLogger: ILogger; + private appLogger: ILogger; - private async entryPointe(context: RequestContext) { - let config = this.appConfig; - context = await NodeHandler(context, config.entryPoint); - context = await NodeHandler(context, config.exitPoint); - return context; + async run(): Promise { + process.on("message", data => { + this.messageReceived(data); + }); + process.send({ type: "online" }); } - private messageReceived(message: any) { - switch (message['type']) { - case 'config': - this.initialize(message['config']); + private messageReceived(message: IMessage): void { + switch (message.type) { + case "config": + let configMessage: IConfigMessage = message; + this.application.reloadConfigs(configMessage.config); break; default: break; diff --git a/src/ConfigBuilder.ts b/src/ConfigBuilder.ts index 785d85f..4dcd328 100644 --- a/src/ConfigBuilder.ts +++ b/src/ConfigBuilder.ts @@ -1,74 +1,96 @@ -import * as Ajv from 'Ajv'; -import * as Request from 'request'; -import { Path, File } from './IO'; -import { InvalidConfigException } from './Exceptions'; -import { IAppConfig } from './IAppConfig'; +import * as Ajv from "Ajv"; +import * as Http from "http"; +import { AnsiStyle, Colors, ILogger } from "./Logging"; +import { File, Path } from "./IO"; +import { IAppConfig } from "./IAppConfig"; +import { IModuleInfo, Service } from "./Core"; +import { InvalidConfigException } from "./Exceptions"; +import { KanroInternalModule } from "./KanroInternalModule"; import { LoggerManager } from "./LoggerManager"; -import { Colors, AnsiStyle } from "./Logging"; -import { Router } from "./Router"; +import { UrlRouter } from "./Router"; -let projectDir = Path.resolve(__dirname, '..'); -let ajv = Ajv(); -let configLogger = LoggerManager.current.registerLogger("Config", AnsiStyle.create().foreground(Colors.green)); +let projectDir: string = Path.resolve(__dirname, ".."); -export class ConfigBuilder { - static async initialize() { +export class ConfigBuilder extends Service { + dependencies = { + loggerManager: { + name: LoggerManager.name, + module: KanroInternalModule.moduleInfo + } + }; + + private ajv = Ajv(); + + constructor() { + super(undefined); + } + + async onLoaded(): Promise { + this.configLogger = await (await this.getDependedService("loggerManager")) + .registerLogger("Config", AnsiStyle.create().foreground(Colors.green)); + } + private configLogger: ILogger; + + async initialize(): Promise { try { - let result = await new Promise((res, rej) => { - Request.get("http://higan.me/schema/1.1/kanro.json", (error, response, body) => { - if (error) { - rej(error); - return; - } - res(body); + let result: any = await new Promise((res, rej) => { + Http.get("https://higan.me/schema/1.1/kanro.json", response => { + var data = ""; + response.on("data", chunk => { + data += chunk; + }); + response.on("end", () => { + res(data); + }); + response.on("error", err => { + rej(err); + }); }); - }) - let schema = JSON.parse(result); - ajv.addSchema(schema, 'kanro'); + }); + let schema: any = JSON.parse(result); + this.ajv.addSchema(schema, "kanro"); } catch (error) { - configLogger.warning("Config schema load failed, config validating will be disable."); + this.configLogger.warning("Config schema load failed, config validating will be disable."); } } - static async readConfig(config?: IAppConfig): Promise { - if (config != undefined) { + async readConfig(config?: IAppConfig): Promise { + if (config != null) { return config; } - configLogger.info("Unspecified config, searching for configs..."); + this.configLogger.info("Unspecified config, searching for configs..."); if (await File.exists(`${process.cwd()}/kanro.json`)) { - return ConfigBuilder.readConfigFromFile(`${process.cwd()}/kanro.json`); - } - else if (await File.exists(`${projectDir}/config/kanro.json`)) { - configLogger.warning("'kanro.json' not found in project dir, default config will be using."); - return ConfigBuilder.readConfigFromFile(`${projectDir}/config/kanro.json`); - } - else { + return this.readConfigFromFile(`${process.cwd()}/kanro.json`); + } else if (await File.exists(`${projectDir}/config/kanro.json`)) { + this.configLogger.warning("'kanro.json' not found in work directory, default config will be used."); + return this.readConfigFromFile(`${projectDir}/config/kanro.json`); + } else { throw new Error("Kanro config 'kanro.json' not found."); } } - static async readConfigFromFile(file: string): Promise { - let result = await File.readJson(file); - ConfigBuilder.validate(result); + async readConfigFromFile(file: string): Promise { + let result: any = await File.readJson(file); + this.validate(result); return result; } - static async readConfigFromJson(jsonString: string): Promise { - let result = JSON.parse(jsonString); - ConfigBuilder.validate(result); + async readConfigFromJson(jsonString: string): Promise { + let result: any = JSON.parse(jsonString); + this.validate(result); return result; } - private static validate(config: IAppConfig) { - if (ajv.getSchema('kanro') == undefined) { + validate(config: IAppConfig): boolean { + if (this.ajv.getSchema("kanro") == null) { return true; } - if (!ajv.validate('kanro', config)) { - configLogger.error("Config can't validate with schema, check your 'kanro.json' file."); - throw new InvalidConfigException('kanro', ajv.errorsText(ajv.errors)) + if (!this.ajv.validate("kanro", config)) { + this.configLogger.error("Config can't validate with schema, check your 'kanro.json' file."); + throw new InvalidConfigException("kanro", this.ajv.errorsText(this.ajv.errors)); } return true; } diff --git a/src/Core/ExceptionHandler.ts b/src/Core/ExceptionHandler.ts index 9e5e933..646e8a3 100644 --- a/src/Core/ExceptionHandler.ts +++ b/src/Core/ExceptionHandler.ts @@ -1,12 +1,12 @@ +import { INodeContainer } from "./INodeContainer"; import { IRequest, IResponse } from "../Http"; import { Node } from "./Node"; -import { INodeContainer } from "./INodeContainer"; /** * A exception handler, a error and other information will be input, if this node understand this error, it will output a response. - * + * * You can implement this interface by friendly error message output. - * + * * @abstract * @class ExceptionHandler * @extends {Node} @@ -14,7 +14,7 @@ import { INodeContainer } from "./INodeContainer"; export abstract class ExceptionHandler extends Node { /** * If this node understand this error, return the response, otherwise return undefined. - * + * * @abstract * @param {Error} err The error which occurred. * @param {IRequest} request Request information. @@ -22,7 +22,7 @@ export abstract class ExceptionHandler extends Node { * @returns {Promise} Result response or undefined. * @memberof ExceptionHandler */ - public abstract handler(err: Error, request: IRequest, response: IResponse): Promise; + abstract handler(err: Error, request: IRequest, response: IResponse): Promise; constructor(container: INodeContainer) { super(container); diff --git a/src/Core/Fuse.ts b/src/Core/Fuse.ts index e84365c..1b6c927 100644 --- a/src/Core/Fuse.ts +++ b/src/Core/Fuse.ts @@ -1,12 +1,12 @@ +import { INodeContainer } from "./INodeContainer"; import { IRequest } from "../Http"; import { Node } from "./Node"; -import { INodeContainer } from "./INodeContainer"; /** * A fuse, a error and request information will be input, if this fuse understand this error, it will output a request to next node. - * + * * You can implement this interface by service degradation. - * + * * @export * @abstract * @class Fuse @@ -15,14 +15,14 @@ import { INodeContainer } from "./INodeContainer"; export abstract class Fuse extends Node { /** * Fusing, if this fuse understand this error, return the request, otherwise return undefined. - * + * * @abstract * @param {Error} err The error which occurred. * @param {IRequest} request Request information. * @returns {Promise} Result request or undefined. * @memberof Fuse */ - public abstract fusing(err: Error, request: IRequest): Promise; + abstract fusing(err: Error, request: IRequest): Promise; constructor(container: INodeContainer) { super(container); diff --git a/src/Core/IModuleInfo.ts b/src/Core/IModuleInfo.ts index 1a8ad90..6931758 100644 --- a/src/Core/IModuleInfo.ts +++ b/src/Core/IModuleInfo.ts @@ -1,20 +1,20 @@ /** * Information of a Kanro module. - * + * * @export * @interface IModuleInfo */ export interface IModuleInfo { /** * Name of this Kanro module. - * + * * @type {string} * @memberOf IModuleInfo */ name: string; /** * Version of this Kanro module. - * + * * @type {string} * @memberOf IModuleInfo */ diff --git a/src/Core/INodeContainer.ts b/src/Core/INodeContainer.ts index 373e880..bb865a8 100644 --- a/src/Core/INodeContainer.ts +++ b/src/Core/INodeContainer.ts @@ -1,12 +1,10 @@ -import { IModuleInfo } from "./IModuleInfo"; import { ExceptionHandler } from "./ExceptionHandler"; import { Fuse } from "./Fuse"; -import { Service } from "./Service"; -import { Node } from "./Node"; +import { IModuleInfo } from "./IModuleInfo"; +import { INodeReference } from "./INodeReference"; +import { Node, Service } from "./Node"; -export interface INodeContainer { - name: string; - module: IModuleInfo; +export interface INodeContainer extends INodeReference { exceptionHandlers?: INodeContainer[]; fuses?: INodeContainer[]; dependencies?: { [name: string]: INodeContainer }; diff --git a/src/Core/INodeReference.ts b/src/Core/INodeReference.ts new file mode 100644 index 0000000..b83c636 --- /dev/null +++ b/src/Core/INodeReference.ts @@ -0,0 +1,7 @@ +import { IModuleInfo } from "."; + +export interface INodeReference { + id?: string; + name: string; + module: IModuleInfo; +} \ No newline at end of file diff --git a/src/Core/Module.ts b/src/Core/Module.ts index f04668a..4d3011f 100644 --- a/src/Core/Module.ts +++ b/src/Core/Module.ts @@ -1,24 +1,22 @@ -import { Node } from "./Node"; import { IModuleInfo } from "./IModuleInfo"; import { INodeContainer } from "./INodeContainer"; +import { Node } from "./Node"; export abstract class Module { - /** - * Dependent module, Kanro will install those module after install this module. - * - * @type {IModuleInfo[]} - * @memberOf IModule - */ - dependencies: IModuleInfo[]; - /** * Get a node by a node config. - * - * Note: Kanro 会对每个 node config 调用这个方法,对于同样的名字的 node 如果模块返回的是不同示例,则会导致在链路中也会使用不同的实例,反之如果对每个相同名字的 node 都返回同一个实例,则会导致这个实例可能被用在多个链路上,如果你需要共享上下文则可以考虑这种方式。请根据具体的需求来返回单例。 - * - * @param {INodeContainer} container - * @returns {Promise} + * + * Note: Kanro 会对每个 node config 调用这个方法,对于同样的名字的 node 如果模块返回的是不同示例, + * 则会导致在链路中也会使用不同的实例,反之如果对每个相同名字的 node 都返回同一个实例,则会导致这个 + * 实例可能被用在多个链路上,如果你需要共享上下文则可以考虑这种方式。请根据具体的需求来返回单例。 + * 另外所有的 Service 类型的 Node 都是单例,除非指定了 Service 的 ID。 + * + * @param {INodeContainer} container + * @returns {Promise} * @memberof IModule */ abstract getNode(container: INodeContainer): Promise; + + + abstract readonly nodes: Array; } \ No newline at end of file diff --git a/src/Core/Node.ts b/src/Core/Node.ts index cd0a05c..8316d25 100644 --- a/src/Core/Node.ts +++ b/src/Core/Node.ts @@ -1,10 +1,10 @@ import { IModuleInfo } from "./IModuleInfo"; import { INodeContainer } from "./INodeContainer"; -import { Service } from "./Service"; +import { INodeReference } from "./INodeReference"; /** * A node which can be used in Kanro. - * + * * @export * @abstract * @class Node @@ -12,52 +12,88 @@ import { Service } from "./Service"; export abstract class Node { /** * The services which be dependent by this node. - * - * If a named 'undefined' property in this object, Kanro will find service in all loaded module and set service instance to property. - * - * If a named 'IModuleInfo' property in this object, Kanro will find service in specified module and set service instance to property. - * - * @type {({ [name: string]: Service | IModuleInfo; })} + * + * If a named 'INodeContainer' property in this object, + * Kanro will find service in specified module and set service instance to property. + * + * @type {({ [name: string]: INodeContainer; })} * @memberof Node */ - dependencies: { [name: string]: Service | IModuleInfo; }; + dependencies: { [name: string]: INodeContainer; } = {}; /** * Name of node, it will return class name by default. - * + * * @type {string} * @memberof Node */ - public get name(): string { + get name(): string { return this.constructor.name; } /** * Creates an instance of Node. - * @param {INodeContainer} container + * @param {INodeContainer} container * @memberof Node */ + // tslint:disable-next-line:no-empty constructor(container: INodeContainer) { - } /** * This method will be called by kanro when node has been create. - * - * @returns {Promise} + * + * @returns {Promise} * @memberof Node */ - public async onLoaded(): Promise { - + // tslint:disable-next-line:no-empty + async onCreated(): Promise { } /** - * This method will be called by kanro when dependencies is filled to 'Node.dependencies'. - * - * @returns {Promise} + * This method will be called by kanro when all dependencies are filled to 'Node.dependencies'. + * + * @returns {Promise} * @memberof Node */ - public async onDependenciesFilled(): Promise { + // tslint:disable-next-line:no-empty + async onLoaded(): Promise { + } + + protected async getDependedService(name: string): Promise { + if (this.dependencies[name] != null) { + let result: Service = this.dependencies[name].instance; + if (result instanceof ServiceResolver) { + return await result.resolve(); + } + return result; + } + return null; + } +} + +/** + * A service which can be used in Kanro. + * + * @export + * @abstract + * @class Service + * @extends {Node} + */ +export abstract class Service extends Node { + readonly isProxable: boolean = true; + + constructor(container: INodeContainer) { + super(container); + } +} + +export abstract class ServiceResolver extends Service { + readonly isProxable: boolean = false; + + abstract async resolve(): Promise; + constructor(container: INodeContainer) { + super(container); } } \ No newline at end of file diff --git a/src/Core/RequestDiverter.ts b/src/Core/RequestDiverter.ts index c9c04f0..098cc1f 100644 --- a/src/Core/RequestDiverter.ts +++ b/src/Core/RequestDiverter.ts @@ -1,12 +1,12 @@ -import { IRequest } from "../Http"; import { INodeContainer } from "./INodeContainer"; +import { IRequest } from "../Http"; import { Node } from "./Node"; /** * A request diverter, a HTTP request will be input, then this node will select next node which will handle the request in nodes list. - * + * * You can implement this interface by request router, load balancing. - * + * * @export * @abstract * @class RequestDiverter @@ -16,14 +16,14 @@ export abstract class RequestDiverter extends Node { /** * Divert the request. - * + * * @abstract * @param {IRequest} request The request will be diverted. * @param {INodeContainer[]} nodes Nodes list. * @returns {Promise>} Selected node. * @memberof RequestDiverter */ - public abstract shunt(request: IRequest, nodes: INodeContainer[]): Promise>; + abstract shunt(request: IRequest, nodes: INodeContainer[]): Promise>; constructor(container: INodeContainer) { super(container); diff --git a/src/Core/RequestHandler.ts b/src/Core/RequestHandler.ts index e46d06e..6c18f0a 100644 --- a/src/Core/RequestHandler.ts +++ b/src/Core/RequestHandler.ts @@ -1,12 +1,12 @@ +import { INodeContainer } from "./INodeContainer"; import { IRequest } from "../Http"; import { Node } from "./Node"; -import { INodeContainer } from "./INodeContainer"; /** * A request handler, a HTTP request will be input, then this node will handle it and output the result to next node. - * + * * You can implement this interface by request validator, authenticator or rewrite this request. - * + * * @export * @abstract * @class RequestHandler @@ -15,13 +15,13 @@ import { INodeContainer } from "./INodeContainer"; export abstract class RequestHandler extends Node { /** * Handle the request. - * + * * @param {IRequest} request The request will be handle. * @returns {Promise} The handled request. - * + * * @memberof RequestHandler */ - public abstract handler(request: IRequest): Promise; + abstract handler(request: IRequest): Promise; constructor(container: INodeContainer) { super(container); diff --git a/src/Core/RequestReplicator.ts b/src/Core/RequestReplicator.ts index f339cb2..d4013ee 100644 --- a/src/Core/RequestReplicator.ts +++ b/src/Core/RequestReplicator.ts @@ -1,14 +1,14 @@ +import { INodeContainer } from "./INodeContainer"; import { IRequest } from "../Http"; import { Node } from "./Node"; -import { INodeContainer } from "./INodeContainer"; /** * A request replicator, a HTTP request will be input, then this node will copy the input request, return a request list. - * + * * You can implement this interface by async request handler. - * + * * Note: the count of returns must be equal to 'count' param, and the first of returns must be original request. - * + * * @export * @abstract * @class RequestReplicator @@ -17,14 +17,14 @@ import { INodeContainer } from "./INodeContainer"; export abstract class RequestReplicator extends Node { /** * Copy request. - * + * * @abstract * @param {IRequest} request The request will be copied. * @param {number} count Count of the returns. * @returns {Promise} Result request list. * @memberof RequestReplicator */ - public abstract copy(request: IRequest, count: number): Promise; + abstract copy(request: IRequest, count: number): Promise; constructor(container: INodeContainer) { super(container); diff --git a/src/Core/Responder.ts b/src/Core/Responder.ts index 78d6407..933ccc4 100644 --- a/src/Core/Responder.ts +++ b/src/Core/Responder.ts @@ -1,12 +1,13 @@ +import { INodeContainer } from "./INodeContainer"; import { IRequest, IResponse } from "../Http"; import { Node } from "./Node"; -import { INodeContainer } from "./INodeContainer"; /** - * A responder, a HTTP request will be input, then this node will respond this request, convert this request to a response, output result response to next node. - * + * A responder, a HTTP request will be input, then this node will respond this request, + * convert this request to a response, output result response to next node. + * * Note: You can use the 'request.respond()' method to convert request to a response. - * + * * @export * @abstract * @class Responder @@ -15,13 +16,13 @@ import { INodeContainer } from "./INodeContainer"; export abstract class Responder extends Node { /** * Handle the request and give a response. - * + * * @abstract * @param {IRequest} request The input request. * @returns {Promise} Result response. * @memberof Responder */ - public abstract respond(request: IRequest): Promise; + abstract respond(request: IRequest): Promise; constructor(container: INodeContainer) { super(container); diff --git a/src/Core/ResponseHandler.ts b/src/Core/ResponseHandler.ts index 3aec27c..45ff285 100644 --- a/src/Core/ResponseHandler.ts +++ b/src/Core/ResponseHandler.ts @@ -1,12 +1,12 @@ +import { INodeContainer } from "./INodeContainer"; import { IResponse } from "../Http"; import { Node } from "./Node"; -import { INodeContainer } from "./INodeContainer"; /** * A response handler, a HTTP response will be input, then this node will handle it and output the result to next node. - * + * * You can implement this interface by response validator or rewrite this response. - * + * * @export * @abstract * @class ResponseHandler @@ -14,13 +14,13 @@ import { INodeContainer } from "./INodeContainer"; export abstract class ResponseHandler extends Node { /** * Handle the response. - * + * * @abstract * @param {IResponse} response The input response. * @returns {Promise} Result response. * @memberof ResponseHandler */ - public abstract handler(response: IResponse): Promise; + abstract handler(response: IResponse): Promise; constructor(container: INodeContainer) { super(container); diff --git a/src/Core/Service.ts b/src/Core/Service.ts deleted file mode 100644 index dca7faa..0000000 --- a/src/Core/Service.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Node } from "./Node"; -import { INodeContainer } from "./INodeContainer"; - -/** - * A service which can be used in Kanro. - * - * @export - * @abstract - * @class Service - * @extends {Node} - */ -export abstract class Service extends Node { - constructor(container: INodeContainer) { - super(container); - } -} \ No newline at end of file diff --git a/src/Core/index.ts b/src/Core/index.ts index 302adc4..85fcbcc 100644 --- a/src/Core/index.ts +++ b/src/Core/index.ts @@ -6,7 +6,7 @@ export * from "./RequestHandler"; export * from "./RequestReplicator"; export * from "./Responder"; export * from "./ResponseHandler"; -export * from "./Service"; export * from "./Module"; export * from "./IModuleInfo"; -export * from "./INodeContainer"; \ No newline at end of file +export * from "./INodeContainer"; +export * from "./INodeReference"; \ No newline at end of file diff --git a/src/DSL/KanroDSL.ts b/src/DSL/KanroDSL.ts new file mode 100644 index 0000000..b318916 --- /dev/null +++ b/src/DSL/KanroDSL.ts @@ -0,0 +1,85 @@ +import * as Chevrotain from "chevrotain"; +import * as Tokens from "./Token" +import { OrderedTokenType } from "./OrderedTokenType"; + +var allTokens = Object.keys(Tokens).map(it => Tokens[it]).sort((a, b) => a.order - b.order); + +export var KanroDSLLexer = new Chevrotain.Lexer(allTokens); + +export class KanroConfigParser extends Chevrotain.Parser { + constructor(input: Chevrotain.IToken[]) { + super(input, allTokens, { + recoveryEnabled: true, + outputCst: true + }) + this.performSelfAnalysis(); + } + + public kanro = this.RULE("kanro", () => { + this.CONSUME(Tokens.Kanro) + this.CONSUME(Tokens.LCurly) + this.MANY_SEP({ + SEP: Tokens.LineBreaker, + DEF: () => { + this.OR([ + { + "ALT": () => this.SUBRULE(this.npm) + }, + { + "ALT": () => this.SUBRULE(this.config) + } + ]) + } + }) + this.CONSUME(Tokens.RCurly) + }); + + public config = this.RULE("config", () => { + this.CONSUME(Tokens.Identifier) + this.SUBRULE(this.value) + }); + + public npm = this.RULE("npm", () => { + this.CONSUME(Tokens.Npm) + this.CONSUME(Tokens.StringValue) + }); + + public value = this.RULE("value", () => { + this.OR([ + { + ALT: () => this.CONSUME(Tokens.StringValue) + }, + { + ALT: () => this.CONSUME(Tokens.NumberValue) + }, + { + ALT: () => this.CONSUME(Tokens.VersionValue) + }, + { + ALT: () => this.CONSUME(Tokens.True) + }, + { + ALT: () => this.CONSUME(Tokens.False) + }, + { + ALT: () => this.CONSUME(Tokens.Null) + } + ]) + }); + + public test = this.RULE("test", () => { + this.MANY_SEP({ + SEP: Tokens.LineBreaker, + DEF: () => { + this.OR([ + { + "ALT": () => this.SUBRULE(this.npm) + }, + { + "ALT": () => this.SUBRULE(this.config) + } + ]) + } + }) + }); +} \ No newline at end of file diff --git a/src/DSL/OrderedTokenType.ts b/src/DSL/OrderedTokenType.ts new file mode 100644 index 0000000..bfdddd1 --- /dev/null +++ b/src/DSL/OrderedTokenType.ts @@ -0,0 +1,6 @@ +import * as Chevrotain from "chevrotain"; + +export interface OrderedTokenType extends Chevrotain.TokenType { + order: number +} + diff --git a/src/DSL/Token.ts b/src/DSL/Token.ts new file mode 100644 index 0000000..c0d7042 --- /dev/null +++ b/src/DSL/Token.ts @@ -0,0 +1,87 @@ +import * as Chevrotain from "chevrotain"; +import { OrderedTokenType } from "./OrderedTokenType" + +declare global { + interface Object { + apply(apply: (T) => void): T; + } +} + +Object.prototype.apply = function (apply: (T) => void): T { + apply(this) + return this +} + +export const LineBreaker = Chevrotain.createToken({ + name: "LineBreaker", + pattern: /[\r\n]/, + group: Chevrotain.Lexer.SKIPPED +}).apply(it => it.order = 0); + +export const WhiteSpace = Chevrotain.createToken({ + name: "WhiteSpace", + pattern: /[ \t]+/, + group: Chevrotain.Lexer.SKIPPED +}).apply(it => it.order = 100); + +export const Identifier = Chevrotain.createToken({ + name: "Identifier", + pattern: /[a-zA-Z_][a-zA-Z0-9_]*|`[^\r\n]+`/ +}).apply(it => it.order = 910); + +export const Kanro = Chevrotain.createToken({ + name: "Kanro", + pattern: /kanro/, + longer_alt: Identifier +}).apply(it => it.order = 200); + +export const Npm = Chevrotain.createToken({ + name: "Npm", + pattern: /npm/, + longer_alt: Identifier +}).apply(it => it.order = 300); + +export const LCurly = Chevrotain.createToken({ + name: "LCurly", + pattern: /{/, + label: "'{'" +}).apply(it => it.order = 500); + +export const RCurly = Chevrotain.createToken({ + name: "RCurly", + pattern: /}/, + label: "'}'" +}).apply(it => it.order = 600); + +export const True = Chevrotain.createToken({ + name: "True", + pattern: /true/, + longer_alt: Identifier +}).apply(it => it.order = 700); + +export const False = Chevrotain.createToken({ + name: "False", + pattern: /false/, + longer_alt: Identifier +}).apply(it => it.order = 800); + +export const Null = Chevrotain.createToken({ + name: "Null", + pattern: /null/, + longer_alt: Identifier +}).apply(it => it.order = 900); + +export const StringValue = Chevrotain.createToken({ + name: "StringValue", + pattern: /"(:?[^\\"\n\r]+|\\(:?[bfnrtv"\\/]|u[0-9a-fA-F]{4}))*"/ +}).apply(it => it.order = 1000); + +export const VersionValue = Chevrotain.createToken({ + name: "VersionValue", + pattern: /\*|[\^~]?[0-9]+(?:.[0-9]+){0,2}(?:-[A-Za-z0-9]+)?|latest/ +}).apply(it => it.order = 1100); + +export const NumberValue = Chevrotain.createToken({ + name: "NumberValue", + pattern: /-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/ +}).apply(it => it.order = 1200); \ No newline at end of file diff --git a/src/DSL/index.ts b/src/DSL/index.ts new file mode 100644 index 0000000..aa410fa --- /dev/null +++ b/src/DSL/index.ts @@ -0,0 +1,2 @@ +export * from "./Token"; +export * from "./KanroDSL"; \ No newline at end of file diff --git a/src/Exceptions/ArgumentException.ts b/src/Exceptions/ArgumentException.ts index 5f9a086..09248be 100644 --- a/src/Exceptions/ArgumentException.ts +++ b/src/Exceptions/ArgumentException.ts @@ -4,7 +4,7 @@ export class ArgumentException extends KanroException { name: string = "Error.Kanro.Argument"; paramName: string; - constructor(message: string, paramName: string, innerException: Error = undefined) { + constructor(message: string, paramName: string, innerException?: Error) { super(message, innerException); paramName = paramName; } diff --git a/src/Exceptions/ArgumentNullException.ts b/src/Exceptions/ArgumentNullException.ts index 3cfd5a4..8dbd5fd 100644 --- a/src/Exceptions/ArgumentNullException.ts +++ b/src/Exceptions/ArgumentNullException.ts @@ -3,7 +3,7 @@ import { ArgumentException } from "./ArgumentException"; export class ArgumentNullException extends ArgumentException { name: string = "Error.Kanro.Argument.Null"; - constructor(paramName: string, innerException: Error = undefined) { + constructor(paramName: string, innerException?: Error) { super(`'${paramName}' cannot be null or undefined.`, paramName, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/ArgumentOutOfRangeException.ts b/src/Exceptions/ArgumentOutOfRangeException.ts index 5233be0..8ab4278 100644 --- a/src/Exceptions/ArgumentOutOfRangeException.ts +++ b/src/Exceptions/ArgumentOutOfRangeException.ts @@ -3,7 +3,7 @@ import { ArgumentException } from "./ArgumentException"; export class ArgumentOutOfRangeException extends ArgumentException { name: string = "Error.Kanro.Argument.OutOfRange"; - constructor(paramName: string, innerException: Error = undefined) { + constructor(paramName: string, innerException?: Error) { super(`'${paramName}' is outside the allowable range.`, paramName, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/ArgumentTypeNotMatchedException.ts b/src/Exceptions/ArgumentTypeNotMatchedException.ts index 7b0823c..0cc4e34 100644 --- a/src/Exceptions/ArgumentTypeNotMatchedException.ts +++ b/src/Exceptions/ArgumentTypeNotMatchedException.ts @@ -1,10 +1,9 @@ - import { ArgumentException } from "./ArgumentException"; export class ArgumentTypeNotMatchedException extends ArgumentException { name: string = "Error.Kanro.Argument.TypeNotMatched"; - constructor(paramName: string, type: string, innerException: Error = undefined) { + constructor(paramName: string, type: string, innerException?: Error) { super(`Type of '${paramName}' is not matched of '${type}'.`, paramName, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/BadGatewayException.ts b/src/Exceptions/BadGatewayException.ts index 4945f6e..4865ef1 100644 --- a/src/Exceptions/BadGatewayException.ts +++ b/src/Exceptions/BadGatewayException.ts @@ -1,10 +1,9 @@ import { HttpException } from "./HttpException"; - export class BadGatewayException extends HttpException { - public name: string = "Error.Kanro.Http.BadGateway"; + name: string = "Error.Kanro.Http.BadGateway"; - constructor(message: string = "Bad Gateway", innerException: Error = undefined) { + constructor(message: string = "Bad Gateway", innerException?: Error) { super(502, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/BadRequestException.ts b/src/Exceptions/BadRequestException.ts index b4c56b7..45be695 100644 --- a/src/Exceptions/BadRequestException.ts +++ b/src/Exceptions/BadRequestException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class BadRequestException extends HttpException { - public name: string = "Error.Kanro.Http.BadRequest"; + name: string = "Error.Kanro.Http.BadRequest"; - constructor(message: string = "Bad Request", innerException: Error = undefined) { + constructor(message: string = "Bad Request", innerException?: Error) { super(400, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/BandwidthLimitExceededException.ts b/src/Exceptions/BandwidthLimitExceededException.ts index 2a2abf9..f6120a2 100644 --- a/src/Exceptions/BandwidthLimitExceededException.ts +++ b/src/Exceptions/BandwidthLimitExceededException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class BandwidthLimitExceededException extends HttpException { - public name: string = "Error.Kanro.Http.BandwidthLimitExceeded"; + name: string = "Error.Kanro.Http.BandwidthLimitExceeded"; - constructor(message: string = "Bandwidth Limit Exceeded", innerException: Error = undefined) { + constructor(message: string = "Bandwidth Limit Exceeded", innerException?: Error) { super(509, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/ConflictException.ts b/src/Exceptions/ConflictException.ts index 7fbec6c..7a9e04d 100644 --- a/src/Exceptions/ConflictException.ts +++ b/src/Exceptions/ConflictException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class ConflictException extends HttpException { - public name: string = "Error.Kanro.Http.Conflict"; + name: string = "Error.Kanro.Http.Conflict"; - constructor(message: string = "Conflict", innerException: Error = undefined) { + constructor(message: string = "Conflict", innerException?: Error) { super(409, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/ForbiddenException.ts b/src/Exceptions/ForbiddenException.ts index 7bec973..d3c0aa1 100644 --- a/src/Exceptions/ForbiddenException.ts +++ b/src/Exceptions/ForbiddenException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class ForbiddenException extends HttpException { - public name: string = "Error.Kanro.Http.Forbidden"; + name: string = "Error.Kanro.Http.Forbidden"; - constructor(message: string = "Forbidden", innerException: Error = undefined) { + constructor(message: string = "Forbidden", innerException?: Error) { super(403, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/GatewayTimeoutException.ts b/src/Exceptions/GatewayTimeoutException.ts index aae06dd..f12a395 100644 --- a/src/Exceptions/GatewayTimeoutException.ts +++ b/src/Exceptions/GatewayTimeoutException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class GatewayTimeoutException extends HttpException { - public name: string = "Error.Kanro.Http.GatewayTimeout"; + name: string = "Error.Kanro.Http.GatewayTimeout"; - constructor(message: string = "Gateway Timeout", innerException: Error = undefined) { + constructor(message: string = "Gateway Timeout", innerException?: Error) { super(504, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/GoneException.ts b/src/Exceptions/GoneException.ts index 9d8fc4b..3f8bada 100644 --- a/src/Exceptions/GoneException.ts +++ b/src/Exceptions/GoneException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class GoneException extends HttpException { - public name: string = "Error.Kanro.Http.Gone"; + name: string = "Error.Kanro.Http.Gone"; - constructor(message: string = "Gone", innerException: Error = undefined) { + constructor(message: string = "Gone", innerException?: Error) { super(410, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/HttpException.ts b/src/Exceptions/HttpException.ts index a32db57..05370e3 100644 --- a/src/Exceptions/HttpException.ts +++ b/src/Exceptions/HttpException.ts @@ -1,10 +1,10 @@ import { KanroException } from "./KanroException"; export class HttpException extends KanroException { - public name: string = "Error.Kanro.Http"; - public status: number = undefined; + name: string = "Error.Kanro.Http"; + status: number = undefined; - constructor(status: number, message: string, innerException: Error = undefined) { + constructor(status: number, message: string, innerException?: Error) { super(message, innerException); this.status = status; } diff --git a/src/Exceptions/InsufficientStorageException.ts b/src/Exceptions/InsufficientStorageException.ts index f86c866..2866d14 100644 --- a/src/Exceptions/InsufficientStorageException.ts +++ b/src/Exceptions/InsufficientStorageException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class InsufficientStorageException extends HttpException { - public name: string = "Error.Kanro.Http.InsufficientStorage"; + name: string = "Error.Kanro.Http.InsufficientStorage"; - constructor(message: string = "Insufficient Storage", innerException: Error = undefined) { + constructor(message: string = "Insufficient Storage", innerException?: Error) { super(507, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/InternalServerErrorException.ts b/src/Exceptions/InternalServerErrorException.ts index 00d4add..038737f 100644 --- a/src/Exceptions/InternalServerErrorException.ts +++ b/src/Exceptions/InternalServerErrorException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class InternalServerErrorException extends HttpException { - public name: string = "Error.Kanro.Http.InternalServerError"; + name: string = "Error.Kanro.Http.InternalServerError"; - constructor(message: string = "Internal Server Error", innerException: Error = undefined) { + constructor(message: string = "Internal Server Error", innerException?: Error) { super(500, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/InvalidConfigException.ts b/src/Exceptions/InvalidConfigException.ts index 302b0cf..47451f9 100644 --- a/src/Exceptions/InvalidConfigException.ts +++ b/src/Exceptions/InvalidConfigException.ts @@ -3,7 +3,9 @@ import { KanroException } from "./KanroException"; export class InvalidConfigException extends KanroException { name: string = "Error.Kanro.Config.Invalid"; - constructor(config: string, message: string = undefined, innerException: Error = undefined) { - super(`Config '${config}' not matched with schema, check your config file${message == undefined ? "" : `, message: '${message}'`}.`, innerException); + constructor(config: string, message?: string, innerException?: Error) { + super( + `Config '${config}' not matched with schema, check your config file${message == null ? "" : `, message: '${message}'`}.`, + innerException); } } \ No newline at end of file diff --git a/src/Exceptions/InvalidModuleException.ts b/src/Exceptions/InvalidModuleException.ts index 748fe81..92e17f6 100644 --- a/src/Exceptions/InvalidModuleException.ts +++ b/src/Exceptions/InvalidModuleException.ts @@ -1,12 +1,12 @@ import { KanroException } from "./KanroException"; export class InvalidModuleException extends KanroException { - public name: string = "Error.Kanro.Module.Invalid"; - public module = undefined; + name: string = "Error.Kanro.Module.Invalid"; + module = undefined; - constructor(module: any, message: string = "Invalid Kanro module.", innerException: Error = undefined) { + constructor(module: any, message: string = "Invalid Kanro module.", innerException?: Error) { super(message, innerException); - if (module == undefined) { + if (module == null) { this.message = "Kanro module not found"; } this.module = module; diff --git a/src/Exceptions/InvalidNodeException.ts b/src/Exceptions/InvalidNodeException.ts index 0132da9..f4a608f 100644 --- a/src/Exceptions/InvalidNodeException.ts +++ b/src/Exceptions/InvalidNodeException.ts @@ -1,12 +1,12 @@ import { KanroException } from "./KanroException"; export class InvalidNodeException extends KanroException { - public name: string = "Error.Kanro.Node.Invalid"; - public node = undefined; + name: string = "Error.Kanro.Node.Invalid"; + node = undefined; - constructor(node: any, message: string = "Invalid Kanro node.", innerException: Error = undefined) { + constructor(node: any, message: string = "Invalid Kanro node.", innerException?: Error) { super(message, innerException); - if (node == undefined) { + if (node == null) { this.message = "Kanro node not found"; } this.node = node; diff --git a/src/Exceptions/InvalidRouterConfigException.ts b/src/Exceptions/InvalidRouterConfigException.ts index 41e615f..967e024 100644 --- a/src/Exceptions/InvalidRouterConfigException.ts +++ b/src/Exceptions/InvalidRouterConfigException.ts @@ -3,7 +3,7 @@ import { KanroException } from "./KanroException"; export class InvalidRouterConfigException extends KanroException { name: string = "Error.Kanro.RouterConfig.Invalid"; - constructor(message: string = "Invalid router config be provided.", innerException: Error = undefined) { + constructor(message: string = "Invalid router config be provided.", innerException?: Error) { super(message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/KanroException.ts b/src/Exceptions/KanroException.ts index 65d180b..9cda101 100644 --- a/src/Exceptions/KanroException.ts +++ b/src/Exceptions/KanroException.ts @@ -1,9 +1,9 @@ export class KanroException extends Error { - public name: string = "Error.Kanro"; - public message: string; - public innerException: Error; + name: string = "Error.Kanro"; + message: string; + innerException: Error; - constructor(message: string, innerException: Error = undefined) { + constructor(message: string, innerException?: Error) { super(message); this.message = message; this.innerException = innerException; diff --git a/src/Exceptions/MethodNotAllowedException.ts b/src/Exceptions/MethodNotAllowedException.ts index e84ac95..6657afb 100644 --- a/src/Exceptions/MethodNotAllowedException.ts +++ b/src/Exceptions/MethodNotAllowedException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class MethodNotAllowedException extends HttpException { - public name: string = "Error.Kanro.Http.MethodNotAllowed"; + name: string = "Error.Kanro.Http.MethodNotAllowed"; - constructor(message: string = "Method Not Allowed", innerException: Error = undefined) { + constructor(message: string = "Method Not Allowed", innerException?: Error) { super(405, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/NodeNotSupportedException.ts b/src/Exceptions/NodeNotSupportedException.ts index 0ef4f44..767a20c 100644 --- a/src/Exceptions/NodeNotSupportedException.ts +++ b/src/Exceptions/NodeNotSupportedException.ts @@ -2,13 +2,13 @@ import { KanroException } from "./KanroException"; import { INodeContainer, Node } from "../Core"; export class NodeNotSupportedException extends KanroException { - public name: string = "Error.Kanro.Node.NotSupported"; - public node: INodeContainer; + name: string = "Error.Kanro.Node.NotSupported"; + node: INodeContainer; - constructor(node: INodeContainer, message: string = undefined, innerException: Error = undefined) { + constructor(node: INodeContainer, message: string = undefined, innerException?: Error) { super(message, innerException); this.node = node; - if (message == undefined) { + if (message == null) { this.message = `Type of '${node.module.name}@${node.module.version}:${node.name}' is not supported.`; } } diff --git a/src/Exceptions/NonstandardNodeException.ts b/src/Exceptions/NonstandardNodeException.ts index d4d1715..327aefb 100644 --- a/src/Exceptions/NonstandardNodeException.ts +++ b/src/Exceptions/NonstandardNodeException.ts @@ -5,11 +5,10 @@ export class NonstandardNodeException extends KanroException { name: string = "Error.Kanro.Node.Nonstandard"; node: INodeContainer; - constructor(node: INodeContainer, message: string = undefined, innerException: Error = undefined) { + constructor(node: INodeContainer, message: string = undefined, innerException?: Error) { super(message, innerException); this.node = node; - - if (message == undefined) { + if (message == null) { message = `Nonstandard output has been given by '${this.node.module.name}@${this.node.module.version}:${this.node.name}'.`; } } diff --git a/src/Exceptions/NotAcceptableException.ts b/src/Exceptions/NotAcceptableException.ts index edc34ad..730e140 100644 --- a/src/Exceptions/NotAcceptableException.ts +++ b/src/Exceptions/NotAcceptableException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class NotAcceptableException extends HttpException { - public name: string = "Error.Kanro.Http.NotAcceptable"; + name: string = "Error.Kanro.Http.NotAcceptable"; - constructor(message: string = "Not Acceptable", innerException: Error = undefined) { + constructor(message: string = "Not Acceptable", innerException?: Error) { super(406, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/NotFoundException.ts b/src/Exceptions/NotFoundException.ts index 34a860b..dc49889 100644 --- a/src/Exceptions/NotFoundException.ts +++ b/src/Exceptions/NotFoundException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class NotFoundException extends HttpException { - public name: string = "Error.Kanro.Http.NotFound"; + name: string = "Error.Kanro.Http.NotFound"; - constructor(message: string = "Not Found", innerException: Error = undefined) { + constructor(message: string = "Not Found", innerException?: Error) { super(404, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/NotImplementedException.ts b/src/Exceptions/NotImplementedException.ts index a271d20..8577f3e 100644 --- a/src/Exceptions/NotImplementedException.ts +++ b/src/Exceptions/NotImplementedException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class NotImplementedException extends HttpException { - public name: string = "Error.Kanro.Http.NotImplemented"; + name: string = "Error.Kanro.Http.NotImplemented"; - constructor(message: string = "Not Implemented", innerException: Error = undefined) { + constructor(message: string = "Not Implemented", innerException?: Error) { super(501, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/ProxyAuthenticationRequiredException.ts b/src/Exceptions/ProxyAuthenticationRequiredException.ts index 5cc041b..c0c9f03 100644 --- a/src/Exceptions/ProxyAuthenticationRequiredException.ts +++ b/src/Exceptions/ProxyAuthenticationRequiredException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class ProxyAuthenticationRequiredException extends HttpException { - public name: string = "Error.Kanro.Http.ProxyAuthenticationRequired"; + name: string = "Error.Kanro.Http.ProxyAuthenticationRequired"; - constructor(message: string = "Proxy Authentication Required", innerException: Error = undefined) { + constructor(message: string = "Proxy Authentication Required", innerException?: Error) { super(407, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/RemoteServiceException.ts b/src/Exceptions/RemoteServiceException.ts new file mode 100644 index 0000000..93905d0 --- /dev/null +++ b/src/Exceptions/RemoteServiceException.ts @@ -0,0 +1,9 @@ +import { KanroException } from "./KanroException"; + +export class RemoteServiceException extends KanroException { + name: string = "Error.Kanro.RemoteService"; + + constructor(operation: string, innerException?: Error) { + super(`Local only operation(${operation}) for remote service.`, innerException); + } +} \ No newline at end of file diff --git a/src/Exceptions/RequestTimeoutException.ts b/src/Exceptions/RequestTimeoutException.ts index db88f79..9760e7d 100644 --- a/src/Exceptions/RequestTimeoutException.ts +++ b/src/Exceptions/RequestTimeoutException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class RequestTimeoutException extends HttpException { - public name: string = "Error.Kanro.Http.RequestTimeout"; + name: string = "Error.Kanro.Http.RequestTimeout"; - constructor(message: string = "Request Timeout", innerException: Error = undefined) { + constructor(message: string = "Request Timeout", innerException?: Error) { super(408, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/ServiceUnavailableException.ts b/src/Exceptions/ServiceUnavailableException.ts index ea79f7d..cacd8bd 100644 --- a/src/Exceptions/ServiceUnavailableException.ts +++ b/src/Exceptions/ServiceUnavailableException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class ServiceUnavailableException extends HttpException { - public name: string = "Error.Kanro.Http.ServiceUnavailable"; + name: string = "Error.Kanro.Http.ServiceUnavailable"; - constructor(message: string = "Service Unavailable", innerException: Error = undefined) { + constructor(message: string = "Service Unavailable", innerException?: Error) { super(503, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/UnauthorizedException.ts b/src/Exceptions/UnauthorizedException.ts index 2406cd5..84cda74 100644 --- a/src/Exceptions/UnauthorizedException.ts +++ b/src/Exceptions/UnauthorizedException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class UnauthorizedException extends HttpException { - public name: string = "Error.Kanro.Http.Unauthorized"; + name: string = "Error.Kanro.Http.Unauthorized"; - constructor(message: string = "Unauthorized", innerException: Error = undefined) { + constructor(message: string = "Unauthorized", innerException?: Error) { super(401, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/UnexpectedNodeException.ts b/src/Exceptions/UnexpectedNodeException.ts index f993939..6203ea0 100644 --- a/src/Exceptions/UnexpectedNodeException.ts +++ b/src/Exceptions/UnexpectedNodeException.ts @@ -5,7 +5,7 @@ export class UnexpectedNodeException extends KanroException { name: string = "Error.Kanro.Node.Unexpected"; node: INodeContainer; - constructor(node: INodeContainer, message: string = "Unexpected input or output node be provided.", innerException: Error = undefined) { + constructor(node: INodeContainer, message: string = "Unexpected input or output node be provided.", innerException?: Error) { super(message, innerException); this.node = node; } diff --git a/src/Exceptions/VersionNotSupportedException.ts b/src/Exceptions/VersionNotSupportedException.ts index ab111ce..39c712e 100644 --- a/src/Exceptions/VersionNotSupportedException.ts +++ b/src/Exceptions/VersionNotSupportedException.ts @@ -1,9 +1,9 @@ import { HttpException } from "./HttpException"; export class VersionNotSupportedException extends HttpException { - public name: string = "Error.Kanro.Http.VersionNotSupported"; + name: string = "Error.Kanro.Http.VersionNotSupported"; - constructor(message: string = "HTTP Version Not Supported", innerException: Error = undefined) { + constructor(message: string = "HTTP Version Not Supported", innerException?: Error) { super(505, message, innerException); } } \ No newline at end of file diff --git a/src/Exceptions/index.ts b/src/Exceptions/index.ts index 275282f..a55cf8a 100644 --- a/src/Exceptions/index.ts +++ b/src/Exceptions/index.ts @@ -27,4 +27,5 @@ export * from "./InvalidConfigException"; export * from "./InvalidNodeException"; export * from "./InvalidModuleException"; export * from "./InvalidRouterConfigException"; -export * from "./NonstandardNodeException"; \ No newline at end of file +export * from "./NonstandardNodeException"; +export * from "./RemoteServiceException"; \ No newline at end of file diff --git a/src/Http/FileResponseBody.ts b/src/Http/FileResponseBody.ts index 9b4880c..f7f68d3 100644 --- a/src/Http/FileResponseBody.ts +++ b/src/Http/FileResponseBody.ts @@ -1,23 +1,21 @@ -import { IResponseBody } from "./IResponseBody"; - -import * as Http from "http"; import * as FileCore from "fs"; import * as FileType from "file-type"; +import * as Http from "http"; import * as MimeType from "mime-types"; import * as ReadChunk from "read-chunk"; +import { IResponseBody } from "./IResponseBody"; import { Path } from "../IO"; export class FileResponseBody implements IResponseBody { path: string; async write(response: Http.ServerResponse): Promise { - let ext = Path.extname(this.path); + let ext: string = Path.extname(this.path); if (ext) { response.setHeader("content-type", MimeType.contentType(ext)); - } - else { - let buffer = await ReadChunk(this.path, 0, 4100); + } else { + let buffer: Buffer = await ReadChunk(this.path, 0, 4100); response.setHeader("content-type", FileType(buffer).mime); } diff --git a/src/Http/IHttpHeader.ts b/src/Http/IHttpHeader.ts index edd1a63..43da7e6 100644 --- a/src/Http/IHttpHeader.ts +++ b/src/Http/IHttpHeader.ts @@ -1,9 +1,9 @@ /** * HTTP header structure. - * + * * @export * @interface IHttpHeader */ export interface IHttpHeader { - [name: string]: string; + [name: string]: string | string[]; } \ No newline at end of file diff --git a/src/Http/IHttpParam.ts b/src/Http/IHttpParam.ts index 4fff201..5281f45 100644 --- a/src/Http/IHttpParam.ts +++ b/src/Http/IHttpParam.ts @@ -1,6 +1,6 @@ /** * HTTP param structure. - * + * * @export * @interface IHttpParam */ diff --git a/src/Http/IRequest.ts b/src/Http/IRequest.ts index 4672f14..d678a07 100644 --- a/src/Http/IRequest.ts +++ b/src/Http/IRequest.ts @@ -1,55 +1,55 @@ import * as Http from "http"; import { IHttpHeader } from "./IHttpHeader"; -import { IUrlQuery } from "./IUrlQuery"; import { IHttpParam } from "./IHttpParam"; -import { IResponse } from "./IResponse"; import { INodeContainer, Node } from "../Core"; +import { IResponse } from "./IResponse"; +import { IUrlQuery } from "./IUrlQuery"; /** * A HTTP request. - * + * * @export * @interface IRequest */ export interface IRequest { /** * Meta data of request. - * + * * @type {Web.IncomingMessage} * @memberOf IRequest */ meta: Http.IncomingMessage; /** * Header information of request. - * + * * @type {IHttpHeader} * @memberOf IRequest */ header: IHttpHeader; /** * Url query information of request. - * + * * @type {IUrlQuery} * @memberOf IRequest */ query: IUrlQuery; /** * Url of request, only include the path. - * + * * @type {string} * @memberOf IRequest */ url: string; /** * Method of HTTP request, 'GET', 'POST' or other something. - * + * * @type {string} * @memberOf IRequest */ method: string; /** * Param of HTTP, router url param, body parser result can be stored here. - * + * * @type {IHttpParam} * @memberOf IRequest */ @@ -57,7 +57,7 @@ export interface IRequest { /** * Nodes which have handled this request, it is very useful for debug. - * + * * @type {INodeContainer[]} * @memberOf IRequest */ @@ -67,17 +67,17 @@ export interface IRequest { /** * Copy a request, but the copy of this request will not include meta information. - * - * @returns {IRequest} - * + * + * @returns {IRequest} + * * @memberOf IRequest */ fork(): IRequest; /** * Convert this request to a response. - * - * @returns {IResponse} - * + * + * @returns {IResponse} + * * @memberOf IRequest */ respond(): IResponse; diff --git a/src/Http/IResponse.ts b/src/Http/IResponse.ts index ec2c94c..646d237 100644 --- a/src/Http/IResponse.ts +++ b/src/Http/IResponse.ts @@ -1,54 +1,54 @@ import * as Http from "http"; +import { IHttpHeader } from "./IHttpHeader"; import { INodeContainer, Node } from "../Core"; import { IRequest } from "./IRequest"; import { IResponseBody } from "./IResponseBody"; -import { IHttpHeader } from "./IHttpHeader"; /** * A HTTP response. - * + * * @export * @interface IResponse */ export interface IResponse { /** * Meta data of response. - * + * * @type {Web.ServerResponse} * @memberOf IResponse */ meta: Http.ServerResponse; /** * Request information. - * + * * @type {IRequest} * @memberOf IResponse */ request: IRequest; /** * Header information of response. - * + * * @type {IHttpHeader} * @memberOf IResponse */ header: IHttpHeader; /** * Body of response. - * + * * @type {IResponseBody} * @memberOf IResponse */ body: IResponseBody; /** * Status code of response. - * + * * @type {number} * @memberOf IResponse */ status: number; /** * Nodes which have handled this request, it is very useful for debug, it will sync with 'request.traceStack'. - * + * * @type {Config.INodeConfig[]} * @memberOf IResponse */ diff --git a/src/Http/IResponseBody.ts b/src/Http/IResponseBody.ts index c2bbe2a..dc4bf04 100644 --- a/src/Http/IResponseBody.ts +++ b/src/Http/IResponseBody.ts @@ -2,17 +2,17 @@ import * as Http from "http"; /** * Body of HTTP response. - * + * * @export * @interface IResponseBody */ export interface IResponseBody { /** * Write data to response. - * - * @param {Web.ServerResponse} response - * @returns {Promise} - * + * + * @param {Web.ServerResponse} response + * @returns {Promise} + * * @memberOf IResponseBody */ write(response: Http.ServerResponse): Promise; diff --git a/src/Http/IUrlQuery.ts b/src/Http/IUrlQuery.ts index 8cb2215..e373dbb 100644 --- a/src/Http/IUrlQuery.ts +++ b/src/Http/IUrlQuery.ts @@ -1,9 +1,10 @@ +import { ParsedUrlQuery } from "querystring"; + /** * Url query structure. - * + * * @export * @interface IUrlQuery */ -export interface IUrlQuery { - [name: string]: any; +export interface IUrlQuery extends ParsedUrlQuery { } \ No newline at end of file diff --git a/src/Http/JsonResponseBody.ts b/src/Http/JsonResponseBody.ts index c0e0e56..6cd31ba 100644 --- a/src/Http/JsonResponseBody.ts +++ b/src/Http/JsonResponseBody.ts @@ -1,22 +1,21 @@ -import { IResponseBody } from "./IResponseBody"; - import * as Http from "http"; +import { IResponseBody } from "./IResponseBody"; /** -* Json response body, it will return a object as json to client. -* -* @export -* @class JsonResponseBody -* @implements {IResponseBody} -*/ + * Json response body, it will return a object as json to client. + * + * @export + * @class JsonResponseBody + * @implements {IResponseBody} + */ export class JsonResponseBody implements IResponseBody { data: any; /** * Write object as json to response. - * - * @param {Web.ServerResponse} response - * @returns {Promise} - * + * + * @param {Web.ServerResponse} response + * @returns {Promise} + * * @memberOf JsonResponseBody */ async write(response: Http.ServerResponse): Promise { @@ -24,14 +23,14 @@ export class JsonResponseBody implements IResponseBody { await new Promise((res, rej) => { response.write(JSON.stringify(this.data), () => { res(); - }) - }) + }); + }); } /** * Creates an instance of JsonResponseBody. * @param {*} data Body object. - * + * * @memberOf JsonResponseBody */ constructor(data: any) { diff --git a/src/Http/Request.ts b/src/Http/Request.ts index 0f8acd7..271b908 100644 --- a/src/Http/Request.ts +++ b/src/Http/Request.ts @@ -1,22 +1,22 @@ import * as Http from "http"; -import * as Url from "url"; import * as QueryString from "querystring"; +import * as Url from "url"; +import { IHttpHeader } from "./IHttpHeader"; +import { IHttpParam } from "./IHttpParam"; import { INodeContainer, Node } from "../Core"; -import { ObjectUtils } from "../Utils"; import { IRequest } from "./IRequest"; -import { RequestMirror } from "./RequestMirror"; import { IResponse } from "./IResponse"; -import { Response } from "./Response"; -import { IHttpParam } from "./IHttpParam"; -import { IHttpHeader } from "./IHttpHeader"; import { IUrlQuery } from "./IUrlQuery"; +import { ObjectUtils } from "../Utils"; +import { RequestMirror } from "./RequestMirror"; +import { Response } from "./Response"; export class Request implements IRequest { fork(): IRequest { return new RequestMirror(this); } respond(): IResponse { - if (this.$response == undefined) { + if (this.$response == null) { this.$response = new Response(this); } @@ -31,7 +31,7 @@ export class Request implements IRequest { method: string; traceStack: INodeContainer[]; - get relativeUrl(): String{ + get relativeUrl(): String { return this.routerKey.slice(this.routerIndex).join("/"); } @@ -45,14 +45,14 @@ export class Request implements IRequest { this.meta = httpRequest; this.$responseMeta = httpResponse; - let url = Url.parse(httpRequest.url); - this.query = QueryString.parse(url.query); + let url: Url.Url = Url.parse(httpRequest.url, true); + this.query = url.query; this.url = url.pathname; - let skipStart = url.pathname.startsWith("/") ? 1 : 0; - let skipEnd = url.pathname.endsWith("/") ? 1 : 0; - let formattedUrl = url.pathname.slice(skipStart, url.pathname.length - skipStart - skipEnd + 1); - this.routerKey = formattedUrl == "" ? [] : url.pathname.slice(skipStart, url.pathname.length - skipStart - skipEnd + 1).split("/"); + let skipStart: number = url.pathname.startsWith("/") ? 1 : 0; + let skipEnd: number = url.pathname.endsWith("/") ? 1 : 0; + let formattedUrl: string = url.pathname.slice(skipStart, url.pathname.length - skipStart - skipEnd + 1); + this.routerKey = formattedUrl === "" ? [] : url.pathname.slice(skipStart, url.pathname.length - skipStart - skipEnd + 1).split("/"); this.routerIndex = 0; this.header = ObjectUtils.copy(httpRequest.headers); diff --git a/src/Http/RequestContext.ts b/src/Http/RequestContext.ts new file mode 100644 index 0000000..ab341a7 --- /dev/null +++ b/src/Http/RequestContext.ts @@ -0,0 +1,28 @@ +import { INodeContainer, Node } from "../Core"; +import { Request } from "./Request"; +import { RequestMirror } from "./RequestMirror"; +import { Response } from "./Response"; + +export class RequestContext { + request: Request | RequestMirror; + response: Response; + error?: Error; + traceStack: INodeContainer[]; + time: number; + + constructor(request: Request | RequestMirror) { + this.request = request; + this.traceStack = []; + this.time = Date.now(); + } + + fork(request: Request | RequestMirror, response: Response): RequestContext { + let result: RequestContext = new RequestContext(request); + result.response = response; + result.error = this.error; + result.traceStack = this.traceStack.slice(0); + result.time = this.time; + + return result; + } +} \ No newline at end of file diff --git a/src/Http/RequestMirror.ts b/src/Http/RequestMirror.ts index 76886cf..197b0ab 100644 --- a/src/Http/RequestMirror.ts +++ b/src/Http/RequestMirror.ts @@ -1,11 +1,11 @@ -import { ObjectUtils } from "../Utils" import * as Http from "http"; +import { IHttpHeader } from "./IHttpHeader"; +import { IHttpParam } from "./IHttpParam"; import { INodeContainer, Node } from "../Core"; import { IRequest } from "./IRequest"; -import { IHttpParam } from "./IHttpParam"; -import { IHttpHeader } from "./IHttpHeader"; -import { IUrlQuery } from "./IUrlQuery"; import { IResponse } from "./IResponse"; +import { IUrlQuery } from "./IUrlQuery"; +import { ObjectUtils } from "../Utils"; import { Response } from "./Response"; export class RequestMirror implements IRequest { @@ -29,7 +29,7 @@ export class RequestMirror implements IRequest { return new RequestMirror(this); } respond(): IResponse { - if (this.$response == undefined) { + if (this.$response == null) { this.$response = new Response(this); } @@ -37,12 +37,13 @@ export class RequestMirror implements IRequest { } constructor(request: IRequest) { - for (var key in request) { - if (key.startsWith("$") || key == "meta" || typeof request[key] == "function") { - break; + for (let key in request) { + if(request.hasOwnProperty(key)) { + if (key.startsWith("$") || key === "meta" || typeof request[key] === "function") { + break; + } + this[key] = ObjectUtils.copy(request[key]); } - - this[key] = ObjectUtils.copy(request[key]); } this.traceStack = [].concat(request.traceStack); diff --git a/src/Http/Response.ts b/src/Http/Response.ts index 33cc158..15a7f69 100644 --- a/src/Http/Response.ts +++ b/src/Http/Response.ts @@ -1,11 +1,10 @@ -import { ObjectUtils } from "../Utils" - import * as Http from "http"; +import { IHttpHeader } from "./IHttpHeader"; import { INodeContainer, Node } from "../Core"; -import { IResponse } from "./IResponse"; import { IRequest } from "./IRequest"; -import { IHttpHeader } from "./IHttpHeader"; +import { IResponse } from "./IResponse"; import { IResponseBody } from "./IResponseBody"; +import { ObjectUtils } from "../Utils"; export class Response implements IResponse { meta: Http.ServerResponse; @@ -16,6 +15,7 @@ export class Response implements IResponse { traceStack: INodeContainer[]; constructor(request: IRequest) { + // tslint:disable-next-line:no-string-literal this.meta = request["$responseMeta"]; this.request = request; this.status = 200; diff --git a/src/Http/Server.ts b/src/Http/Server.ts new file mode 100644 index 0000000..f1ac1a4 --- /dev/null +++ b/src/Http/Server.ts @@ -0,0 +1,57 @@ +import * as Http from "http"; +import { RequestContext } from "./RequestContext"; +import { Request } from "./Request"; + +export interface IHttpRequestHandler { + (context: RequestContext): Promise; +} + +export class Server { + private httpServer: Http.Server; + private handler: (request: Http.IncomingMessage, response: Http.ServerResponse) => Promise; + private eventHandler: (name: string, error: any) => Promise; + private port: number; + + constructor(port: number, handler: (request: Http.IncomingMessage, response: Http.ServerResponse) => Promise, + eventHandler: (name: string, error: any) => Promise) { + this.port = port; + this.handler = handler; + this.eventHandler = eventHandler; + } + + async startListen(): Promise { + await new Promise((res, rej) => { + this.httpServer = Http.createServer(async (request, response) => { + this.entryPoint(request, response); + }); + this.httpServer.on("error", async (err) => { + await this.eventHandler("error", err); + }); + this.httpServer.on("listening", async (err) => { + await this.eventHandler("listening", err); + res(); + }); + this.httpServer.on("close", async (err) => { + await this.eventHandler("close", err); + }); + this.httpServer.listen(this.port); + }); + } + + hotSwap(handler: (request: Http.IncomingMessage, response: Http.ServerResponse) => Promise, + eventHandler: (name: string, error: any) => Promise): void { + this.handler = handler; + this.eventHandler = eventHandler; + } + + private async entryPoint(request: Http.IncomingMessage, response: Http.ServerResponse): Promise { + if (this.handler != null) { + await this.handler(request, response); + } + response.end(); + } + + close(): void { + this.httpServer.close(); + } +} \ No newline at end of file diff --git a/src/Http/StringResponseBody.ts b/src/Http/StringResponseBody.ts index e07e6c6..8a835b5 100644 --- a/src/Http/StringResponseBody.ts +++ b/src/Http/StringResponseBody.ts @@ -3,20 +3,20 @@ import { IResponseBody } from "./IResponseBody"; import * as Http from "http"; /** -* String response body, it will return a object as json to client. -* -* @export -* @class StringResponseBody -* @implements {IResponseBody} -*/ + * String response body, it will return a object as json to client. + * + * @export + * @class StringResponseBody + * @implements {IResponseBody} + */ export class StringResponseBody implements IResponseBody { data: any; /** * Write string to response. - * - * @param {Web.ServerResponse} response - * @returns {Promise} - * + * + * @param {Web.ServerResponse} response + * @returns {Promise} + * * @memberOf StringResponseBody */ async write(response: Http.ServerResponse): Promise { @@ -24,14 +24,14 @@ export class StringResponseBody implements IResponseBody { await new Promise((res, rej) => { response.write(this.data, () => { res(); - }) - }) + }); + }); } /** * Creates an instance of StringResponseBody. * @param {*} data Body object. - * + * * @memberOf StringResponseBody */ constructor(data: any) { diff --git a/src/Http/index.ts b/src/Http/index.ts index dc30fa9..62d828e 100644 --- a/src/Http/index.ts +++ b/src/Http/index.ts @@ -8,4 +8,6 @@ export * from "./IUrlQuery"; export * from "./JsonResponseBody"; export * from "./Request"; export * from "./RequestMirror"; -export * from "./Response"; \ No newline at end of file +export * from "./Response"; +export * from "./Server"; +export * from "./RequestContext"; \ No newline at end of file diff --git a/src/HttpServer.ts b/src/HttpServer.ts index ceffb0c..209550c 100644 --- a/src/HttpServer.ts +++ b/src/HttpServer.ts @@ -1,80 +1,90 @@ import * as Http from "http"; -import { RequestContext } from "./RequestContext"; -import { Request } from "./Http"; -import { INodeContainer, Node } from "./Core/index"; +import { Request, IHttpRequestHandler, RequestContext, Server } from "./Http"; +import { INodeContainer, Node, Service, IModuleInfo } from "./Core"; import { LoggerManager } from "./LoggerManager"; -import { AnsiStyle, Colors, Style } from "./Logging/index"; +import { AnsiStyle, Colors, Style, ILogger } from "./Logging"; import { Application, HttpMethod } from "./Application"; +import { KanroInternalModule } from "./KanroInternalModule"; -let httpLogger = LoggerManager.current.registerLogger("HTTP", AnsiStyle.create().foreground(Colors.yellow)); +export class HttpServer extends Service { + private httpServer: Server; + private handler: IHttpRequestHandler; + private port: number; + private preHttpServer: HttpServer; -export interface IHttpRequestHandler { - (context: RequestContext): Promise; -} + dependencies = { + loggerManager: { + name: LoggerManager.name, + module: KanroInternalModule.moduleInfo + }, + application: { + name: Application.name, + module: KanroInternalModule.moduleInfo + } + }; -export class HttpServer { - private httpServer: Http.Server; - private handler: IHttpRequestHandler; + readonly isProxable: boolean = false; - private constructor() { + constructor() { + super(undefined); } - private static instance: HttpServer; - - static get current(): HttpServer { - return HttpServer.instance; + async onLoaded(): Promise { + this.application = await this.getDependedService("application"); + this.httpLogger = await (await this.getDependedService("loggerManager")) + .registerLogger("HTTP", AnsiStyle.create().foreground(Colors.yellow)); } - static async initialize(port: number, handler: IHttpRequestHandler): Promise { - if (HttpServer.instance != undefined) { - return HttpServer.instance; - } + private application: Application; + + private httpLogger: ILogger; - HttpServer.instance = new HttpServer(); - HttpServer.instance.handler = handler; + async initialize(port: number, handler: IHttpRequestHandler, httpServer?: HttpServer): Promise { + this.port = port; + this.handler = handler; + this.preHttpServer = httpServer; - await new Promise((res, rej) => { - HttpServer.instance.httpServer = Http.createServer(async (request, response) => { - HttpServer.instance.entryPoint(request, response); + if (httpServer != null && httpServer.port === port) { + this.httpLogger.info(`Hot swapping http server...`); + this.httpServer = httpServer.httpServer; + this.httpServer.hotSwap(async (request, response) => { + await this.entryPoint(request, response); + }, async (name, error) => { + await this.eventHandler(name, error); }); - HttpServer.instance.httpServer.on('error', (err) => { - httpLogger.error(`Error in http server, message: '${err.message}'`); - Application.current.die(err, "HTTP"); + } else { + this.httpServer = new Server(port, async (request, response) => { + await this.entryPoint(request, response); + }, async (name, error) => { + await this.eventHandler(name, error); }); - HttpServer.instance.httpServer.on('listening', (err) => { - if (err) { - httpLogger.error(`Create http server fail, message: '${err.message}'`); - Application.current.die(err, "HTTP"); - } - httpLogger.success(`Http server listening on '${port}'.`); - res(); - }); - HttpServer.instance.httpServer.listen(port); - }); + await this.httpServer.startListen(); + } - return HttpServer.instance; + return this; } - private async entryPoint(request: Http.IncomingMessage, response: Http.ServerResponse) { - let context = new RequestContext(new Request(request, response)); + private async entryPoint(request: Http.IncomingMessage, response: Http.ServerResponse): Promise { + let context: RequestContext = new RequestContext(new Request(request, response)); try { context = await this.handler(context); - if (context.response == undefined) { + if (context.response == null) { response.statusCode = 404; response.end(); - } - else { - if (context.response.status != undefined) { + } else { + if (context.response.status != null) { response.statusCode = context.response.status; } - if (context.response.header != undefined) { - for (var key in context.response.header) { - response.setHeader(key, context.response.header[key]); + if (context.response.header != null) { + for (let key in context.response.header) { + if (context.response.header.hasOwnProperty(key)) { + response.setHeader(key, context.response.header[key]); + } } } - if (context.response.body != undefined) { + if (context.response.body != null) { await context.response.body.write(response); } response.end(); @@ -82,64 +92,81 @@ export class HttpServer { } catch (error) { response.statusCode = 500; response.end(); - httpLogger.error(`Uncaught exception thrown in HTTP handler, message :'${error.message}'`) + this.httpLogger.error(`Uncaught exception thrown in HTTP handler, message :'${error.message}'`); } - httpLogger.info(this.buildHttpLogMessage(context)); + this.httpLogger.info(this.buildHttpLogMessage(context)); } + private async eventHandler(name: string, error: any): Promise { + switch (name) { + case "error": + this.httpLogger.error(`Error in http server, message: '${error.message}'`); + this.application.die(error, "HTTP"); + break; + case "listening": + if (error) { + this.httpLogger.error(`Create http server fail, message: '${error.message}'`); + this.application.die(error, "HTTP"); + } + this.httpLogger.success(`Http server listening on '${this.port}'.`); + + if (!error && this.preHttpServer != null) { + this.httpLogger.info(`Deprecated http server will be closed in 1 minute.`); + setTimeout(() => { + this.preHttpServer.httpServer.close(); + this.preHttpServer = null; + }, 60000); + } + break; + case "close": + this.httpLogger.info(`Deprecated http server on '${this.port}' closed.`); + + break; + } + } - private buildHttpLogMessage(context: RequestContext) { + private buildHttpLogMessage(context: RequestContext): string { let methodColor: Colors; let timeColor: Colors; let statusColor: Colors; - if (HttpMethod[context.request.method.toLowerCase()] != undefined) { + if (HttpMethod[context.request.method.toLowerCase()] != null) { methodColor = HttpMethod[context.request.method.toLowerCase()]; - } - else { + } else { methodColor = Colors.white; } - let costTime = Date.now() - context.time; + let costTime: number = Date.now() - context.time; if (costTime < 10) { timeColor = Colors.green; - } - else if (costTime < 50) { + } else if (costTime < 50) { timeColor = Colors.cyan; - } - else if (costTime < 100) { + } else if (costTime < 100) { timeColor = Colors.blue; - } - else if (costTime < 500) { + } else if (costTime < 500) { timeColor = Colors.yellow; - } - else if (costTime < 1000) { + } else if (costTime < 1000) { timeColor = Colors.red; - } - else { + } else { timeColor = Colors.magenta; } if (context.response.status >= 500) { statusColor = Colors.red; - } - else if (context.response.status >= 400) { + } else if (context.response.status >= 400) { statusColor = Colors.yellow; - } - else if (context.response.status >= 300) { + } else if (context.response.status >= 300) { statusColor = Colors.cyan; - } - else if (context.response.status >= 200) { + } else if (context.response.status >= 200) { statusColor = Colors.green; - } - else if (context.response.status >= 100) { + } else if (context.response.status >= 100) { statusColor = Colors.blue; - } - else { + } else { statusColor = Colors.magenta; } + // tslint:disable-next-line:max-line-length return Style`${AnsiStyle.create().foreground(methodColor)}${context.request.method} ${context.request.url} ${AnsiStyle.create().foreground(statusColor)}${context.response.status} ${AnsiStyle.create().foreground(timeColor)}${`${costTime}ms`} `; } } \ No newline at end of file diff --git a/src/IAppConfig.ts b/src/IAppConfig.ts index 4567d35..aa57301 100644 --- a/src/IAppConfig.ts +++ b/src/IAppConfig.ts @@ -1,8 +1,8 @@ -import { IModuleInfo, RequestHandler, RequestReplicator, RequestDiverter, ResponseHandler, INodeContainer, Service } from "./Core"; +import { IModuleInfo, INodeContainer, RequestDiverter, RequestHandler, RequestReplicator, ResponseHandler, Service } from "./Core"; /** * Config of Kanro APP. - * + * * @export * @interface IAppConfig */ @@ -10,7 +10,7 @@ export interface IAppConfig { /** * The port what Kanro HTTP server will listen. - * + * * @type {number} * @memberOf IAppConfig */ @@ -18,7 +18,7 @@ export interface IAppConfig { /** * The NPM registry what module manager will used. - * + * * @type {string} * @memberOf IAppConfig */ @@ -26,7 +26,7 @@ export interface IAppConfig { /** * The static resource dir of app. - * + * * @type {string} * @memberOf IAppConfig */ @@ -38,7 +38,7 @@ export interface IAppConfig { /** * The http request entry point. - * + * * @type {(INodeContainer)} * @memberof IAppConfig */ @@ -46,7 +46,7 @@ export interface IAppConfig { /** * The http response exit point. - * + * * @type {INodeContainer} * @memberof IAppConfig */ diff --git a/src/IO/File.ts b/src/IO/File.ts index 9301ee5..b82b5ab 100644 --- a/src/IO/File.ts +++ b/src/IO/File.ts @@ -1,6 +1,6 @@ -import { AsyncUtils } from "../Utils"; import * as FileModule from "fs"; import * as Path from "path"; +import { AsyncUtils } from "../Utils"; export class File { static async unlink(path: string | Buffer): Promise { @@ -16,7 +16,7 @@ export class File { } static async readJson(path: string): Promise { - let data = await AsyncUtils.promise(FileModule.readFile, undefined, path); + let data: Buffer = await AsyncUtils.promise(FileModule.readFile, undefined, path); return JSON.parse(data.toString()); } @@ -32,7 +32,7 @@ export class File { return await AsyncUtils.promise(FileModule.symlink, undefined, path, target, type); } - static async createDir(path: string) { + static async createDir(path: string): Promise { if (await File.exists(path)) { return; } @@ -41,7 +41,7 @@ export class File { await AsyncUtils.promise(FileModule.mkdir, undefined, path); } - static createDirSync(path: string) { + static createDirSync(path: string): void { if (FileModule.existsSync(path)) { return; } diff --git a/src/KanroInternalModule.ts b/src/KanroInternalModule.ts new file mode 100644 index 0000000..190f60e --- /dev/null +++ b/src/KanroInternalModule.ts @@ -0,0 +1,61 @@ +import { Application } from "./Application"; +import { ConfigBuilder } from "./ConfigBuilder"; +import { HttpServer } from "./HttpServer"; +import { IModuleInfo, INodeContainer, Module, Node } from "./Core"; +import { LoggerManager } from "./LoggerManager"; +import { ModuleManager } from "./ModuleManager"; +import { NpmClient } from "./NpmClient"; + +export class KanroInternalModule extends Module { + application: Application; + npmClient: NpmClient; + moduleManager: ModuleManager; + loggerManager: LoggerManager; + configBuilder: ConfigBuilder; + httpServer: HttpServer; + + constructor(application: Application) { + super(); + this.application = application; + this.npmClient = new NpmClient(); + this.moduleManager = new ModuleManager(); + this.loggerManager = new LoggerManager(); + this.configBuilder = new ConfigBuilder(); + this.httpServer = new HttpServer(); + } + + get nodes(): Array { + return [ + Application.name, + NpmClient.name, + HttpServer.name, + ModuleManager.name, + LoggerManager.name, + ConfigBuilder.name, + ]; + } + + async getNode(container: INodeContainer): Promise { + switch (container.name) { + case Application.name: + return this.application; + case NpmClient.name: + return this.npmClient; + case HttpServer.name: + return this.httpServer; + case ModuleManager.name: + return this.moduleManager; + case LoggerManager.name: + return this.loggerManager; + case ConfigBuilder.name: + return this.configBuilder; + default: + return undefined; + } + } + + static moduleInfo: IModuleInfo = { + name: "kanro.internal", + version: "*" + }; +} \ No newline at end of file diff --git a/src/KanroManager.ts b/src/KanroManager.ts index 72c68ba..c0516da 100644 --- a/src/KanroManager.ts +++ b/src/KanroManager.ts @@ -1,27 +1,36 @@ -import { Service } from "./Core"; +import { AnsiStyle, Colors, ILogger, Logger } from "./Logging"; +import { Application } from "./Application"; import { IAppConfig } from "./IAppConfig"; -import { Logger, Colors, AnsiStyle, ILogger } from "./Logging"; +import { IModuleInfo, Service } from "./Core"; +import { KanroInternalModule } from "./KanroInternalModule"; import { LoggerManager } from "./LoggerManager"; -import { Application } from "./Application"; export class KanroManager extends Service { - public get application(): Application { - return Application.current; - } + dependencies = { + loggerManager: { + name: LoggerManager.name, + module: KanroInternalModule.moduleInfo + }, + application: { + name: Application.name, + module: KanroInternalModule.moduleInfo + } + }; - constructor() { - super({ name: KanroManager.name, module: { name: "kanro", version: "*" } }); - } + readonly isProxable: boolean = false; + private application: Application; + private loggerManager: LoggerManager; - async reloadConfigs(configs: IAppConfig): Promise { - Application.current.reloadConfigs(configs); + async onLoaded(): Promise { + this.application = await this.getDependedService("application"); + this.loggerManager = await this.getDependedService("loggerManager"); } - getKanroConfig(key: string): any { - return Application.current.config[key]; + async getKanroConfig(name: string): Promise { + return this.application.config[name]; } - registerLogger(namespace: string, style: AnsiStyle): ILogger { - return LoggerManager.current.registerLogger(namespace, style); + async registerLogger(namespace: string, style?: AnsiStyle): Promise { + return this.loggerManager.registerLogger(namespace, style); } } \ No newline at end of file diff --git a/src/KanroModule.ts b/src/KanroModule.ts index 71d15c4..208c2a0 100644 --- a/src/KanroModule.ts +++ b/src/KanroModule.ts @@ -1,10 +1,23 @@ -import { Module, INodeContainer, Node } from "./Core"; +import { Application } from "./Application"; import { FileRenderer, HttpExceptionRenderer, JsonRenderer } from "./Primitives"; -import { Router, MethodRouter } from "./Router"; +import { IModuleInfo, INodeContainer, Module, Node } from "./Core"; import { KanroManager } from "./KanroManager"; -import { Application } from "./Application"; +import { MethodRouter, UrlRouter } from "./Router"; +import { RemoteServiceContainer, RemoteServiceHolder } from "./Remoting"; export class KanroModule extends Module { + readonly nodes: Array = + [ + FileRenderer.name, + HttpExceptionRenderer.name, + JsonRenderer.name, + UrlRouter.name, + MethodRouter.name, + KanroManager.name, + RemoteServiceHolder.name, + RemoteServiceContainer.name, + ]; + async getNode(container: INodeContainer): Promise { switch (container.name) { @@ -14,14 +27,23 @@ export class KanroModule extends Module { return new HttpExceptionRenderer(container); case JsonRenderer.name: return new JsonRenderer(container); - case Router.name: - return new Router(container); + case UrlRouter.name: + return new UrlRouter(container); case MethodRouter.name: return new MethodRouter(container); case KanroManager.name: - return new KanroManager(); + return new KanroManager(container); + case RemoteServiceHolder.name: + return new RemoteServiceHolder(container); + case RemoteServiceContainer.name: + return new RemoteServiceContainer(container); default: return undefined; } } + + static moduleInfo: IModuleInfo = { + name: "kanro", + version: "*" + }; } \ No newline at end of file diff --git a/src/LoggerManager.ts b/src/LoggerManager.ts index f8afb13..abe05dd 100644 --- a/src/LoggerManager.ts +++ b/src/LoggerManager.ts @@ -1,15 +1,17 @@ import * as Cluster from "cluster"; -import { Logger, Colors, AnsiStyle, ILogger, WorkerLogger } from "./Logging"; +import { AnsiStyle, Colors, ILogger, Logger, WorkerLogger } from "./Logging"; +import { Service } from "./Core"; -export class LoggerManager { +export class LoggerManager extends Service { private loggers: { [namespace: string]: ILogger } = {}; + readonly isProxable: boolean = false; + registerLogger(namespace: string, style?: AnsiStyle): ILogger { - if (this.loggers[namespace] == undefined) { + if (this.loggers[namespace] == null) { if (Cluster.isMaster) { this.loggers[namespace] = new Logger(`Kanro:${namespace}`, style); - } - else { + } else { this.loggers[namespace] = new WorkerLogger(`Worker:${Cluster.worker.id}:${namespace}`); } } @@ -20,15 +22,7 @@ export class LoggerManager { return this.loggers[namespace]; } - private constructor() { - } - - private static instance: LoggerManager; - public static get current() { - if (LoggerManager.instance == undefined) { - LoggerManager.instance = new LoggerManager(); - } - - return LoggerManager.instance; + constructor() { + super(undefined); } } \ No newline at end of file diff --git a/src/Logging/AnsiStyle.ts b/src/Logging/AnsiStyle.ts index facaa70..dff12a7 100644 --- a/src/Logging/AnsiStyle.ts +++ b/src/Logging/AnsiStyle.ts @@ -1,72 +1,72 @@ import { Colors } from "./Colors"; export class AnsiStyle { - public styleString = ""; + styleString = ""; private styleEnable = true; - public bold(): AnsiStyle { - return this.addStyle('1'); + bold(): AnsiStyle { + return this.addStyle("1"); } - public faint(): AnsiStyle { - return this.addStyle('2'); + faint(): AnsiStyle { + return this.addStyle("2"); } - public italic(): AnsiStyle { - return this.addStyle('3'); + italic(): AnsiStyle { + return this.addStyle("3"); } - public underline(): AnsiStyle { - return this.addStyle('4'); + underline(): AnsiStyle { + return this.addStyle("4"); } - public blinkSlow(): AnsiStyle { - return this.addStyle('5'); + blinkSlow(): AnsiStyle { + return this.addStyle("5"); } - public blinkRapid(): AnsiStyle { - return this.addStyle('6'); + blinkRapid(): AnsiStyle { + return this.addStyle("6"); } - public negative(): AnsiStyle { - return this.addStyle('7'); + negative(): AnsiStyle { + return this.addStyle("7"); } - public conceal(): AnsiStyle { - return this.addStyle('8'); + conceal(): AnsiStyle { + return this.addStyle("8"); } - public strikeThrough(): AnsiStyle { - return this.addStyle('9'); + strikeThrough(): AnsiStyle { + return this.addStyle("9"); } - public foreground(color: Colors): AnsiStyle { - return this.addStyle('3' + color); + foreground(color: Colors): AnsiStyle { + return this.addStyle("3" + color); } - public background(color: Colors): AnsiStyle { - return this.addStyle('4' + color); + background(color: Colors): AnsiStyle { + return this.addStyle("4" + color); } - public styling(string: string): string { + styling(string: string): string { if (!this.styleEnable) { return string; } - return this.styleString + string + '\x1b[0m'; + return this.styleString + string + "\x1b[0m"; } - public addStyle(styleCode: string): AnsiStyle { + addStyle(styleCode: string): AnsiStyle { this.styleString += `\x1b[${styleCode}m`; return this; } - public enable(styleEnable = true): AnsiStyle { + enable(styleEnable: boolean = true): AnsiStyle { this.styleEnable = styleEnable; return this; } - public static create(str: string = '') { - let result = new AnsiStyle(); + static create(str: string = ""): AnsiStyle { + let result: AnsiStyle = new AnsiStyle(); result.styleString = str; return result; } diff --git a/src/Logging/CoreLogger.ts b/src/Logging/CoreLogger.ts index 33b1b64..9fef68e 100644 --- a/src/Logging/CoreLogger.ts +++ b/src/Logging/CoreLogger.ts @@ -1,8 +1,9 @@ -import * as FileStream from 'fs'; -import { Path, File } from "../IO"; +import * as FileStream from "fs"; +import { File, Path } from "../IO"; import { StringUtils } from "../Utils/index"; +import { ILogConfig } from "./ILogConfig"; -let projectDir = Path.resolve(__dirname, '../..'); +let projectDir: string = Path.resolve(__dirname, "../.."); export class CoreLogger { private logFile: NodeJS.WritableStream; @@ -10,56 +11,55 @@ export class CoreLogger { static time: number = -1; private constructor(logFile: string, errorFile: string) { - if (logFile != undefined) { + if (logFile != null) { File.createDirSync(Path.dirname(logFile)); - this.logFile = FileStream.createWriteStream(logFile, {'flags': 'a'}); + this.logFile = FileStream.createWriteStream(logFile, { "flags": "a" }); } - if (errorFile != undefined) { + if (errorFile != null) { File.createDirSync(Path.dirname(errorFile)); - this.errorFile = FileStream.createWriteStream(errorFile, {'flags': 'a'}); + this.errorFile = FileStream.createWriteStream(errorFile, { "flags": "a" }); } } private static instance: CoreLogger; - public static get current() { - if (CoreLogger.instance == undefined) { + static get current(): CoreLogger { + if (CoreLogger.instance == null) { - let config = {}; + let config: ILogConfig; if (FileStream.existsSync(`${process.cwd()}/logger.json`)) { config = require(`${process.cwd()}/logger.json`); - } - else if (FileStream.existsSync(`${projectDir}/config/logger.json`)) { + } else if (FileStream.existsSync(`${projectDir}/config/logger.json`)) { config = require(`${projectDir}/config/logger.json`); } - CoreLogger.instance = new CoreLogger(config['logFile'], config['errorFile']); + CoreLogger.instance = new CoreLogger(config.logFile, config.errorFile); } return CoreLogger.instance; } - public log(message: string) { + log(message: string): void { console.log(message); - if(this.logFile != undefined){ - this.logFile.write(StringUtils.removeStyling(message) + '\n') + if (this.logFile != null) { + this.logFile.write(StringUtils.removeStyling(message) + "\n"); } CoreLogger.time = Date.now(); } - public error(message: string) { + error(message: string): void { console.error(message); - - let noStyling = StringUtils.removeStyling(message) + '\n'; - if(this.logFile != undefined){ - this.logFile.write(noStyling) + let noStyling: string = StringUtils.removeStyling(message) + "\n"; + + if (this.logFile != null) { + this.logFile.write(noStyling); } - if(this.errorFile != undefined){ - this.errorFile.write(noStyling) + if (this.errorFile != null) { + this.errorFile.write(noStyling); } - + CoreLogger.time = Date.now(); } } \ No newline at end of file diff --git a/src/Logging/ILogConfig.ts b/src/Logging/ILogConfig.ts new file mode 100644 index 0000000..4ded648 --- /dev/null +++ b/src/Logging/ILogConfig.ts @@ -0,0 +1,4 @@ +export interface ILogConfig { + logFile: string; + errorFile: string; +} \ No newline at end of file diff --git a/src/Logging/ILogger.ts b/src/Logging/ILogger.ts index 8cc1aac..853a734 100644 --- a/src/Logging/ILogger.ts +++ b/src/Logging/ILogger.ts @@ -1,6 +1,6 @@ export interface ILogger { - info(message: string); - error(message: string); - success(message: string); - warning(message: string); + info(message: string): void; + error(message: string): void; + success(message: string): void; + warning(message: string): void; } \ No newline at end of file diff --git a/src/Logging/Logger.ts b/src/Logging/Logger.ts index 38fa37b..5f376c4 100644 --- a/src/Logging/Logger.ts +++ b/src/Logging/Logger.ts @@ -1,13 +1,13 @@ import * as Cluster from "cluster"; -import * as Stream from 'stream' - -import { StringUtils, TimeUtils, LoggerUtils } from '../Utils'; -import { Colors } from './Colors'; -import { Style } from "./Style"; +import * as Stream from "stream"; import { AnsiStyle } from "./AnsiStyle"; -import { LogLevel } from "./LogLevel"; +import { Colors } from "./Colors"; import { CoreLogger } from "./CoreLogger"; import { ILogger } from "./ILogger"; +import { LoggerUtils, StringUtils, TimeUtils } from "../Utils"; +import { LogLevel } from "./LogLevel"; +import { Style } from "./Style"; + export class Logger implements ILogger { private namespace: string; @@ -15,30 +15,49 @@ export class Logger implements ILogger { constructor(namespace: string, style: AnsiStyle) { this.style = style; - this.namespace = style.styling(StringUtils.rightPad(namespace, 16, ' ')); + this.namespace = style.styling(StringUtils.rightPad(namespace, 16, " ")); } - public info(message: string) { - this.log(LoggerUtils.buildLogString(this.namespace, LogLevel.info, message, TimeUtils.getTimePassed(CoreLogger.time), this.style), LogLevel.info); + info(message: string): void { + this.log(LoggerUtils.buildLogString( + this.namespace, + LogLevel.info, + message, + TimeUtils.getTimePassed(CoreLogger.time), + this.style), LogLevel.info); } - public error(message: string) { - this.log(LoggerUtils.buildLogString(this.namespace, LogLevel.error, message, TimeUtils.getTimePassed(CoreLogger.time), this.style), LogLevel.error); + error(message: string): void { + this.log(LoggerUtils.buildLogString( + this.namespace, + LogLevel.error, + message, + TimeUtils.getTimePassed(CoreLogger.time), + this.style), LogLevel.error); } - public success(message: string) { - this.log(LoggerUtils.buildLogString(this.namespace, LogLevel.success, message, TimeUtils.getTimePassed(CoreLogger.time), this.style), LogLevel.success); + success(message: string): void { + this.log(LoggerUtils.buildLogString( + this.namespace, + LogLevel.success, + message, + TimeUtils.getTimePassed(CoreLogger.time), + this.style), LogLevel.success); } - public warning(message: string) { - this.log(LoggerUtils.buildLogString(this.namespace, LogLevel.warning, message, TimeUtils.getTimePassed(CoreLogger.time), this.style), LogLevel.warning); + warning(message: string): void { + this.log(LoggerUtils.buildLogString( + this.namespace, + LogLevel.warning, + message, + TimeUtils.getTimePassed(CoreLogger.time), + this.style), LogLevel.warning); } - private log(message: string, level: LogLevel) { + private log(message: string, level: LogLevel): void { if (LoggerUtils.isErrorOutput(level)) { CoreLogger.current.error(message); - } - else { + } else { CoreLogger.current.log(message); } } diff --git a/src/Logging/Style.ts b/src/Logging/Style.ts index 3163857..3e48967 100644 --- a/src/Logging/Style.ts +++ b/src/Logging/Style.ts @@ -1,18 +1,18 @@ import { AnsiStyle } from "./AnsiStyle"; import { StringUtils } from "../Utils"; -export function Style(stringArray: TemplateStringsArray, ...values: any[]) { - let result = []; +export function Style(stringArray: TemplateStringsArray, ...values: any[]): string { + let result: string[] = []; - let strings = stringArray.slice(0); - let index = 0; + let strings: string[] = stringArray.slice(0); + let index: number = 0; for (index = 0; index < values.length; index++) { - var element = values[index]; + let element: any = values[index]; result.push(strings[index]); if (element instanceof AnsiStyle) { - if (strings[index + 1].length == 0) { + if (strings[index + 1].length === 0) { if (index + 1 < values.length) { result.push(element.styling(StringUtils.toString(values[index + 1]))); index += 1; @@ -25,5 +25,5 @@ export function Style(stringArray: TemplateStringsArray, ...values: any[]) { } result.push(strings.pop()); - return result.join(''); + return result.join(""); } \ No newline at end of file diff --git a/src/Logging/WorkerLogger.ts b/src/Logging/WorkerLogger.ts index 06fa41e..a34ccae 100644 --- a/src/Logging/WorkerLogger.ts +++ b/src/Logging/WorkerLogger.ts @@ -1,32 +1,63 @@ import * as Cluster from "cluster"; -import * as MessageQueue from "amqplib/callback_api"; -import { ILogger } from "./ILogger"; import { AnsiStyle } from "./AnsiStyle"; -import { Application } from "../Application"; +import { CoreLogger } from "./CoreLogger"; +import { ILogger } from "./ILogger"; +import { LoggerUtils } from "../Utils/LoggerUtils"; import { LogLevel } from "./LogLevel"; import { StringUtils } from "../Utils/index"; +import { TimeUtils } from "../Utils/TimeUtils"; export class WorkerLogger implements ILogger { private namespace: string; private style: AnsiStyle; constructor(namespace: string) { - let color = Number(Cluster.worker.id) % 7; - + let color: number = Number(Cluster.worker.id) % 7; this.style = AnsiStyle.create().foreground(color + 1); - this.namespace = this.style.styling(StringUtils.rightPad(namespace, 16, ' ')); + this.namespace = this.style.styling(StringUtils.rightPad(namespace, 16, " ")); + } + + info(message: string): void { + this.log(LoggerUtils.buildLogString( + this.namespace, + LogLevel.info, + message, + TimeUtils.getTimePassed(CoreLogger.time), + this.style), LogLevel.info); } - info(message: string) { - process.send({ type: "log", namespace: this.namespace, message: message, level: LogLevel.info, style: this.style.styleString }); + error(message: string): void { + this.log(LoggerUtils.buildLogString( + this.namespace, + LogLevel.info, + message, + TimeUtils.getTimePassed(CoreLogger.time), + this.style), LogLevel.error); } - error(message: string) { - process.send({ type: "log", namespace: this.namespace, message: message, level: LogLevel.error }); + + success(message: string): void { + this.log(LoggerUtils.buildLogString( + this.namespace, + LogLevel.info, + message, + TimeUtils.getTimePassed(CoreLogger.time), + this.style), LogLevel.success); } - success(message: string) { - process.send({ type: "log", namespace: this.namespace, message: message, level: LogLevel.success }); + + warning(message: string): void { + this.log(LoggerUtils.buildLogString( + this.namespace, + LogLevel.info, + message, + TimeUtils.getTimePassed(CoreLogger.time), + this.style), LogLevel.warning); } - warning(message: string) { - process.send({ type: "log", namespace: this.namespace, message: message, level: LogLevel.warning }); + + private log(message: string, level: LogLevel): void { + if (LoggerUtils.isErrorOutput(level)) { + CoreLogger.current.error(message); + } else { + CoreLogger.current.log(message); + } } } \ No newline at end of file diff --git a/src/ModuleManager.ts b/src/ModuleManager.ts index e8f50ab..abbe546 100644 --- a/src/ModuleManager.ts +++ b/src/ModuleManager.ts @@ -1,105 +1,136 @@ -import * as Npm from 'npm'; import * as Cluster from "cluster"; - -import { Path, File } from "./IO"; -import { Node, Service, INodeContainer, Module, IModuleInfo } from "./Core"; -import { KanroModule } from "./KanroModule"; -import { NpmClient } from "./NpmClient"; +import * as Npm from "npm"; +import { AnsiStyle, Colors, ILogger } from "./Logging"; +import { Application } from "."; +import { ConfigBuilder } from "./ConfigBuilder"; import { ExceptionUtils, ObjectUtils } from "./Utils"; -import { LoggerManager } from "./LoggerManager"; -import { Colors, AnsiStyle } from "./Logging"; +import { File, Path } from "./IO"; +import { HttpServer } from "./HttpServer"; import { IAppConfig } from "./IAppConfig"; +import { IModuleInfo, INodeContainer, Module, Node, Service } from "./Core"; import { KanroException } from "./Exceptions/index"; +import { KanroInternalModule } from "./KanroInternalModule"; +import { KanroModule } from "./KanroModule"; +import { LoggerManager } from "./LoggerManager"; +import { NpmClient } from "./NpmClient"; -let moduleLogger = LoggerManager.current.registerLogger("Module", AnsiStyle.create().foreground(Colors.blue)); -export class ModuleManager { +export class ModuleManager extends Service { modules: { [name: string]: { [version: string]: Module } } = {}; - private static localModules: { [name: string]: { [version: string]: Module } } = {}; + localModules: { [name: string]: { [version: string]: Module } } = {}; + service: { [name: string]: INodeContainer } = {}; + + dependencies = { + loggerManager: { + name: LoggerManager.name, + module: KanroInternalModule.moduleInfo + }, + npmClient: { + name: NpmClient.name, + module: KanroInternalModule.moduleInfo + } + }; - registerLocalModule(name: string, version: string, module: Module) { + private moduleLogger: ILogger; + + constructor() { + super(undefined); + } + + private npmClient: NpmClient; + + readonly isProxable: boolean = false; + + async onLoaded(): Promise { + this.npmClient = await this.getDependedService("npmClient"); + this.moduleLogger = await (await this.getDependedService("loggerManager")) + .registerLogger("Module", AnsiStyle.create().foreground(Colors.blue)); + } + + registerLocalModule(name: string, version: string, module: Module): void { ExceptionUtils.throwIfInvalidModule(module); this.registerModule(name, version, module); - ObjectUtils.setValueFormKeys(ModuleManager.localModules, module, name, version); + ObjectUtils.setValueFormKeys(this.localModules, module, name, version); } - private registerModule(name: string, version: string, module: Module) { + private registerModule(name: string, version: string, module: Module): void { ExceptionUtils.throwIfInvalidModule(module); - if (name == 'kanro' && !(module instanceof KanroModule)) { - moduleLogger.error(`Try to register a named 'kanro' module, but it is not 'kanro' module.`); + if (name === "kanro" && !(module instanceof KanroModule)) { + this.moduleLogger.error(`Try to register a named 'kanro' module, but it is not 'kanro' module.`); throw new Error(); } - if (ObjectUtils.getValueFormKeys(this.modules, name, version) != undefined) { - moduleLogger.warning(`Try to overwrite a registered module '${name}@${version}'.`); + if (ObjectUtils.getValueFormKeys(this.modules, name, version) != null) { + this.moduleLogger.warning(`Try to overwrite a registered module '${name}@${version}'.`); } ObjectUtils.setValueFormKeys(this.modules, module, name, version); } - async installModule(name: string, version: string = '*'): Promise { + async installModule(name: string, version: string = "*"): Promise { let result: Module; name = name.toLowerCase(); - // Try to find it in cache. + // try to find it in cache. result = this.getModule(name, version); - if (result != undefined) { + if (result != null) { return result; } - if (version == '*' && name != 'kanro') { - moduleLogger.warning(`Try to install a unspecific version module '${name}', but not a kanro core module.`); + if (version === "*" && name !== "kanro") { + this.moduleLogger.warning(`Try to install a unspecific version module '${name}', but not a kanro core module.`); } - let module; - // Try to require local module. + let module: any; + // try to require local module. try { - if (version == '*') { + if (version === "*") { module = require(name); - } - else { + } else { module = require(`.${version}@${name}`); } - // Try to register local module. + // try to register local module. try { - this.registerModule(name, version, module['KanroModule']); - return module['KanroModule']; + this.registerModule(name, version, module.KanroModule); + return module.KanroModule; } catch (error) { - moduleLogger.warning(`Local module '${name}' is not a valid kanro module, try to reinstall it.`); + this.moduleLogger.warning(`Local module '${name}' is not a valid kanro module, try to reinstall it.`); } } catch (error) { - + // ignored } - // Try to install module by NPM. + // try to install module by NPM. try { - module = await NpmClient.install(name, version); - this.registerModule(name, version, result['KanroModule']); - result = module['KanroModule']; - moduleLogger.success(`Module '${name}@${version}' has been installed success!`); + module = await this.npmClient.install(name, version); + this.registerModule(name, version, module.KanroModule); + result = module.KanroModule; + this.moduleLogger.success(`Module '${name}@${version}' has been installed success!`); return result; } catch (error) { - moduleLogger.warning(`Install module '${name}@${version}' fail! Message: '${error.message}'.`); + this.moduleLogger.warning(`Install module '${name}@${version}' fail! Message: '${error.message}'.`); throw error; } } getModule(name: string, version: string): Module { - if (version == '*') { - if (this.modules[name] != undefined) { - for (var key in this.modules[name]) { - return this.modules[name][key]; + if (version === "*") { + if (this.modules[name] != null) { + for (let key in this.modules[name]) { + if (this.modules[name].hasOwnProperty(key)) { + return this.modules[name][key]; + } } } return undefined; } - if (this.modules[name] != undefined) { + if (this.modules[name] != null) { return this.modules[name][version]; } @@ -107,18 +138,27 @@ export class ModuleManager { } isModuleInstalled(name: string, version: string): boolean { - let result = this.getModule(name, version); - return result != undefined; + let result: Module = this.getModule(name, version); + return result != null; } async getNode(config: INodeContainer): Promise { - let module = this.getModule(config.module.name, config.module.version); - let result = await module.getNode(config); + let module: Module = this.getModule(config.module.name, config.module.version); + if (module == null && Cluster.isMaster) { + await this.installModule(config.module.name, config.module.version); + module = this.getModule(config.module.name, config.module.version); + } + if (module == null) { + let message: string = `Module '${config.module.name}@${config.module.version}' not found.`; + this.moduleLogger.error(message); + throw new Error(message); + } + let result: Node = await module.getNode(config); try { ExceptionUtils.throwIfInvalidNode(result); } catch (error) { - moduleLogger.error(`Node '${config.module.name}@${config.module.version}:${config.name}' is a invalid Kanro module.`); + this.moduleLogger.error(`Node '${config.module.name}@${config.module.version}:${config.name}' is a invalid Kanro module.`); throw error; } @@ -126,196 +166,395 @@ export class ModuleManager { return result; } - private constructor() { + async registerService(config: INodeContainer, registerContext?: { count: number }): Promise> { + let serviceKey: string = `${config.module.name}@${config.module.version}:${config.name}`; + if (config.id != null) { + serviceKey = `${serviceKey}@${config.id}`; + } + if (this.service[serviceKey] == null) { + config.instance = await this.getNode(config); + this.service[serviceKey] = config; + if (registerContext != null) { + registerContext.count++; + } + } else { + if (config.dependencies != null) { + for (const key in config.dependencies) { + if (config.dependencies.hasOwnProperty(key)) { + this.service[serviceKey].dependencies[key] = config.dependencies[key]; + } + } + } + } + + return this.service[serviceKey]; } - private static instance: ModuleManager; - public static get current() { - return ModuleManager.instance; + async getService(config: INodeContainer): Promise> { + let serviceKey: string = `${config.module.name}@${config.module.version}:${config.name}`; + if (config.id != null) { + serviceKey = `${serviceKey}@${config.id}`; + } + + if (this.service[serviceKey] === undefined) { + throw new KanroException(`Service '${serviceKey} can't be resolved.`); + } + + return this.service[serviceKey]; } private async installMissedModule(node: INodeContainer): Promise { - let result = 0; + let result: number = 0; - if (node == undefined) { + if (node === undefined) { return result; } - if (node.dependencies != undefined) { + if (node.dependencies != null) { for (let key in node.dependencies) { - result += await this.installMissedModule(node.dependencies[key]); + if (node.dependencies.hasOwnProperty(key)) { + result += await this.installMissedModule(node.dependencies[key]); + } } } - if (node.module != undefined) { + if (node.module != null) { if (this.getModule(node.module.name, node.module.version) == null) { await this.installModule(node.module.name, node.module.version); result++; } } + if (node.exceptionHandlers != null) { + for (let exceptionHandler of node.exceptionHandlers) { + result += await this.installMissedModule(exceptionHandler); + } + } + + if (node.fuses != null) { + for (let fuse of node.fuses) { + result += await this.installMissedModule(fuse); + } + } + if (node.next instanceof Array) { for (let nextNode of node.next) { result += await this.installMissedModule(nextNode); } - } - else { + } else { result += await this.installMissedModule(node.next); } } - private async fillNodeInstance(node: INodeContainer): Promise { - let result = true; + private async nodeLoadEvent(node: INodeContainer): Promise { + if (node === undefined) { + return; + } - if (node == undefined) { - return result; + try { + await node.instance.onLoaded(); + } catch (error) { + throw new KanroException("A exception has been threw by 'node.onLoaded' event.", error); } - if (node.instance == undefined) { - node.instance = await this.getNode(node); - result = result && (node.instance != undefined); + if (node.exceptionHandlers != null) { + for (let exceptionHandler of node.exceptionHandlers) { + await this.nodeLoadEvent(exceptionHandler); + } + } - if (!result) { - return result; + if (node.fuses != null) { + for (let fuse of node.fuses) { + await this.nodeLoadEvent(fuse); } - else { - try { - await node.instance.onLoaded(); - } catch (error) { - throw new KanroException("A exception has been threw by 'node.onLoaded' event.", error); - } + } + + if (node.next instanceof Array) { + for (let nextNode of node.next) { + await this.nodeLoadEvent(nextNode); } + } else { + await this.nodeLoadEvent(node.next); } + } - if (node.dependencies != undefined) { - for (let key in node.dependencies) { - result = result && await this.fillNodeInstance(node.dependencies[key]); + private async resolveRequiredServices(node: INodeContainer): Promise { + if (node === undefined) { + return; + } - if (node.dependencies[key].instance != undefined) { - node.instance.dependencies[key] = node.dependencies[key].instance; + if (node.dependencies != null) { + for (let key in node.dependencies) { + if (node.dependencies.hasOwnProperty(key)) { + await this.registerService(node.dependencies[key]); } } } - if (ObjectUtils.getValueFormKeys(node, 'instance', 'dependencies') != undefined) { + if (ObjectUtils.getValueFormKeys(node, "instance", "dependencies") != null) { for (let key in node.instance.dependencies) { - let serviceInfo = node.instance.dependencies[key]; - - if (!(serviceInfo instanceof Service)) { - let serviceConfig: INodeContainer = { - name: key, - module: serviceInfo + if (node.instance.dependencies.hasOwnProperty(key)) { + let serviceInfo: Service | INodeContainer = node.instance.dependencies[key]; + + if (!(serviceInfo instanceof Service)) { + await this.registerService(serviceInfo); + } else { + await this.resolveRequiredServices({ + name: "", + module: { + name: "", + version: "" + }, + instance: serviceInfo + }); } + } + } + } - let service = await this.getNode(serviceConfig); + if (node.exceptionHandlers != null) { + for (let exceptionHandler of node.exceptionHandlers) { + await this.resolveRequiredServices(exceptionHandler); + } + } - if (service != undefined) { - node.instance.dependencies[key] = service; - } + if (node.fuses != null) { + for (let fuse of node.fuses) { + await this.resolveRequiredServices(fuse); + } + } + + if (node.next != null) { + if (node.next instanceof Array) { + for (let nextNode of node.next) { + await this.resolveRequiredServices(nextNode); } + } else { + await this.resolveRequiredServices(node.next); } + } + } - let dependenciesFilled = true; + private async resolveAllDependedService(): Promise { + let result: number = 0; - for (let key in node.instance.dependencies) { - let serviceInfo = node.instance.dependencies[key]; + do { + for (let key in this.service) { + if (this.service.hasOwnProperty(key)) { + let serviceInfo: INodeContainer = this.service[key]; + result += await this.resolveDependedService(this.service[key]); + } + } + } while (result !== 0); - if (!(serviceInfo instanceof Service)) { - dependenciesFilled = false; - break; + for (const key in this.service) { + if (this.service.hasOwnProperty(key)) { + try { + await this.service[key].instance.onLoaded(); + } catch (error) { + throw new KanroException("A exception has been threw by 'node.onLoaded' event.", error); } } + } + } + + private async resolveDependedService(serviceInfo: INodeContainer): Promise { + let result: any = { + count: 0 + }; + + if (serviceInfo.instance === undefined) { + throw new Error("A empty registered service."); + } + + if (serviceInfo.dependencies != null) { + for (const key in serviceInfo.dependencies) { + if (serviceInfo.dependencies.hasOwnProperty(key)) { + let dependedServiceInfo: INodeContainer = serviceInfo.dependencies[key]; + if (dependedServiceInfo.instance === undefined) { + dependedServiceInfo = await this.registerService(dependedServiceInfo, result); + } + serviceInfo.dependencies[key] = dependedServiceInfo; + serviceInfo.instance.dependencies[key] = dependedServiceInfo; + } + } + } + + for (const key in serviceInfo.instance.dependencies) { + if (serviceInfo.instance.dependencies.hasOwnProperty(key)) { + let dependedServiceInfo: INodeContainer = serviceInfo.instance.dependencies[key]; + + if (!(dependedServiceInfo instanceof Service)) { + if (dependedServiceInfo.instance === undefined) { + dependedServiceInfo = await this.registerService(dependedServiceInfo, result); + } + serviceInfo.instance.dependencies[key] = dependedServiceInfo; + } + } + } + + return result.count; + } + + private async resolveNode(node: INodeContainer): Promise { + let result: number = 0; + + if (node === undefined) { + return result; + } + + if (node.instance === undefined) { + node.instance = await this.getNode(node); + result++; - if (dependenciesFilled) { + if (!result) { + return result; + } else { try { - await node.instance.onDependenciesFilled(); + await node.instance.onCreated(); } catch (error) { - throw new KanroException("A exception has been threw by 'node.onDependenciesFilled' event.", error); + throw new KanroException("A exception has been threw by 'node.onCreated' event.", error); } } } - if (node.exceptionHandlers != undefined) { + if (node.exceptionHandlers != null) { for (let exceptionHandler of node.exceptionHandlers) { - result = result && await this.fillNodeInstance(exceptionHandler); + result += await this.resolveNode(exceptionHandler); } } - if (node.fuses != undefined) { + if (node.fuses != null) { for (let fuse of node.fuses) { - result = result && await this.fillNodeInstance(fuse); + result += await this.resolveNode(fuse); } } - if (node.next != undefined) { + if (node.next != null) { if (node.next instanceof Array) { for (let nextNode of node.next) { - result = result && await this.fillNodeInstance(nextNode); + result += await this.resolveNode(nextNode); } - } - else { - result = result && await this.fillNodeInstance(node.next); + } else { + result += await this.resolveNode(node.next); } } return result; } - async loadConfig(config: IAppConfig) { - let missedModule = 0; - let allNodeFilled = true; - + private async resolveNodeDependencies(node: INodeContainer): Promise { + if (node === undefined) { + return; + } - if (Cluster.isMaster) { - do { - missedModule = 0; - allNodeFilled = true; + if (ObjectUtils.getValueFormKeys(node, "instance", "dependencies") != null) { + for (let key in node.instance.dependencies) { + if (node.instance.dependencies.hasOwnProperty(key)) { + let serviceInfo: INodeContainer | Service = node.instance.dependencies[key]; + + if (!(serviceInfo instanceof Service)) { + let serviceConfig: INodeContainer = serviceInfo; + serviceConfig = await this.getService(serviceConfig); + node.instance.dependencies[key] = serviceConfig; + // result += await this.fillNodeInstance(serviceConfig); + } + } + } + } - missedModule += await this.installMissedModule(config.entryPoint); - missedModule += await this.installMissedModule(config.exitPoint); + if (node.dependencies != null) { + for (let key in node.dependencies) { + if (node.dependencies.hasOwnProperty(key)) { + let serviceInfo: INodeContainer = await this.getService(node.dependencies[key]); + node.dependencies[key] = serviceInfo; + node.instance.dependencies[key] = serviceInfo; + } + } + } - allNodeFilled = allNodeFilled && await this.fillNodeInstance(config.entryPoint); - allNodeFilled = allNodeFilled && await this.fillNodeInstance(config.exitPoint); - } while ((missedModule == 0) && allNodeFilled); + if (node.exceptionHandlers != null) { + for (let exceptionHandler of node.exceptionHandlers) { + await this.resolveNodeDependencies(exceptionHandler); + } } - else { - allNodeFilled = allNodeFilled && await this.fillNodeInstance(config.entryPoint); - allNodeFilled = allNodeFilled && await this.fillNodeInstance(config.exitPoint); - if (!allNodeFilled) { - //TODO: - throw new Error(); + if (node.fuses != null) { + for (let fuse of node.fuses) { + await this.resolveNodeDependencies(fuse); } } - } - static async initialize(config: IAppConfig): Promise { - if (ModuleManager.instance != undefined) { - return ModuleManager.instance; + if (node.next != null) { + if (node.next instanceof Array) { + for (let nextNode of node.next) { + await this.resolveNodeDependencies(nextNode); + } + } else { + await this.resolveNodeDependencies(node.next); + } } - let result = new ModuleManager(); + } + + async loadConfig(config: IAppConfig): Promise { + let missedModule: number = 0; + let newNode: number = 0; if (Cluster.isMaster) { - await NpmClient.initialize(config); + await this.npmClient.initialize(config); } - ModuleManager.instance = result; - return result; + + await this.resolveNode(config.entryPoint); + await this.resolveNode(config.exitPoint); + + await this.resolveRequiredServices(config.entryPoint); + await this.resolveRequiredServices(config.exitPoint); + + await this.resolveAllDependedService(); + + await this.resolveNodeDependencies(config.entryPoint); + await this.resolveNodeDependencies(config.exitPoint); + + await this.nodeLoadEvent(config.entryPoint); + await this.nodeLoadEvent(config.exitPoint); } - async reloadConfig(config: IAppConfig) { - try { - let result = new ModuleManager(); - for (let name in ModuleManager.localModules) { - for (let version in ModuleManager.localModules[name]) { - result.registerModule(name, version, ModuleManager.localModules[name][version]); - } - } - await result.loadConfig(config); - ModuleManager.instance = result; - } catch (error) { - moduleLogger.error('Reload config fail, operation will be canceled.') + async initialize(internalModule: KanroInternalModule): Promise { + if (internalModule.moduleManager !== this) { + throw new KanroException("Unknown error for initialize internal kanro module"); } + + this.registerLocalModule(KanroInternalModule.moduleInfo.name, + KanroInternalModule.moduleInfo.version, internalModule); + this.registerLocalModule(KanroModule.moduleInfo.name, + KanroModule.moduleInfo.version, new KanroModule()); + + await this.registerService({ + name: LoggerManager.name, + module: KanroInternalModule.moduleInfo + }); + await this.registerService({ + name: Application.name, + module: KanroInternalModule.moduleInfo + }); + await this.registerService({ + name: NpmClient.name, + module: KanroInternalModule.moduleInfo + }); + await this.registerService({ + name: ConfigBuilder.name, + module: KanroInternalModule.moduleInfo + }); + await this.registerService({ + name: HttpServer.name, + module: KanroInternalModule.moduleInfo + }); + await this.registerService({ + name: ModuleManager.name, + module: KanroInternalModule.moduleInfo + }); + await this.resolveAllDependedService(); } } \ No newline at end of file diff --git a/src/NodeHandler.ts b/src/NodeHandler.ts index 7473264..2c4b904 100644 --- a/src/NodeHandler.ts +++ b/src/NodeHandler.ts @@ -1,10 +1,20 @@ -import { RequestContext } from "./RequestContext"; -import { INodeContainer, Node, RequestHandler, RequestDiverter, RequestReplicator, Responder, ResponseHandler, ExceptionHandler, Fuse } from "./Core/index"; -import { UnexpectedNodeException } from "./Exceptions/UnexpectedNodeException"; +import { + ExceptionHandler, + Fuse, + INodeContainer, + Node, + RequestDiverter, + RequestHandler, + RequestReplicator, + Responder, + ResponseHandler +} from "./Core/index"; +import { IRequest, RequestContext } from "./Http"; import { NonstandardNodeException } from "./Exceptions/index"; +import { UnexpectedNodeException } from "./Exceptions/UnexpectedNodeException"; export async function NodeHandler(context: RequestContext, container: INodeContainer): Promise { - if (container == undefined) { + if (container == null) { return context; } @@ -16,55 +26,48 @@ export async function NodeHandler(context: RequestContext, container: INodeConta if (container.instance instanceof RequestHandler) { context.request = await container.instance.handler(context.request); next = >container.next; - } - else if (container.instance instanceof RequestDiverter) { + } else if (container.instance instanceof RequestDiverter) { next = await container.instance.shunt(context.request, []>container.next); - } - else if (container.instance instanceof RequestReplicator) { - let nextNodeContainers = []>container.next; + } else if (container.instance instanceof RequestReplicator) { + let nextNodeContainers: INodeContainer[] = []>container.next; - let nodes = nextNodeContainers.map((c) => c.instance); - if (nextNodeContainers.length == 0) { + let nodes: Node[] = nextNodeContainers.map((c) => c.instance); + if (nextNodeContainers.length === 0) { throw new UnexpectedNodeException(container); } - let requests = await container.instance.copy(context.request, nextNodeContainers.length); + let requests: IRequest[] = await container.instance.copy(context.request, nextNodeContainers.length); - if (nextNodeContainers.length != requests.length) { + if (nextNodeContainers.length !== requests.length) { throw new NonstandardNodeException(container); } requests.forEach((v, i, a) => { - if (i == 0) { + if (i === 0) { context.request = v; - } - else { + } else { NodeHandler(context.fork(v, undefined), nextNodeContainers[i]); } }); next = nextNodeContainers[0]; - } - else if (container.instance instanceof Responder) { + } else if (container.instance instanceof Responder) { context.response = await container.instance.respond(context.request); next = >container.next; - } - else if (container.instance instanceof ResponseHandler) { + } else if (container.instance instanceof ResponseHandler) { context.response = await container.instance.handler(context.response); next = >container.next; - } - else if (container.instance instanceof ExceptionHandler) { - let res = await container.instance.handler(context.error, context.request, context.response); - if (res == undefined) { - return undefined; + } else if (container.instance instanceof ExceptionHandler) { + let res: any = await container.instance.handler(context.error, context.request, context.response); + if (res == null) { + return null; } context.response = res; next = >container.next; - } - else if (container.instance instanceof Fuse) { - let req = await container.instance.fusing(context.error, context.request); - if (req == undefined) { - return undefined; + } else if (container.instance instanceof Fuse) { + let req: any = await container.instance.fusing(context.error, context.request); + if (req == null) { + return null; } context.request = req; next = >container.next; @@ -74,11 +77,11 @@ export async function NodeHandler(context: RequestContext, container: INodeConta } catch (error) { context.error = error; - if (container.fuses != undefined) { + if (container.fuses != null) { for (let fuse of container.fuses) { try { - let result = await NodeHandler(context, fuse); - if (result != undefined) { + let result: RequestContext = await NodeHandler(context, fuse); + if (result != null) { return result; } } catch (error) { @@ -87,11 +90,11 @@ export async function NodeHandler(context: RequestContext, container: INodeConta } } - if (container.exceptionHandlers != undefined) { + if (container.exceptionHandlers != null) { for (let exceptionHandler of container.exceptionHandlers) { try { - let result = await NodeHandler(context, exceptionHandler); - if (result != undefined) { + let result: RequestContext = await NodeHandler(context, exceptionHandler); + if (result != null) { return result; } } catch (error) { diff --git a/src/NpmClient.ts b/src/NpmClient.ts index 8e5ce41..4ddf37c 100644 --- a/src/NpmClient.ts +++ b/src/NpmClient.ts @@ -1,51 +1,73 @@ -import * as Npm from 'npm'; -import { LoggerManager } from "./LoggerManager"; -import { Colors, Style, AnsiStyle } from "./Logging"; +import * as Npm from "npm"; +import { AnsiStyle, Colors, ILogger, Style } from "./Logging"; import { AsyncUtils } from "./Utils"; import { File, Path } from "./IO"; +import { IAppConfig } from "./IAppConfig"; +import { IModuleInfo, Service } from "./Core"; +import { KanroInternalModule } from "./KanroInternalModule"; +import { LoggerManager } from "./LoggerManager"; -let npmLogger = LoggerManager.current.registerLogger("NPM", AnsiStyle.create().foreground(Colors.cyan)); +export class NpmClient extends Service { + dependencies = { + loggerManager: { + name: LoggerManager.name, + module: KanroInternalModule.moduleInfo + } + }; + + constructor() { + super(undefined); + } -export class NpmClient { - public static async install(name: String, version: String = '*'): Promise { - let moduleId = `${name}@${version}`; + private npmLogger: ILogger; + + readonly isProxable: boolean = false; + + async install(name: String, version: String = "*"): Promise { + let moduleId: string = `${name}@${version}`; try { - npmLogger.info(`Module '${moduleId}' installing...`); - let installResult = await AsyncUtils.promise(Npm.commands.install, Npm, [`${moduleId}`, ' --quiet']); - let data = installResult[0].split("@"); - let moduleName = data[0]; - let moduleVersion = data[1]; - npmLogger.success(Style`NPM install module '${AnsiStyle.create().foreground(Colors.magenta)}${`${moduleName}@${moduleVersion}`}' success.`); - let newPath = `${Path.parse(installResult[1]).dir}/.${moduleVersion}@${moduleName}`; + this.npmLogger.info(`Module '${moduleId}' installing...`); + let installResult: string[] = await AsyncUtils.promise(Npm.commands.install, Npm, [`${moduleId}`, " --quiet"]); + let data: string[] = installResult[0].split("@"); + let moduleName: string = data[0]; + let moduleVersion: string = data[1]; + // tslint:disable-next-line:max-line-length + this.npmLogger.success(Style`NPM install module '${AnsiStyle.create().foreground(Colors.magenta)}${`${moduleName}@${moduleVersion}`}' success.`); + let newPath: string = `${Path.parse(installResult[1]).dir}/.${moduleVersion}@${moduleName}`; if (await File.exists(newPath)) { await File.unlink(newPath); } await File.rename(installResult[1], newPath); - if(version == "*"){ - await File.symlink(newPath, `${Path.parse(installResult[1]).dir}/${moduleName}`, 'dir'); + if (version === "*") { + await File.symlink(newPath, `${Path.parse(installResult[1]).dir}/${moduleName}`, "dir"); } return require(`.${moduleVersion}@${moduleName}`); } catch (error) { - npmLogger.error(`NPM install module '${moduleId}' fail, message: '${error.message}'.`); + this.npmLogger.error(`NPM install module '${moduleId}' fail, message: '${error.message}'.`); } } - public static async initialize(npmConfig): Promise { + async initialize(npmConfig: IAppConfig): Promise { try { await AsyncUtils.promise(Npm.load, Npm, npmConfig); - if (npmConfig.registry != undefined) { - npmLogger.info(`Set NPM registry to '${npmConfig.registry}'.`); + if (npmConfig.registry != null) { + this.npmLogger.info(`Set NPM registry to '${npmConfig.registry}'.`); } - Npm.on('log', message => { - npmLogger.info(message); + Npm.on("log", message => { + this.npmLogger.info(message); }); } catch (error) { - npmLogger.error(`NPM initialize failed! Message: ${error.message}.\n ${error.stack}`); + this.npmLogger.error(`NPM initialize failed! Message: ${error.message}.\n ${error.stack}`); } } + + async onLoaded(): Promise { + this.npmLogger = await (await this.getDependedService("loggerManager")) + .registerLogger("NPM", AnsiStyle.create().foreground(Colors.cyan)); + } } \ No newline at end of file diff --git a/src/Primitives/FileRenderer.ts b/src/Primitives/FileRenderer.ts index 82e3543..3e5f01c 100644 --- a/src/Primitives/FileRenderer.ts +++ b/src/Primitives/FileRenderer.ts @@ -1,17 +1,18 @@ -import { Responder, INodeContainer } from "../Core"; import { File } from "../IO"; -import { IRequest, IResponse, JsonResponseBody, Request, FileResponseBody } from "../Http"; -import { NotFoundException } from "../Exceptions"; +import { FileResponseBody, IRequest, IResponse, JsonResponseBody, Request } from "../Http"; +import { IModuleInfo, INodeContainer, Responder } from "../Core"; import { KanroManager } from ".."; +import { KanroModule } from "../KanroModule"; +import { NotFoundException } from "../Exceptions"; -export interface FileRendererContainer extends INodeContainer { +export interface IFileRendererContainer extends INodeContainer { resource: string; } export class FileRenderer extends Responder { async respond(request: IRequest): Promise { - let response = request.respond(); - let path = `${this.resource}/${request.relativeUrl}`; + let response: IResponse = request.respond(); + let path: string = `${this.resource}/${request.relativeUrl}`; if (await File.exists(path)) { response.body = new FileResponseBody(path); @@ -21,12 +22,12 @@ export class FileRenderer extends Responder { throw new NotFoundException(); } - dependencies = { KanroManager: { name: "kanro", version: "*" } }; + dependencies = { kanroManager: { name: KanroManager.name, module: KanroModule.moduleInfo } }; resource: string; - constructor(config: FileRendererContainer) { + constructor(config: IFileRendererContainer) { super(config); - if (config.resource != undefined) { + if (config.resource != null) { if ((config.resource).startsWith(".")) { this.resource = `${process.cwd()}/${config.resource}`; } else { @@ -35,12 +36,12 @@ export class FileRenderer extends Responder { } } - async onDependenciesFilled() { - if(this.resource == undefined){ - this.resource = (this.dependencies.KanroManager).getKanroConfig('resource'); + async onLoaded(): Promise { + if (this.resource == null) { + this.resource = await (await this.getDependedService("kanroManager")).getKanroConfig("resource"); } - if(this.resource == undefined){ + if (this.resource == null) { this.resource = process.cwd(); } } diff --git a/src/Primitives/HttpExceptionRenderer.ts b/src/Primitives/HttpExceptionRenderer.ts index 285dca8..60ee652 100644 --- a/src/Primitives/HttpExceptionRenderer.ts +++ b/src/Primitives/HttpExceptionRenderer.ts @@ -1,6 +1,6 @@ import { ExceptionHandler } from "../Core"; -import { IRequest, IResponse, JsonResponseBody } from "../Http"; import { HttpException } from "../Exceptions"; +import { IRequest, IResponse, JsonResponseBody } from "../Http"; export class HttpExceptionRenderer extends ExceptionHandler { async handler(err: Error, request: IRequest, response: IResponse): Promise { @@ -9,7 +9,7 @@ export class HttpExceptionRenderer extends ExceptionHandler { } else { let kanroHttpException: HttpException = err; - let response = request.respond(); + let response: IResponse = request.respond(); response.status = kanroHttpException.status; response.body = new JsonResponseBody({ code: response.status, message: kanroHttpException.message }); return response; diff --git a/src/Primitives/JsonRenderer.ts b/src/Primitives/JsonRenderer.ts index 053181e..6f20851 100644 --- a/src/Primitives/JsonRenderer.ts +++ b/src/Primitives/JsonRenderer.ts @@ -2,23 +2,23 @@ import { Responder, INodeContainer } from "../Core"; import { IRequest, IResponse, JsonResponseBody } from "../Http"; import { StringResponseBody } from "../Http/StringResponseBody"; -export interface JsonRendererContainer extends INodeContainer { +export interface IJsonRendererContainer extends INodeContainer { response: Object; } export class JsonRenderer extends Responder { async respond(request: IRequest): Promise { - let response = request.respond(); + let response: IResponse = request.respond(); response.status = 200; - response.body = new StringResponseBody("Hello world."); + response.body = new JsonResponseBody(this.response); return response; } response: any; - constructor(config: JsonRendererContainer) { + constructor(config: IJsonRendererContainer) { super(config); - if (config.response != undefined) { + if (config.response != null) { this.response = config.response; } else { this.response = { code: 0, message: "normal" }; diff --git a/src/Remoting/IServiceCall.ts b/src/Remoting/IServiceCall.ts new file mode 100644 index 0000000..b2dfa2b --- /dev/null +++ b/src/Remoting/IServiceCall.ts @@ -0,0 +1,6 @@ +import { ServiceCallType } from "./ServiceCallType"; + +export interface IServiceCall { + type: ServiceCallType; + args: any[]; +} \ No newline at end of file diff --git a/src/Remoting/RemoteServiceContainer.ts b/src/Remoting/RemoteServiceContainer.ts new file mode 100644 index 0000000..4e25879 --- /dev/null +++ b/src/Remoting/RemoteServiceContainer.ts @@ -0,0 +1,11 @@ +import { RemoteServiceException } from "../Exceptions"; +import { Service } from "../Core"; +import { ServiceProxyHandler } from "./ServiceProxyHandler"; + +export class RemoteServiceContainer extends Service { + async onLoaded(): Promise { + if (this.getDependedService("target") == null) { + throw new Error("No target for service proxy"); + } + } +} \ No newline at end of file diff --git a/src/Remoting/RemoteServiceHolder.ts b/src/Remoting/RemoteServiceHolder.ts new file mode 100644 index 0000000..65c02a8 --- /dev/null +++ b/src/Remoting/RemoteServiceHolder.ts @@ -0,0 +1,18 @@ +import { INodeContainer, Responder } from "../Core"; +import { IRequest, IResponse } from "../Http"; + +export class RemoteServiceHolder extends Responder { + async respond(request: IRequest): Promise { + if (this.getDependedService("target") == null) { + throw new Error("No target for service proxy"); + } + + let response: IResponse = request.respond(); + + return response; + } + + constructor(container: INodeContainer) { + super(container); + } +} \ No newline at end of file diff --git a/src/Remoting/ServiceCallType.ts b/src/Remoting/ServiceCallType.ts new file mode 100644 index 0000000..82e02b3 --- /dev/null +++ b/src/Remoting/ServiceCallType.ts @@ -0,0 +1,8 @@ +export enum ServiceCallType { + has, + get, + set, + enumerate, + ownKeys, + apply +} \ No newline at end of file diff --git a/src/Remoting/ServiceProxyHandler.ts b/src/Remoting/ServiceProxyHandler.ts new file mode 100644 index 0000000..f971b04 --- /dev/null +++ b/src/Remoting/ServiceProxyHandler.ts @@ -0,0 +1,46 @@ +import { RemoteServiceException } from "../Exceptions"; + +export class ServiceProxyHandler implements ProxyHandler { + getPrototypeOf(target: any): object | null { + throw new RemoteServiceException("getPrototypeOf"); + } + setPrototypeOf(target: any, v: any): boolean { + throw new RemoteServiceException("setPrototypeOf"); + } + isExtensible(target: any): boolean { + return false; + } + preventExtensions(target: any): boolean { + return true; + } + getOwnPropertyDescriptor(target: any, p: PropertyKey): PropertyDescriptor | undefined { + throw new RemoteServiceException("getOwnPropertyDescriptor"); + } + has(target: any, p: PropertyKey): boolean { + throw new RemoteServiceException("has"); + } + get(target: any, p: PropertyKey, receiver: any): any { + throw new RemoteServiceException("get"); + } + set(target: any, p: PropertyKey, value: any, receiver: any): boolean { + throw new RemoteServiceException("set"); + } + deleteProperty(target: any, p: PropertyKey): boolean { + throw new RemoteServiceException("deleteProperty"); + } + defineProperty(target: any, p: PropertyKey, attributes: PropertyDescriptor): boolean { + throw new RemoteServiceException("defineProperty"); + } + enumerate(target: any): PropertyKey[] { + throw new RemoteServiceException("enumerate"); + } + ownKeys(target: any): PropertyKey[] { + throw new RemoteServiceException("ownKeys"); + } + apply(target: any, thisArg: any, argArray?: any): any { + throw new RemoteServiceException("apply"); + } + construct(target: any, argArray: any, newTarget?: any): object { + throw new RemoteServiceException("construct"); + } +} \ No newline at end of file diff --git a/src/Remoting/index.ts b/src/Remoting/index.ts new file mode 100644 index 0000000..2f0d3fe --- /dev/null +++ b/src/Remoting/index.ts @@ -0,0 +1,2 @@ +export * from "./RemoteServiceHolder"; +export * from "./RemoteServiceContainer"; \ No newline at end of file diff --git a/src/RequestContext.ts b/src/RequestContext.ts deleted file mode 100644 index 91a7c8a..0000000 --- a/src/RequestContext.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Request, RequestMirror, Response } from "./Http/index"; -import { INodeContainer, Node } from "./Core/index"; - -export class RequestContext { - public request: Request | RequestMirror; - public response: Response; - public error?: Error; - public traceStack: INodeContainer[]; - public time: number; - - public constructor(request: Request | RequestMirror) { - this.request = request; - this.traceStack = []; - this.time = Date.now(); - } - - fork(request: Request | RequestMirror, response: Response): RequestContext { - let result = new RequestContext(request); - result.response = response; - result.error = this.error; - result.traceStack = this.traceStack.slice(0); - result.time = this.time; - - return result; - } -} \ No newline at end of file diff --git a/src/Router/MethodRouter.ts b/src/Router/MethodRouter.ts index e18c867..0620788 100644 --- a/src/Router/MethodRouter.ts +++ b/src/Router/MethodRouter.ts @@ -4,16 +4,15 @@ import { MethodNotAllowedException } from "../Exceptions"; export class MethodRouter extends RequestDiverter { async shunt(request: IRequest, nodes: INodeContainer[]): Promise> { - let method = request.method.toUpperCase(); + let method: string = request.method.toUpperCase(); - if (this.methods[method] != undefined) { + if (this.methods[method] != null) { return this.methods[method]; } throw new MethodNotAllowedException(); } - dependencies: { [name: string]: Service; } = {}; methods: { [method: string]: INodeContainer } = {}; constructor(container: INodeContainer) { @@ -21,26 +20,28 @@ export class MethodRouter extends RequestDiverter { container.next = []; - for (var key in container) { - switch (key.toUpperCase()) { - case "OPTIONS": - case "GET": - case "HEAD": - case "POST": - case "PUT": - case "DELETE": - case "TRACE": - case "CONNECT": - case "PATCH": - this.methods[key.toUpperCase()] = container[key]; - container.next.push(container[key]); - break; - default: - if (key.startsWith("-")) { - this.methods[key.slice(1).toUpperCase()] = container[key]; + for (let key in container) { + if (container.hasOwnProperty(key)) { + switch (key.toUpperCase()) { + case "OPTIONS": + case "GET": + case "HEAD": + case "POST": + case "PUT": + case "DELETE": + case "TRACE": + case "CONNECT": + case "PATCH": + this.methods[key.toUpperCase()] = container[key]; container.next.push(container[key]); - } - break; + break; + default: + if (key.startsWith("-")) { + this.methods[key.slice(1).toUpperCase()] = container[key]; + container.next.push(container[key]); + } + break; + } } } } diff --git a/src/Router/RouterKey.ts b/src/Router/RouterKey.ts index 5f1def2..9c1d9a4 100644 --- a/src/Router/RouterKey.ts +++ b/src/Router/RouterKey.ts @@ -6,38 +6,34 @@ export class RouterKey { type: RouterKeyType; constructor(stringKey: string) { - if (stringKey == "*") { + if (stringKey === "*") { this.key = stringKey; this.type = RouterKeyType.Wildcard; - } - else if (stringKey == "**") { + } else if (stringKey === "**") { this.key = stringKey; this.type = RouterKeyType.Wildcard; - } - else if (stringKey.startsWith("{") && stringKey.endsWith("}")) { + } else if (stringKey.startsWith("{") && stringKey.endsWith("}")) { this.type = RouterKeyType.Param; - let index = stringKey.indexOf(":") + let index: number = stringKey.indexOf(":"); if (index < 0) { this.key = stringKey.slice(1, stringKey.length - 1); - } - else { + } else { this.key = stringKey.slice(1, index); this.regex = new RegExp(stringKey.slice(index + 1, stringKey.length - 1)); } - } - else { + } else { this.type = RouterKeyType.Path; this.key = stringKey; } } - match(path: string) { + match(path: string): boolean { switch (this.type) { case RouterKeyType.Wildcard: return true; case RouterKeyType.Param: - if (path != undefined) { - if (this.regex != undefined) { + if (path != null) { + if (this.regex != null) { return this.regex.test(path); } else { return true; @@ -45,8 +41,8 @@ export class RouterKey { } return false; case RouterKeyType.Path: - if (path != undefined) { - return path == this.key; + if (path != null) { + return path === this.key; } return false; default: diff --git a/src/Router/RouterNode.ts b/src/Router/RouterNode.ts index 07f0b8f..881092d 100644 --- a/src/Router/RouterNode.ts +++ b/src/Router/RouterNode.ts @@ -1,33 +1,33 @@ import { INodeContainer } from "../Core"; -import { StringUtils, ObjectUtils } from "../Utils"; import { InvalidRouterConfigException } from "../Exceptions"; -import { Request } from "../Http" -import { Router } from "./Router"; +import { ObjectUtils, StringUtils } from "../Utils"; +import { Request } from "../Http"; import { RouterKey } from "./RouterKey"; -import { RouterResult } from "./RouterResult"; import { RouterKeyType } from "./RouterKeyType"; +import { RouterResult } from "./RouterResult"; +import { UrlRouter } from "./UrlRouter"; export class RouterNode { path: string; children: { [name: string]: RouterNode } = {}; - node: INodeContainer; + node: INodeContainer; routerKey: RouterKey; constructor(path: string) { this.path = path; - if (path != undefined) { + if (path != null) { this.routerKey = new RouterKey(path); } } - addRouter(node: INodeContainer, routerKey: string) { - let keys = StringUtils.routerPathSplit(routerKey).reverse(); + addRouter(node: INodeContainer, routerKey: string): void { + let keys: string[] = StringUtils.routerPathSplit(routerKey).reverse(); this.add(node, keys); } - private add(node: INodeContainer, keys: string[]) { - if (keys.length == 0) { + private add(node: INodeContainer, keys: string[]): void { + if (keys.length === 0) { if (this.node != null) { throw new InvalidRouterConfigException("Duplicate router config be provided."); } @@ -35,76 +35,80 @@ export class RouterNode { return; } - if (this.routerKey != undefined && this.routerKey.key == "**" && keys.length > 0) { + if (this.routerKey != null && this.routerKey.key === "**" && keys.length > 0) { throw new Error("Cannot use '**' as a middle node"); } - let key = keys.pop(); + let key: string = keys.pop(); - if (this.children[key] == undefined) { + if (this.children[key] == null) { this.children[key] = new RouterNode(key); } this.children[key].add(node, keys); } - matchRequest(request: Request, deep: number = 0, routerStack: RouterKey[] = [], param: { [name: string]: string } = {}): RouterResult[] { - if (this.path == undefined) { - if (request.routerKey.length == deep && this.node != undefined) { + matchRequest(request: Request, + deep: number = 0, + routerStack: RouterKey[] = [], + param: { [name: string]: string } = {}): RouterResult[] { + if (this.path == null) { + if (request.routerKey.length === deep && this.node != null) { return [new RouterResult(this.node, deep, [], {})]; } - let results = []; + let results: RouterResult[] = []; for (let name in this.children) { - let result = this.children[name].matchRequest(request, deep, routerStack, { ...param }); + if (this.children.hasOwnProperty(name)) { + let result: RouterResult[] = this.children[name].matchRequest(request, deep, routerStack, { ...param }); - if (Array.isArray(result)) { - results = results.concat(result); - continue; + if (Array.isArray(result)) { + results = results.concat(result); + continue; + } } } return results; } - let key = request.routerKey[deep]; + let key: string = request.routerKey[deep]; routerStack.push(this.routerKey); if (this.routerKey.match(key) && (deep <= request.routerKey.length)) { - if (this.routerKey.type == RouterKeyType.Param) { + if (this.routerKey.type === RouterKeyType.Param) { param[this.routerKey.key] = key; - } - else if (this.routerKey.type == RouterKeyType.Wildcard && this.routerKey.key == "**") { - if (this.node == undefined) { + } else if (this.routerKey.type === RouterKeyType.Wildcard && this.routerKey.key === "**") { + if (this.node == null) { return []; } return [new RouterResult(this.node, deep, this.forkAndPopRouterStack(routerStack), param)]; } - if (deep >= request.routerKey.length - 1 && this.node != undefined) { + if (deep >= request.routerKey.length - 1 && this.node != null) { return [new RouterResult(this.node, deep, this.forkAndPopRouterStack(routerStack), param)]; - } - else { - let results = []; + } else { + let results: RouterResult[] = []; for (let name in this.children) { - let result = this.children[name].matchRequest(request, deep + 1, routerStack, { ...param }); + if (this.children.hasOwnProperty(name)) { + let result: RouterResult[] = this.children[name].matchRequest(request, deep + 1, routerStack, { ...param }); - if (Array.isArray(result)) { - results = results.concat(result); - continue; + if (Array.isArray(result)) { + results = results.concat(result); + continue; + } } } routerStack.pop(); return results; } - } - else { + } else { routerStack.pop(); return []; } } - forkAndPopRouterStack(routerStack: RouterKey[]) { - let stack = ObjectUtils.copy(routerStack); + forkAndPopRouterStack(routerStack: RouterKey[]): RouterKey[] { + let stack: RouterKey[] = ObjectUtils.copy(routerStack); routerStack.pop(); return stack; } diff --git a/src/Router/RouterResult.ts b/src/Router/RouterResult.ts index f3a1f2a..b4f76fd 100644 --- a/src/Router/RouterResult.ts +++ b/src/Router/RouterResult.ts @@ -1,14 +1,14 @@ import { INodeContainer } from "../Core"; import { RouterKey } from "./RouterKey"; -import { Router } from "./Router"; +import { UrlRouter } from "./UrlRouter"; export class RouterResult { param: { [name: string]: string }; - node: INodeContainer; + node: INodeContainer; deep: number; routerStack: RouterKey[]; - constructor(node: INodeContainer, deep: number, routerStack: RouterKey[], param: { [name: string]: string } = {}) { + constructor(node: INodeContainer, deep: number, routerStack: RouterKey[], param: { [name: string]: string } = {}) { this.node = node; this.deep = deep; this.routerStack = routerStack; diff --git a/src/Router/Router.ts b/src/Router/UrlRouter.ts similarity index 67% rename from src/Router/Router.ts rename to src/Router/UrlRouter.ts index 9944f37..b552aff 100644 --- a/src/Router/Router.ts +++ b/src/Router/UrlRouter.ts @@ -7,19 +7,20 @@ import { RouterResult } from "./RouterResult"; import { RouterNode } from "./RouterNode"; import { Logger, Colors, AnsiStyle, ILogger } from "../Logging"; import { KanroManager } from ".."; +import { KanroModule } from "../KanroModule"; -export class Router extends RequestDiverter { +export class UrlRouter extends RequestDiverter { async shunt(request: IRequest, nodes: INodeContainer[]): Promise> { - let result = this.node.matchRequest(request, (request).routerIndex); + let result: RouterResult[] = this.node.matchRequest(request, (request).routerIndex); - let deep = -1; + let deep: number = -1; let selectedNode: RouterResult = undefined; for (let node of result) { if (node.deep > deep) { deep = node.deep; selectedNode = node; - } else if (node.deep == deep) { - for (var index = 0; index < selectedNode.routerStack.length; index++) { + } else if (node.deep === deep) { + for (let index: number = 0; index < selectedNode.routerStack.length; index++) { if (selectedNode.routerStack[index].type < node.routerStack[index].type) { selectedNode = node; break; @@ -30,34 +31,43 @@ export class Router extends RequestDiverter { } } - if (selectedNode == undefined || selectedNode.node == undefined) { + if (selectedNode == null || selectedNode.node == null) { throw new NotFoundException(); } (request).routerIndex = deep; - request["param"] = Object.assign(request["param"] == undefined ? {} : request["param"], selectedNode.param); + request.param = Object.assign(request.param == null ? {} : request.param, selectedNode.param); return selectedNode.node; } node: RouterNode; - $preRouters: string; - dependencies: { [name: string]: Service | IModuleInfo; } = { KanroManager: { name: "kanro", version: "*" } }; + $preRouters: string = ""; + dependencies = { kanroManager: { name: KanroManager.name, module: KanroModule.moduleInfo } }; container: INodeContainer; logger: ILogger; constructor(container: INodeContainer) { super(container); - this.$preRouters = container["$preRouters"] != undefined ? container["$preRouters"] : ""; + // this.$preRouters = container["$preRouters"] != null ? container["$preRouters"] : ""; this.container = container; } - async onDependenciesFilled() { - this.logger = (this.dependencies.KanroManager).registerLogger("Router", AnsiStyle.create().foreground(Colors.red)); - + async onCreated(): Promise { this.container.next = []; - this.node = new RouterNode(undefined); for (let name in this.container) { if (name.startsWith("/")) { this.container.next.push(this.container[name]); + } + } + } + + async onLoaded(): Promise { + this.logger = + await (await this.getDependedService("kanroManager")) + .registerLogger("Router", AnsiStyle.create().foreground(Colors.red)); + + this.node = new RouterNode(undefined); + for (let name in this.container) { + if (name.startsWith("/")) { this.node.addRouter(this.container[name], name); if (name.endsWith("/**")) { if (this.addRouterKeyToNextRouter(`${this.$preRouters}${name.slice(0, name.length - 3)}`, this.container[name])) { @@ -72,10 +82,10 @@ export class Router extends RequestDiverter { } } - addRouterKeyToNextRouter(key: string, node: INodeContainer[] | INodeContainer) { - let result = false; + addRouterKeyToNextRouter(key: string, node: INodeContainer[] | INodeContainer): boolean { + let result: boolean = false; - if (node == undefined) { + if (node == null) { return; } @@ -89,9 +99,9 @@ export class Router extends RequestDiverter { return result; } - if (node.name == this.name) { - let router = >node; - if (router.instance.$preRouters == undefined) { + if (node.name === this.name) { + let router: INodeContainer = >node; + if (router.instance.$preRouters == null) { router.instance.$preRouters = ""; } router.instance.$preRouters += key; @@ -105,6 +115,6 @@ export class Router extends RequestDiverter { result = true; } - return result || this.addRouterKeyToNextRouter(key, node["next"]); + return result || this.addRouterKeyToNextRouter(key, node.next); } } \ No newline at end of file diff --git a/src/Router/index.ts b/src/Router/index.ts index d209bf6..bebdba7 100644 --- a/src/Router/index.ts +++ b/src/Router/index.ts @@ -1,6 +1,6 @@ export * from "./RouterNode"; export * from "./MethodRouter"; -export * from "./Router"; +export * from "./UrlRouter"; export * from "./RouterKey"; export * from "./RouterKeyType"; export * from "./RouterResult"; \ No newline at end of file diff --git a/src/Utils/AsyncUtils.ts b/src/Utils/AsyncUtils.ts index ae4c4ed..3928012 100644 --- a/src/Utils/AsyncUtils.ts +++ b/src/Utils/AsyncUtils.ts @@ -1,16 +1,16 @@ export class AsyncUtils { - static async promise(func: Function, thisArg: any = undefined, ...args: any[]): Promise { + static async promise(func: Function, thisArg?: any, ...args: any[]): Promise { return new Promise((res, rej) => { - let callback = function () { - let result = undefined; + let callback: () => void = function (): void { + let result: any; for (let index in arguments) { - if (arguments[index] != undefined) { - if (typeof arguments[index].stack == "string") { + if (arguments[index] != null) { + if (typeof arguments[index].stack === "string") { rej(arguments[index]); return; } else { - if (result == undefined) { + if (result == null) { result = {}; } result[index] = arguments[index]; @@ -18,14 +18,14 @@ export class AsyncUtils { } } - if (Object.keys(result).length == 1) { + if (Object.keys(result).length === 1) { result = result[Object.keys(result)[0]]; } res(result); - } + }; - if (args == undefined) { + if (args == null) { args = []; } args.push(callback); diff --git a/src/Utils/ExceptionUtils.ts b/src/Utils/ExceptionUtils.ts index 58142da..fce9112 100644 --- a/src/Utils/ExceptionUtils.ts +++ b/src/Utils/ExceptionUtils.ts @@ -2,19 +2,19 @@ import { InvalidModuleException, InvalidNodeException, ArgumentNullException } f import { Module, Node } from "../Core"; export class ExceptionUtils { - public static throwIfInvalidModule(module: Module) { + static throwIfInvalidModule(module: Module): void { if (!(module instanceof Module)) { - throw new InvalidNodeException(module); + throw new InvalidModuleException(module); } } - public static throwIfInvalidNode(node: Node) { + static throwIfInvalidNode(node: Node): void { if (!(node instanceof Node)) { throw new InvalidNodeException(node); } } - public static throwIfNullOrUndefined(value: any, param: string) { + static throwIfNullOrUndefined(value: any, param: string): void { if (value === null || value === undefined) { throw new ArgumentNullException(param); } diff --git a/src/Utils/LoggerUtils.ts b/src/Utils/LoggerUtils.ts index 890f99a..1df829b 100644 --- a/src/Utils/LoggerUtils.ts +++ b/src/Utils/LoggerUtils.ts @@ -2,7 +2,7 @@ import { LogLevel, Style, AnsiStyle, Colors } from "../Logging"; import { TimeUtils } from "./TimeUtils"; export class LoggerUtils { - static getLogLevelFlag(level: LogLevel) { + static getLogLevelFlag(level: LogLevel): string { switch (level) { case LogLevel.info: return Style`${AnsiStyle.create().foreground(Colors.blue)}${"[·]"}`; @@ -15,26 +15,26 @@ export class LoggerUtils { } } - static isErrorOutput(level: LogLevel) { + static isErrorOutput(level: LogLevel): boolean { return level > 1; } static buildLogString(namespace: string, level: LogLevel, message: string, interval: number, style: AnsiStyle): string { - let lines = message.split('\n'); - let result = []; + let lines: string[] = message.split("\n"); + let result: string[] = []; - for (var index = 0; index < lines.length; index++) { - var line = lines[index]; + for (let index: number = 0; index < lines.length; index++) { + let line: string = lines[index]; - if (index == 0 && interval != undefined) { - let intervalString = Style`${style}${'+' + TimeUtils.getIntervalString(interval)}`; + if (index === 0 && interval != null) { + let intervalString: String = Style`${style}${"+" + TimeUtils.getIntervalString(interval)}`; + // tslint:disable-next-line:max-line-length result.push(`[${TimeUtils.getTimeString(Date.now())}] ${namespace} - ${LoggerUtils.getLogLevelFlag(level)} ${line} ${intervalString}`); - } - else { + } else { result.push(`[${TimeUtils.getTimeString(Date.now())}] ${namespace} - ${LoggerUtils.getLogLevelFlag(level)} ${line}`); } } - return result.join('\n'); + return result.join("\n"); } } \ No newline at end of file diff --git a/src/Utils/ObjectUtils.ts b/src/Utils/ObjectUtils.ts index 9b44d96..a0a4f85 100644 --- a/src/Utils/ObjectUtils.ts +++ b/src/Utils/ObjectUtils.ts @@ -7,13 +7,17 @@ export class ObjectUtils { return JSON.parse(JSON.stringify(object)); } - static isEmptyObject(obj) { + static isEmptyObject(obj: any): boolean { return !Object.keys(obj).length; } - static getValueFormKeys(object, ...keys: (string | number)[]) { + static getValueFormKeys(object: any, ...keys: PropertyKey[]): any { + if (object == null) { + return undefined; + } + for (let key of keys) { - if (object[key] == undefined) { + if (object[key] == null) { return undefined; } @@ -23,11 +27,11 @@ export class ObjectUtils { return object; } - static setValueFormKeys(object, value, ...keys: (string | number)[]) { - for (var index = 0; index < keys.length - 1; index++) { - var key = keys[index]; + static setValueFormKeys(object: any, value: any, ...keys: PropertyKey[]): void { + for (let index: number = 0; index < keys.length - 1; index++) { + let key: PropertyKey = keys[index]; - if (object[key] == undefined) { + if (object[key] == null) { object[key] = {}; } diff --git a/src/Utils/StringUtils.ts b/src/Utils/StringUtils.ts index 0199533..d54e800 100644 --- a/src/Utils/StringUtils.ts +++ b/src/Utils/StringUtils.ts @@ -1,12 +1,12 @@ export class StringUtils { static routerPathSplit(path: string): string[] { let result: string[] = []; - let current = ""; + let current: string = ""; if (!path.endsWith("/")) { path += "/"; } - let regex = 0; + let regex: number = 0; for (let char of path) { switch (char) { case "/": @@ -14,7 +14,7 @@ export class StringUtils { current += char; break; } - if (current != undefined && current != "") { + if (current != null && current !== "") { result.push(current); current = ""; } @@ -36,10 +36,10 @@ export class StringUtils { return result; } - static rightPad(str, len, ch) { + static rightPad(str: string, len: number, ch: string): string { str = "" + str; ch = ("" + ch) || " "; - let padLen = len - str.length; + let padLen: number = len - str.length; if (padLen <= 0) { return str; } else { @@ -47,10 +47,10 @@ export class StringUtils { } } - static leftPad(str, len, ch) { + static leftPad(str: string, len: number, ch: string): string { str = "" + str; ch = ("" + ch) || " "; - let padLen = len - str.length; + let padLen: number = len - str.length; if (padLen <= 0) { return str; } else { @@ -58,21 +58,20 @@ export class StringUtils { } } - static toString(obj: any) { - if (obj == undefined) { - return new String(obj); + static toString(obj: any): string { + if (obj === null) { + return "null"; } - if (typeof obj['toString'] == 'function') { - return obj.toString(); - } - else { - return new String(obj); + if (obj === undefined) { + return "undefined"; } + + return obj.toString(); } static removeStyling(str: string): string { - let result = str.replace(/\[[0-9]+m/g, ""); + let result: string = str.replace(/\[[0-9]+m/g, ""); return result; } } \ No newline at end of file diff --git a/src/Utils/TimeUtils.ts b/src/Utils/TimeUtils.ts index 50568c5..ab85cca 100644 --- a/src/Utils/TimeUtils.ts +++ b/src/Utils/TimeUtils.ts @@ -2,34 +2,33 @@ import { StringUtils } from "./StringUtils"; export class TimeUtils { - public static getIntervalString(interval: number): string { + static getIntervalString(interval: number): string { if (interval < 1000) { return `${interval}ms`; - } - else if (interval < 60 * 1000) { + } else if (interval < 60 * 1000) { return `${(interval / 1000).toFixed(2)}s`; - } - else if (interval < 60 * 60 * 1000) { + } else if (interval < 60 * 60 * 1000) { return `${(interval / 1000 / 60).toFixed(0)}m${((interval / 1000) % 60).toFixed(0)}s`; - } - else { + } else { + // tslint:disable-next-line:max-line-length return `${(interval / 1000 / 60 / 60).toFixed(0)}h${((interval / 1000 / 60) % 60).toFixed(0)}m${((interval / 1000) % 60).toFixed(0)}s`; } } - public static getTimePassed(time: number): number { + static getTimePassed(time: number): number { if (time < 0) { return 0; } return Date.now() - time; } - public static getTimePassedString(time: number): string { + static getTimePassedString(time: number): string { return TimeUtils.getIntervalString(TimeUtils.getTimePassed(time)); } - public static getTimeString(time: number) { - let date = new Date(time); - return `${StringUtils.leftPad(date.getMonth() + 1, 2, '0')}/${StringUtils.leftPad(date.getDate(), 2, '0')} ${StringUtils.leftPad(date.getHours(), 2, '0')}:${StringUtils.leftPad(date.getMinutes(), 2, '0')}:${StringUtils.leftPad(date.getSeconds(), 2, '0')}`; + static getTimeString(time: number): string { + let date: Date = new Date(time); + // tslint:disable-next-line:max-line-length + return `${StringUtils.leftPad((date.getMonth() + 1).toString(), 2, "0")}/${StringUtils.leftPad(date.getDate().toString(), 2, "0")} ${StringUtils.leftPad(date.getHours().toString(), 2, "0")}:${StringUtils.leftPad(date.getMinutes().toString(), 2, "0")}:${StringUtils.leftPad(date.getSeconds().toString(), 2, "0")}`; } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ab0cbf1..1faf1e7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ - export * from "./Application"; export * from "./KanroManager"; import * as Core from "./Core"; @@ -7,7 +6,8 @@ import * as Http from "./Http"; import * as IO from "./IO"; import * as Logging from "./Logging"; import * as Router from "./Router"; +import * as Remoting from "./Remoting"; import * as Utils from "./Utils"; import * as Primitives from "./Primitives"; -export { Core, Exceptions, Http, IO, Logging, Router, Utils, Primitives }; \ No newline at end of file +export { Core, Exceptions, Http, IO, Logging, Router, Utils, Primitives, Remoting }; \ No newline at end of file diff --git a/src/test.ts b/src/test.ts index 2f3ec7c..e19c90e 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,3 +1,18 @@ -import * as Kanro from "."; +import * as DSL from "./DSL" -Kanro.Application.current.run(); \ No newline at end of file +const dsl = ` +npm"http://" +test"test" +` + +var result = DSL.KanroDSLLexer.tokenize(dsl); + +var parser = new DSL.KanroConfigParser(result.tokens); + +parser.test() + +if (parser.errors.length > 0) { + throw new Error("sad sad panda, Parsing errors detected") +} + +parser \ No newline at end of file diff --git a/src/test2.ts b/src/test2.ts new file mode 100644 index 0000000..8c0332f --- /dev/null +++ b/src/test2.ts @@ -0,0 +1,44 @@ +export class ServiceProxyHandler implements ProxyHandler { + getPrototypeOf(target: any): object | null { + throw new Error("getPrototypeOf"); + } + setPrototypeOf(target: any, v: any): boolean { + throw new Error("setPrototypeOf"); + } + isExtensible(target: any): boolean { + return false; + } + preventExtensions(target: any): boolean { + return true; + } + getOwnPropertyDescriptor(target: any, p: PropertyKey): PropertyDescriptor | undefined { + throw new Error("getOwnPropertyDescriptor"); + } + has(target: any, p: PropertyKey): boolean { + throw new Error("has"); + } + get(target: any, p: PropertyKey, receiver: any): any { + throw new Error("get"); + } + set(target: any, p: PropertyKey, value: any, receiver: any): boolean { + throw new Error("set"); + } + deleteProperty(target: any, p: PropertyKey): boolean { + throw new Error("deleteProperty"); + } + defineProperty(target: any, p: PropertyKey, attributes: PropertyDescriptor): boolean { + throw new Error("defineProperty"); + } + enumerate(target: any): PropertyKey[] { + throw new Error("enumerate"); + } + ownKeys(target: any): PropertyKey[] { + throw new Error("ownKeys"); + } + apply(target: any, thisArg: any, argArray?: any): any { + throw new Error("apply"); + } + construct(target: any, argArray: any, newTarget?: any): object { + throw new Error("construct"); + } +} \ No newline at end of file