diff --git a/CHANGELOG.md b/CHANGELOG.md index 32a94c8..c81ea38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## `1.2.0` June 3, 2018 + +### Add + +- Multi-process log system. + ## `1.1.3` May 20, 2018 ### Fix diff --git a/README.md b/README.md index 6f7df31..c76c640 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ ServerHub can do a lot of things now. No matter you want to start a static websi ## Latest Updates -`v1.1.0` supports auto-redirect HTTP traffic to HTTPS. Asynchronous plugins are supported since `v1.0.8`. TLS supported since `v1.0.6`, you can use your self-signed or verified certificate to hold an HTTPS connection for your websites. Module style of ServerHub controllers now supported (since `v1.0.4`). You can import your own libraries in your controllers now. Legacy ways of scripting controllers will also be supported. But a little workaround should be taken into consideration (deprecated usage). +`v1.2.0` supports **runtime** and **error** log service. They are available through `global` object. `v1.1.0` supports auto-redirect HTTP traffic to HTTPS. Asynchronous plugins are supported since `v1.0.8`. TLS supported since `v1.0.6`, you can use your self-signed or verified certificate to hold an HTTPS connection for your websites. Module style of ServerHub controllers now supported (since `v1.0.4`). You can import your own libraries in your controllers now. Legacy ways of scripting controllers will also be supported. But a little workaround should be taken into consideration (deprecated usage). Detailed update information could be found on [CHANGELOG.md](CHANGELOG.md) (English only). diff --git a/dist/lib/core/core.d.ts b/dist/lib/core/core.d.ts index 88e9c88..bf8e2bf 100644 --- a/dist/lib/core/core.d.ts +++ b/dist/lib/core/core.d.ts @@ -5,5 +5,5 @@ export declare function RegisterController(controllerJs: string): void; export declare function RegisterControllerM(controllerJs: string): void; export declare function UpdateGlobalVariable(variable: string, value: Object): boolean; export declare function SetGlobalVariable(variable: string, value: Object): void; -export declare function RoutePath(path: string, request: IncomingMessage, response: ServerResponse): void; +export declare function RoutePath(path: string, request: IncomingMessage, res: ServerResponse): void; export declare function RegisterRouter(route: Route): void; diff --git a/dist/lib/core/core.js b/dist/lib/core/core.js index 991fe1e..f77cbdb 100644 --- a/dist/lib/core/core.js +++ b/dist/lib/core/core.js @@ -8,6 +8,8 @@ const content_type_1 = require("./content-type"); const rcs_1 = require("./cache/rcs"); const index_1 = require("./helper/index"); const plugin_1 = require("./plugin"); +const server_1 = require("./server"); +const log_1 = require("./log"); const node_version = process.version; global['EnvironmentVariables'] = global['EnvironmentVariables'] ? global['EnvironmentVariables'] : { ServerBaseDir: __dirname, @@ -25,7 +27,15 @@ global['EnvironmentVariables'] = global['EnvironmentVariables'] ? global['Enviro Verbose: true, TLSOptions: void 0, RedirectToTLS: true, - Hostname: 'localhost' + Hostname: 'localhost', + LogConfig: { + Dir: 'log/', + MaxSize: 65536, + Access: true, + Error: true, + Runtime: true, + Filename: 'serverhub' + } }; const core_env = { platform: process.platform, @@ -51,9 +61,13 @@ function SetGlobalVariable(variable, value) { global['EnvironmentVariables'][variable] = value; } exports.SetGlobalVariable = SetGlobalVariable; -function RoutePath(path, request, response) { - response.setHeader('server', `ServerHub/${global['EnvironmentVariables'].PackageData['version']} (${core_env.platform}) Node.js ${core_env.node_version}`); - response.setHeader('x-powered-by', `ServerHub`); +function RoutePath(path, request, res) { + res.setHeader('server', `ServerHub/${global['EnvironmentVariables'].PackageData['version']} (${core_env.platform}) Node.js ${core_env.node_version}`); + res.setHeader('x-powered-by', `ServerHub`); + let response = new server_1.ServerHubResponse(res); + response.on('finish', () => { + log_1.LogAccess(request.connection.remoteAddress || '::1', path, response.__length__, request['user'] || 'guest', '-', request.method, request['secure'], request.httpVersion, response.statusCode); + }); let bPromise = plugin_1.BeforeRoute(request, response); let routeResult = ROUTE.RunRoute(path); let doneAfterRoutePluginExecution = (errCount) => { diff --git a/dist/lib/core/global.d.ts b/dist/lib/core/global.d.ts index b898f63..2639970 100644 --- a/dist/lib/core/global.d.ts +++ b/dist/lib/core/global.d.ts @@ -18,6 +18,15 @@ export interface GlobalEnvironmentVariables { TLSOptions: TLSConfiguration; RedirectToTLS: true; Hostname: string; + LogConfig: LogConfiguration; +} +export interface LogConfiguration { + Dir: string; + MaxSize: number; + Access: boolean; + Error: boolean; + Runtime: boolean; + Filename: string; } export interface TLSConfiguration { Port: number; diff --git a/dist/lib/core/log/index.d.ts b/dist/lib/core/log/index.d.ts new file mode 100644 index 0000000..88593a9 --- /dev/null +++ b/dist/lib/core/log/index.d.ts @@ -0,0 +1,7 @@ +import Access from "./log_access"; +import Runtime from "./log_runtime"; +import Error from "./log_error"; +declare class LogService { + static Start(): void; +} +export { Access as LogAccess, Runtime as LogRuntime, Error as LogError, LogService }; diff --git a/dist/lib/core/log/index.js b/dist/lib/core/log/index.js new file mode 100644 index 0000000..f713604 --- /dev/null +++ b/dist/lib/core/log/index.js @@ -0,0 +1,39 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const log_access_1 = require("./log_access"); +exports.LogAccess = log_access_1.default; +const log_runtime_1 = require("./log_runtime"); +exports.LogRuntime = log_runtime_1.default; +const log_error_1 = require("./log_error"); +exports.LogError = log_error_1.default; +const worker_manager_1 = require("./worker_manager"); +class LogService { + static Start() { + let variables = global['EnvironmentVariables']; + if (variables.LogConfig.Runtime) { + LogServiceCallback(worker_manager_1.EWorkerType.RUNTIME, () => { + log_runtime_1.default('system', 'ServerHub started'); + }); + } + if (variables.LogConfig.Access) { + LogServiceCallback(worker_manager_1.EWorkerType.ACCESS, () => { }); + } + if (variables.LogConfig.Error) { + LogServiceCallback(worker_manager_1.EWorkerType.ERROR, () => { }); + } + } +} +exports.LogService = LogService; +function LogServiceCallback(type, callback) { + switch (type) { + case worker_manager_1.EWorkerType.RUNTIME: + worker_manager_1.WorkerManager.GetInstace().ForkWorker(worker_manager_1.EWorkerType.RUNTIME).then(() => callback()); + break; + case worker_manager_1.EWorkerType.ACCESS: + worker_manager_1.WorkerManager.GetInstace().ForkWorker(worker_manager_1.EWorkerType.ACCESS).then(() => callback()); + break; + case worker_manager_1.EWorkerType.ERROR: + worker_manager_1.WorkerManager.GetInstace().ForkWorker(worker_manager_1.EWorkerType.ERROR).then(() => callback()); + break; + } +} diff --git a/dist/lib/core/log/log_access.d.ts b/dist/lib/core/log/log_access.d.ts new file mode 100644 index 0000000..8fbf4e8 --- /dev/null +++ b/dist/lib/core/log/log_access.d.ts @@ -0,0 +1 @@ +export default function (ip: string, path: string, length: number, user: string, identity?: string, method?: string, secure?: boolean, version?: string, status?: number): Promise; diff --git a/dist/lib/core/log/log_access.js b/dist/lib/core/log/log_access.js new file mode 100644 index 0000000..301e9b9 --- /dev/null +++ b/dist/lib/core/log/log_access.js @@ -0,0 +1,37 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const worker_manager_1 = require("./worker_manager"); +const _1 = require("../util/"); +function default_1(ip, path, length, user, identity = '-', method = 'GET', secure = true, version = '1.1', status = 200) { + return __awaiter(this, void 0, void 0, function* () { + let data = `${ip} ${identity} ${user} [${ConvertLogDate(new Date())}] "${method} ${path} HTTP${secure ? 'S' : ''}/${version}" ${status} ${length}`; + if (!worker_manager_1.WorkerManager.GetInstace().Status(worker_manager_1.EWorkerType.ACCESS)) { + yield worker_manager_1.WorkerManager.GetInstace().ForkWorker(worker_manager_1.EWorkerType.ACCESS); + worker_manager_1.WorkerManager.GetInstace().Use(worker_manager_1.EWorkerType.ACCESS, data); + } + else + worker_manager_1.WorkerManager.GetInstace().Use(worker_manager_1.EWorkerType.ACCESS, data); + }); +} +exports.default = default_1; +function ConvertLogDate(date) { + let offset = date.getTimezoneOffset(); + let offsetStr = ''; + if (offset < 0) { + offsetStr = '-' + ((Math.abs(offset) > 999 ? offset.toString() : () => Math.abs(offset) > 99) ? '0' + Math.abs(offset) : '00' + Math.abs(offset)); + } + else if (offset > 0) { + offsetStr = '+' + ((offset > 999 ? offset.toString() : () => offset > 99) ? '0' + offset : '00' + offset); + } + else + offsetStr = '0000'; + return `${_1.DateTime.GetDay(date.getDate())}/${_1.DateTime.GetMonth(date.getMonth())}/${_1.DateTime.GetYear(date.getFullYear())}:${_1.DateTime.GetHours(date.getHours())}:${_1.DateTime.GetMinutes(date.getMinutes())}:${_1.DateTime.GetSeconds(date.getSeconds())} ${offsetStr}`; +} diff --git a/dist/lib/core/log/log_error.d.ts b/dist/lib/core/log/log_error.d.ts new file mode 100644 index 0000000..d6a4a71 --- /dev/null +++ b/dist/lib/core/log/log_error.d.ts @@ -0,0 +1 @@ +export default function (type: string, message: string, note?: any[], source?: string): Promise; diff --git a/dist/lib/core/log/log_error.js b/dist/lib/core/log/log_error.js new file mode 100644 index 0000000..67cb6ba --- /dev/null +++ b/dist/lib/core/log/log_error.js @@ -0,0 +1,37 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const worker_manager_1 = require("./worker_manager"); +const _1 = require("../util/"); +function default_1(type, message, note = [], source = '') { + return __awaiter(this, void 0, void 0, function* () { + let data = ` [${ConvertLogDate(new Date())}] [${type}] [${note.join(' ')}] "${message}" ${source}`; + if (!worker_manager_1.WorkerManager.GetInstace().Status(worker_manager_1.EWorkerType.ERROR)) { + yield worker_manager_1.WorkerManager.GetInstace().ForkWorker(worker_manager_1.EWorkerType.ERROR); + worker_manager_1.WorkerManager.GetInstace().Use(worker_manager_1.EWorkerType.ERROR, data); + } + else + worker_manager_1.WorkerManager.GetInstace().Use(worker_manager_1.EWorkerType.ERROR, data); + }); +} +exports.default = default_1; +function ConvertLogDate(date) { + let offset = date.getTimezoneOffset(); + let offsetStr = ''; + if (offset < 0) { + offsetStr = '-' + ((Math.abs(offset) > 999 ? offset.toString() : () => Math.abs(offset) > 99) ? '0' + Math.abs(offset) : '00' + Math.abs(offset)); + } + else if (offset > 0) { + offsetStr = '+' + ((offset > 999 ? offset.toString() : () => offset > 99) ? '0' + offset : '00' + offset); + } + else + offsetStr = '0000'; + return `${_1.DateTime.GetDayName(date.getDay())} ${_1.DateTime.GetMonth(date.getMonth())} ${_1.DateTime.GetDay(date.getDate())} ${_1.DateTime.GetHours(date.getHours())}:${_1.DateTime.GetMinutes(date.getMinutes())}:${_1.DateTime.GetSeconds(date.getSeconds())} ${offsetStr}`; +} diff --git a/dist/lib/core/log/log_runtime.d.ts b/dist/lib/core/log/log_runtime.d.ts new file mode 100644 index 0000000..195c8af --- /dev/null +++ b/dist/lib/core/log/log_runtime.d.ts @@ -0,0 +1 @@ +export default function (role: string, message: string): Promise; diff --git a/dist/lib/core/log/log_runtime.js b/dist/lib/core/log/log_runtime.js new file mode 100644 index 0000000..a7b1205 --- /dev/null +++ b/dist/lib/core/log/log_runtime.js @@ -0,0 +1,37 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const worker_manager_1 = require("./worker_manager"); +const _1 = require("../util/"); +function default_1(role, message) { + return __awaiter(this, void 0, void 0, function* () { + let data = `${role} [${ConvertLogDate(new Date())}] "${message}"`; + if (!worker_manager_1.WorkerManager.GetInstace().Status(worker_manager_1.EWorkerType.RUNTIME)) { + yield worker_manager_1.WorkerManager.GetInstace().ForkWorker(worker_manager_1.EWorkerType.RUNTIME); + worker_manager_1.WorkerManager.GetInstace().Use(worker_manager_1.EWorkerType.RUNTIME, data); + } + else + worker_manager_1.WorkerManager.GetInstace().Use(worker_manager_1.EWorkerType.RUNTIME, data); + }); +} +exports.default = default_1; +function ConvertLogDate(date) { + let offset = date.getTimezoneOffset(); + let offsetStr = ''; + if (offset < 0) { + offsetStr = '-' + ((Math.abs(offset) > 999 ? offset.toString() : () => Math.abs(offset) > 99) ? '0' + Math.abs(offset) : '00' + Math.abs(offset)); + } + else if (offset > 0) { + offsetStr = '+' + ((offset > 999 ? offset.toString() : () => offset > 99) ? '0' + offset : '00' + offset); + } + else + offsetStr = '0000'; + return `${_1.DateTime.GetDay(date.getDate())}/${_1.DateTime.GetMonth(date.getMonth())}/${_1.DateTime.GetYear(date.getFullYear())}:${_1.DateTime.GetHours(date.getHours())}:${_1.DateTime.GetMinutes(date.getMinutes())}:${_1.DateTime.GetSeconds(date.getSeconds())} ${offsetStr}`; +} diff --git a/dist/lib/core/log/worker.d.ts b/dist/lib/core/log/worker.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/lib/core/log/worker.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/lib/core/log/worker.js b/dist/lib/core/log/worker.js new file mode 100644 index 0000000..bed071c --- /dev/null +++ b/dist/lib/core/log/worker.js @@ -0,0 +1,71 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const path = require("path"); +const fs = require("fs"); +const BUFFER = Buffer.alloc(65536 * 8); +const BUFFER_STATUS = { + DATA_FLAG: false, + LENGTH: 0 +}; +const StreamWrapper = (ws) => { + ws.on('drain', () => { + if (BUFFER_STATUS.DATA_FLAG !== false) { + let buffer = BUFFER.toString('utf8', 0, BUFFER_STATUS.LENGTH); + BUFFER.fill(0); + BUFFER_STATUS.DATA_FLAG = false; + BUFFER_STATUS.LENGTH = 0; + ws.write(buffer); + } + }); +}; +let filename = 'serverhub'; +let maxsize = 65536; +process.argv.forEach(arg => { + if (arg.match(/^filename=[^=/\\> { + try { + if (logstream.bytesWritten > maxsize) { + const newstream = () => { + logstream.close(); + logstream = fs.createWriteStream(path.resolve(process.cwd(), `${filename}-${new Date().getTime()}.log`)); + logstream.on('open', () => { + StreamWrapper(logstream); + logstream.write(data); + }); + }; + if (logstream.writable) { + newstream(); + } + else + logstream.on('drain', newstream); + } + else { + if (logstream.writable) { + logstream.write(data); + } + else { + BUFFER.write(data, BUFFER_STATUS.LENGTH, data.length); + BUFFER_STATUS.DATA_FLAG = true; + BUFFER_STATUS.LENGTH += data.length; + } + } + } + catch (err) { + process.send({ + error: err + }); + } +}); diff --git a/dist/lib/core/log/worker_manager.d.ts b/dist/lib/core/log/worker_manager.d.ts new file mode 100644 index 0000000..eece487 --- /dev/null +++ b/dist/lib/core/log/worker_manager.d.ts @@ -0,0 +1,15 @@ +declare enum EWorkerType { + ACCESS = 0, + ERROR = 1, + RUNTIME = 2, +} +declare class WorkerManager { + private constructor(); + private static _instance; + private _workers; + static GetInstace(): WorkerManager; + ForkWorker(type: EWorkerType): Promise<{}>; + Use(type: EWorkerType, data: string): void; + Status(type: EWorkerType): boolean; +} +export { EWorkerType, WorkerManager }; diff --git a/dist/lib/core/log/worker_manager.js b/dist/lib/core/log/worker_manager.js new file mode 100644 index 0000000..a828d41 --- /dev/null +++ b/dist/lib/core/log/worker_manager.js @@ -0,0 +1,109 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const child_process_1 = require("child_process"); +const fs = require("fs"); +const path = require("path"); +var EWorkerType; +(function (EWorkerType) { + EWorkerType[EWorkerType["ACCESS"] = 0] = "ACCESS"; + EWorkerType[EWorkerType["ERROR"] = 1] = "ERROR"; + EWorkerType[EWorkerType["RUNTIME"] = 2] = "RUNTIME"; +})(EWorkerType || (EWorkerType = {})); +exports.EWorkerType = EWorkerType; +class WorkerManager { + constructor() { + this._workers = { + access: void 0, + runtime: void 0, + error: void 0 + }; + } + static GetInstace() { + return WorkerManager._instance; + } + ForkWorker(type) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((res, rej) => { + try { + let variables = global['EnvironmentVariables']; + let log_path = path.resolve(variables.ServerBaseDir, variables.LogConfig.Dir); + let typestr = ''; + switch (type) { + case EWorkerType.ACCESS: + typestr = 'access'; + break; + case EWorkerType.ERROR: + typestr = 'error'; + break; + case EWorkerType.RUNTIME: + typestr = 'runtime'; + break; + } + if (!fs.existsSync(log_path)) + fs.mkdirSync(log_path); + const loop_till_created = () => { + if (childp.connected) + res(); + else + process.nextTick(loop_till_created); + }; + let childp = child_process_1.fork(path.resolve(__dirname, './worker.js'), [`filename=${variables.LogConfig.Filename}-${typestr}`, `maxsize=${variables.LogConfig.MaxSize}`], { + cwd: log_path + }); + childp.on('exit', (code) => { + WorkerManager.GetInstace().ForkWorker(type); + }); + childp.on('message', (m) => { + if (m instanceof Object && m.error) { + WorkerManager.GetInstace().ForkWorker(type); + } + else { + console.log(m); + } + }); + childp.on('error', e => { + console.error(e); + }); + this._workers[typestr] = childp; + process.nextTick(loop_till_created); + } + catch (err) { + rej(err); + } + }); + }); + } + Use(type, data) { + switch (type) { + case EWorkerType.RUNTIME: + this._workers.runtime.send(data + '\n'); + break; + case EWorkerType.ACCESS: + this._workers.access.send(data + '\n'); + break; + case EWorkerType.ERROR: + this._workers.error.send(data + '\n'); + break; + } + } + Status(type) { + switch (type) { + case EWorkerType.RUNTIME: + return this._workers.runtime && !this._workers.runtime.killed || false; + case EWorkerType.ACCESS: + return this._workers.access && !this._workers.access.killed || false; + case EWorkerType.ERROR: + return this._workers.error && !this._workers.error.killed || false; + } + } +} +WorkerManager._instance = new WorkerManager(); +exports.WorkerManager = WorkerManager; diff --git a/dist/lib/core/server/index.d.ts b/dist/lib/core/server/index.d.ts index ed3843f..550974d 100644 --- a/dist/lib/core/server/index.d.ts +++ b/dist/lib/core/server/index.d.ts @@ -1,4 +1,5 @@ import { Headers, IsValidHeaders, IsValidHeader, GetReasonMessage, TransformHeader, FormatDate } from "./head"; +import { ServerHubResponse } from './response'; declare const Head: { IsValidHeaders: typeof IsValidHeaders; IsValidHeader: typeof IsValidHeader; @@ -6,4 +7,4 @@ declare const Head: { TransformHeader: typeof TransformHeader; FormatDate: typeof FormatDate; }; -export { Head, Headers }; +export { Head, Headers, ServerHubResponse }; diff --git a/dist/lib/core/server/index.js b/dist/lib/core/server/index.js index f255ab8..e726bf2 100644 --- a/dist/lib/core/server/index.js +++ b/dist/lib/core/server/index.js @@ -1,6 +1,8 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const head_1 = require("./head"); +const response_1 = require("./response"); +exports.ServerHubResponse = response_1.ServerHubResponse; const Head = { IsValidHeaders: head_1.IsValidHeaders, IsValidHeader: head_1.IsValidHeader, diff --git a/dist/lib/core/server/response.d.ts b/dist/lib/core/server/response.d.ts new file mode 100644 index 0000000..8d7ede4 --- /dev/null +++ b/dist/lib/core/server/response.d.ts @@ -0,0 +1,68 @@ +/// +import { ServerResponse, OutgoingHttpHeaders } from "http"; +import * as net from 'net'; +declare class ServerHubResponse implements ServerResponse { + __length__: number; + readonly __res__: ServerResponse; + private res; + statusCode: number; + statusMessage: string; + upgrading: boolean; + chunkedEncoding: boolean; + shouldKeepAlive: boolean; + useChunkedEncodingByDefault: boolean; + sendDate: boolean; + readonly finished: boolean; + readonly headersSent: boolean; + connection: net.Socket; + writable: boolean; + readonly writableHighWaterMark: number; + constructor(res: ServerResponse); + write(chunk: any, cb?: Function): boolean; + write(chunk: any, encoding?: string, cb?: Function): boolean; + _write(chunk: any, encoding: string, callback: (err?: Error) => void): void; + _writev?(chunks: Array<{ + chunk: any; + encoding: string; + }>, callback: (err?: Error) => void): void; + _destroy(err: Error, callback: Function): void; + _final(callback: Function): void; + pipe(destination: T, options?: { + end?: boolean; + }): T; + setDefaultEncoding(encoding: string): this; + end(cb?: Function): void; + end(chunk: any, cb?: Function): void; + end(chunk: any, encoding?: string, cb?: Function): void; + cork(): void; + uncork(): void; + assignSocket(socket: net.Socket): void; + detachSocket(socket: net.Socket): void; + writeContinue(callback?: () => void): void; + writeHead(statusCode: number, headers?: OutgoingHttpHeaders): void; + writeHead(statusCode: number, reasonPhrase?: string, headers?: OutgoingHttpHeaders): void; + setTimeout(msecs: number, callback?: () => void): this; + destroy(error: Error): void; + setHeader(name: string, value: number | string | string[]): void; + getHeader(name: string): number | string | string[] | undefined; + getHeaders(): OutgoingHttpHeaders; + getHeaderNames(): string[]; + hasHeader(name: string): boolean; + removeHeader(name: string): void; + addTrailers(headers: OutgoingHttpHeaders | Array<[string, string]>): void; + flushHeaders(): void; + addListener(event: string, listener: (...args: any[]) => void): this; + emit(event: string | symbol, ...args: any[]): boolean; + on(event: string, listener: (...args: any[]) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + removeAllListeners(event?: string | symbol): this; + setMaxListeners(n: number): this; + getMaxListeners(): number; + listeners(event: string | symbol): Function[]; + listenerCount(type: string | symbol): number; + eventNames(): Array; +} +export { ServerHubResponse }; diff --git a/dist/lib/core/server/response.js b/dist/lib/core/server/response.js new file mode 100644 index 0000000..730a4c9 --- /dev/null +++ b/dist/lib/core/server/response.js @@ -0,0 +1,197 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +class ServerHubResponse { + constructor(res) { + this.__length__ = 0; + this.res = res; + } + get __res__() { + return this.res; + } + get statusCode() { + return this.res.statusCode; + } + ; + get statusMessage() { + return this.res.statusMessage; + } + get upgrading() { + return this.res.upgrading; + } + ; + get chunkedEncoding() { + return this.res.chunkedEncoding; + } + ; + get shouldKeepAlive() { + return this.res.shouldKeepAlive; + } + ; + get useChunkedEncodingByDefault() { + return this.res.useChunkedEncodingByDefault; + } + ; + get sendDate() { + return this.res.sendDate; + } + ; + get finished() { + return this.res.finished; + } + ; + get headersSent() { + return this.res.headersSent; + } + ; + get connection() { + return this.res.connection; + } + ; + get writable() { + return this.res.writable; + } + ; + set statusCode(number) { + this.res.statusCode = number; + } + set statusMessage(m) { + this.res.statusMessage = m; + } + set upgrading(v) { + this.res.upgrading = v; + } + ; + set chunkedEncoding(v) { + this.res.chunkedEncoding = v; + } + ; + set shouldKeepAlive(v) { + this.res.shouldKeepAlive = v; + } + ; + set useChunkedEncodingByDefault(v) { + this.res.useChunkedEncodingByDefault = v; + } + ; + set sendDate(v) { + this.res.sendDate = v; + } + ; + set connection(s) { + this.res.connection = s; + } + ; + set writable(v) { + this.res.writable = v; + } + ; + get writableHighWaterMark() { + return this.res.writableHighWaterMark; + } + ; + write(chunk, encoding, cb) { + if (typeof chunk === 'string') + this.__length__ += chunk.length; + else if (chunk instanceof Buffer) + this.__length__ += chunk.byteLength; + else + this.__length__ += chunk.toString().length; + if (encoding && typeof encoding === 'string') + return this.res.write(chunk.toString(), encoding, cb); + else + return this.res.write(chunk, cb); + } + _write(chunk, encoding, callback) { this.res._write(chunk, encoding, callback); } + ; + _writev(chunks, callback) { this.res._writev(chunks, callback); } + ; + _destroy(err, callback) { this.res._destroy(err, callback); } + ; + _final(callback) { this.res._final(callback); } + ; + pipe(destination, options) { return this.res.pipe(destination, options); } + ; + setDefaultEncoding(encoding) { this.res.setDefaultEncoding(encoding); return this; } + ; + end(chunk, encoding, cb) { + if (typeof chunk === 'string') + this.__length__ += chunk.length; + else if (chunk instanceof Buffer) + this.__length__ += chunk.byteLength; + else if (chunk && !(chunk instanceof Function)) { + this.__length__ += chunk.toString().length; + } + else + return this.res.end(chunk); + if (encoding && typeof encoding === 'string') + return this.res.end(chunk.toString(), encoding, cb); + else + return this.res.end(chunk, cb); + } + ; + cork() { this.res.cork(); } + ; + uncork() { this.res.uncork(); } + ; + assignSocket(socket) { this.res.assignSocket(socket); } + ; + detachSocket(socket) { this.res.detachSocket(socket); } + ; + writeContinue(callback) { this.res.writeContinue(callback); } + ; + writeHead(statusCode, reasonPhrase, headers) { + if (reasonPhrase && typeof reasonPhrase === 'string') { + this.res.writeHead(statusCode, reasonPhrase, headers); + } + else + this.res.writeHead(statusCode, reasonPhrase); + } + ; + setTimeout(msecs, callback) { this.res.setTimeout(msecs, callback); return this; } + ; + destroy(error) { return this.res.destroy(error); } + ; + setHeader(name, value) { return this.res.setHeader(name, value); } + ; + getHeader(name) { return this.res.getHeader(name); } + ; + getHeaders() { return this.res.getHeaders(); } + ; + getHeaderNames() { return this.res.getHeaderNames(); } + ; + hasHeader(name) { return this.res.hasHeader(name); } + ; + removeHeader(name) { return this.res.removeHeader(name); } + ; + addTrailers(headers) { return this.res.addTrailers(headers); } + ; + flushHeaders() { return this.res.flushHeaders(); } + ; + addListener(event, listener) { this.res.addListener(event, listener); return this; } + ; + emit(event, ...args) { return this.res.emit(event, ...args); } + ; + on(event, listener) { this.res.on(event, listener); return this; } + ; + once(event, listener) { this.res.once(event, listener); return this; } + ; + prependListener(event, listener) { this.res.prependListener(event, listener); return this; } + ; + prependOnceListener(event, listener) { this.res.prependOnceListener; return this; } + ; + removeListener(event, listener) { this.res.removeListener(event, listener); return this; } + ; + removeAllListeners(event) { this.res.removeAllListeners(event); return this; } + ; + setMaxListeners(n) { this.res.setMaxListeners(n); return this; } + ; + getMaxListeners() { return this.res.getMaxListeners(); } + ; + listeners(event) { return this.res.listeners(event); } + ; + listenerCount(type) { return this.res.listenerCount(type); } + ; + eventNames() { return this.res.eventNames(); } + ; +} +exports.ServerHubResponse = ServerHubResponse; diff --git a/index.d.ts b/index.d.ts index 47f7f82..acca810 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,5 @@ import { Route } from "./dist/lib/route/route"; -import { TLSConfiguration } from "./dist/lib/core/global"; +import { TLSConfiguration, LogConfiguration } from "./dist/lib/core/global"; export declare function Run(config: ServerHubConfig, appstart: (route: Route) => void): void; @@ -21,6 +21,7 @@ export declare interface ServerHubConfig { SSLOptions: TLSConfiguration; RedirectToTLS: boolean; Hostname: string; + LogConfig: LogConfiguration } export declare function Module(name: string): any; diff --git a/index.js b/index.js index 05b6a57..56e63d1 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,11 @@ const https = require('https'); const package = require('./package.json'); const path = require('path'); const fs = require('fs'); +const logService = require('./dist/lib/core/log/').LogService; +const { + LogError, + LogRuntime +} = require('./dist/lib/core/log/'); const AutoRegister = require('./dist/lib/core/plugin/').AutoRegister; const { LoadModule, @@ -168,6 +173,18 @@ exports.Run = (config, appstart) => { } } + if (config['LogConfig']) { + let cfg = config['LogConfig']; + let temp = {}; + if (cfg.Dir) temp['Dir'] = cfg.Dir; + if (cfg.Access && cfg.Access === false) temp['Access'] = false; + if (cfg.Runtime && cfg.Runtime === false) temp['Runtime'] = false; + if (cfg.Error && cfg.Error === false) temp['Error'] = false; + if (cfg.Filename) temp['Filename'] = cfg.Filename; + if (cfg.MaxSize && cfg.MaxSize > 0) temp['MaxSize'] = cfg.MaxSize; + libcore.SetGlobalVariable('LogConfig', temp); + } + if (appstart) appstart(libroute.Route.GetRoute()); @@ -276,6 +293,7 @@ exports.Run = (config, appstart) => { }) // server.listen(port); console.log('>> Server started on port:', colors.green(port.join(', '))); + logService.Start(); } catch (e) { console.error('!! Server cannot start and listen to', colors.red(port.join(', '))); console.error(' There might be another instance process of ServerHub. Please check and attempt to start later.') @@ -295,4 +313,8 @@ global_load_from_properties.map(p => { }) global_load_properties.map(p => { global[p] = LoadModule; -}) \ No newline at end of file +}) +global['LogService'] = { + Runtime: LogRuntime, + Error: LogError +} \ No newline at end of file diff --git a/lib/core/core.ts b/lib/core/core.ts index 78ea9aa..4477810 100644 --- a/lib/core/core.ts +++ b/lib/core/core.ts @@ -18,6 +18,8 @@ import { RCS } from './cache/rcs'; import { CacheHelper } from "./helper/index"; import { BeforeRoute, AfterRoute } from './plugin'; import * as colors from "colors"; +import { ServerHubResponse } from './server'; +import { LogAccess } from './log'; const node_version = process.version; @@ -39,7 +41,15 @@ global['EnvironmentVariables'] = global['EnvironmentVariables'] ? global['Enviro Verbose: true, TLSOptions: void 0, RedirectToTLS: true, - Hostname: 'localhost' + Hostname: 'localhost', + LogConfig: { + Dir: 'log/', + MaxSize: 65536, + Access: true, + Error: true, + Runtime: true, + Filename: 'serverhub' + } } as GlobalEnvironmentVariables; /** @@ -87,9 +97,14 @@ export function SetGlobalVariable (variable: string, value: Object): void { * @param req Incomming message (request) * @param res Server response (response) */ -export function RoutePath (path: string, request: IncomingMessage, response: ServerResponse): void { - response.setHeader('server', `ServerHub/${(global['EnvironmentVariables'] as GlobalEnvironmentVariables).PackageData['version']} (${core_env.platform}) Node.js ${core_env.node_version}`); - response.setHeader('x-powered-by', `ServerHub`); +export function RoutePath (path: string, request: IncomingMessage, res: ServerResponse): void { + res.setHeader('server', `ServerHub/${(global['EnvironmentVariables'] as GlobalEnvironmentVariables).PackageData['version']} (${core_env.platform}) Node.js ${core_env.node_version}`); + res.setHeader('x-powered-by', `ServerHub`); + + let response = new ServerHubResponse(res); + response.on('finish', () => { + LogAccess(request.connection.remoteAddress || '::1', path, response.__length__, request['user'] || 'guest', '-', request.method, request['secure'], request.httpVersion, response.statusCode); + }) let bPromise = BeforeRoute(request, response); let routeResult = ROUTE.RunRoute(path); diff --git a/lib/core/global.ts b/lib/core/global.ts index 568ca96..40ec312 100644 --- a/lib/core/global.ts +++ b/lib/core/global.ts @@ -31,8 +31,17 @@ export interface GlobalEnvironmentVariables { TLSOptions: TLSConfiguration; RedirectToTLS: true; Hostname: string; + LogConfig: LogConfiguration; }; +export interface LogConfiguration { + Dir: string; + MaxSize: number; + Access: boolean; + Error: boolean; + Runtime: boolean; + Filename: string; +} export interface TLSConfiguration { Port: number; Cert: string; diff --git a/lib/core/log/index.ts b/lib/core/log/index.ts new file mode 100644 index 0000000..b1b448e --- /dev/null +++ b/lib/core/log/index.ts @@ -0,0 +1,34 @@ +import Access from "./log_access"; +import Runtime from "./log_runtime"; +import Error from "./log_error"; +import { EWorkerType, WorkerManager } from "./worker_manager"; +import { GlobalEnvironmentVariables } from "../global"; + +class LogService { + static Start () { + let variables = global['EnvironmentVariables'] as GlobalEnvironmentVariables; + if (variables.LogConfig.Runtime) { + LogServiceCallback(EWorkerType.RUNTIME, () => { + Runtime('system', 'ServerHub started'); + }) + } + if (variables.LogConfig.Access) { + LogServiceCallback(EWorkerType.ACCESS, () => { }) + } + + if (variables.LogConfig.Error) { + LogServiceCallback(EWorkerType.ERROR, () => { }) + } + } +} + +function LogServiceCallback (type: EWorkerType, callback: Function) { + switch (type) { + case EWorkerType.RUNTIME: WorkerManager.GetInstace().ForkWorker(EWorkerType.RUNTIME).then(() => callback()); break; + case EWorkerType.ACCESS: WorkerManager.GetInstace().ForkWorker(EWorkerType.ACCESS).then(() => callback()); break; + case EWorkerType.ERROR: WorkerManager.GetInstace().ForkWorker(EWorkerType.ERROR).then(() => callback()); break; + } +} + + +export { Access as LogAccess, Runtime as LogRuntime, Error as LogError, LogService }; \ No newline at end of file diff --git a/lib/core/log/log_access.ts b/lib/core/log/log_access.ts new file mode 100644 index 0000000..935519d --- /dev/null +++ b/lib/core/log/log_access.ts @@ -0,0 +1,27 @@ +import { WorkerManager, EWorkerType } from "./worker_manager"; +import { DateTime } from "../util/"; + + +export default async function (ip: string, path: string, length: number, user: string, identity = '-', method = 'GET', secure = true, version = '1.1', status = 200) { + // 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 + // ip client_identity authenticated_user [Date/Month/Year:Hour:Minute:Seconds Zone] "HTTP_method request_path HTTP_version" status_code content_length + let data = `${ip} ${identity} ${user} [${ConvertLogDate(new Date())}] "${method} ${path} HTTP${secure ? 'S' : ''}/${version}" ${status} ${length}`; + + if (!WorkerManager.GetInstace().Status(EWorkerType.ACCESS)) { + await WorkerManager.GetInstace().ForkWorker(EWorkerType.ACCESS); + WorkerManager.GetInstace().Use(EWorkerType.ACCESS, data); + } else + WorkerManager.GetInstace().Use(EWorkerType.ACCESS, data); +} + +function ConvertLogDate (date: Date) { + let offset = date.getTimezoneOffset(); + let offsetStr = ''; + if (offset < 0) { + offsetStr = '-' + ((Math.abs(offset) > 999 ? offset.toString() : () => Math.abs(offset) > 99) ? '0' + Math.abs(offset) : '00' + Math.abs(offset)); + } else if (offset > 0) { + offsetStr = '+' + ((offset > 999 ? offset.toString() : () => offset > 99) ? '0' + offset : '00' + offset); + } else + offsetStr = '0000'; + return `${DateTime.GetDay(date.getDate())}/${DateTime.GetMonth(date.getMonth())}/${DateTime.GetYear(date.getFullYear())}:${DateTime.GetHours(date.getHours())}:${DateTime.GetMinutes(date.getMinutes())}:${DateTime.GetSeconds(date.getSeconds())} ${offsetStr}` +} \ No newline at end of file diff --git a/lib/core/log/log_error.ts b/lib/core/log/log_error.ts new file mode 100644 index 0000000..0bc7bdd --- /dev/null +++ b/lib/core/log/log_error.ts @@ -0,0 +1,26 @@ +import { WorkerManager, EWorkerType } from "./worker_manager"; +import { DateTime } from "../util/"; + + +export default async function (type: string, message: string, note = [], source = '') { + // [Wed Oct 11 14:32:52 2000] [error] [client 127.0.0.1] client denied by server configuration: /export/home/live/ap/htdocs/test + let data = ` [${ConvertLogDate(new Date())}] [${type}] [${note.join(' ')}] "${message}" ${source}`; + + if (!WorkerManager.GetInstace().Status(EWorkerType.ERROR)) { + await WorkerManager.GetInstace().ForkWorker(EWorkerType.ERROR); + WorkerManager.GetInstace().Use(EWorkerType.ERROR, data); + } else + WorkerManager.GetInstace().Use(EWorkerType.ERROR, data); +} + +function ConvertLogDate (date: Date) { + let offset = date.getTimezoneOffset(); + let offsetStr = ''; + if (offset < 0) { + offsetStr = '-' + ((Math.abs(offset) > 999 ? offset.toString() : () => Math.abs(offset) > 99) ? '0' + Math.abs(offset) : '00' + Math.abs(offset)); + } else if (offset > 0) { + offsetStr = '+' + ((offset > 999 ? offset.toString() : () => offset > 99) ? '0' + offset : '00' + offset); + } else + offsetStr = '0000'; + return `${DateTime.GetDayName(date.getDay())} ${DateTime.GetMonth(date.getMonth())} ${DateTime.GetDay(date.getDate())} ${DateTime.GetHours(date.getHours())}:${DateTime.GetMinutes(date.getMinutes())}:${DateTime.GetSeconds(date.getSeconds())} ${offsetStr}` +} \ No newline at end of file diff --git a/lib/core/log/log_runtime.ts b/lib/core/log/log_runtime.ts new file mode 100644 index 0000000..c99aa2f --- /dev/null +++ b/lib/core/log/log_runtime.ts @@ -0,0 +1,26 @@ +import { WorkerManager, EWorkerType } from "./worker_manager"; +import { DateTime } from "../util/"; + + +export default async function (role: string, message: string) { + // system [10/Oct/2000:13:55:36 -0700] "Service started" + let data = `${role} [${ConvertLogDate(new Date())}] "${message}"`; + + if (!WorkerManager.GetInstace().Status(EWorkerType.RUNTIME)) { + await WorkerManager.GetInstace().ForkWorker(EWorkerType.RUNTIME); + WorkerManager.GetInstace().Use(EWorkerType.RUNTIME, data); + } else + WorkerManager.GetInstace().Use(EWorkerType.RUNTIME, data); +} + +function ConvertLogDate (date: Date) { + let offset = date.getTimezoneOffset(); + let offsetStr = ''; + if (offset < 0) { + offsetStr = '-' + ((Math.abs(offset) > 999 ? offset.toString() : () => Math.abs(offset) > 99) ? '0' + Math.abs(offset) : '00' + Math.abs(offset)); + } else if (offset > 0) { + offsetStr = '+' + ((offset > 999 ? offset.toString() : () => offset > 99) ? '0' + offset : '00' + offset); + } else + offsetStr = '0000'; + return `${DateTime.GetDay(date.getDate())}/${DateTime.GetMonth(date.getMonth())}/${DateTime.GetYear(date.getFullYear())}:${DateTime.GetHours(date.getHours())}:${DateTime.GetMinutes(date.getMinutes())}:${DateTime.GetSeconds(date.getSeconds())} ${offsetStr}` +} \ No newline at end of file diff --git a/lib/core/log/worker.ts b/lib/core/log/worker.ts new file mode 100644 index 0000000..a313cfa --- /dev/null +++ b/lib/core/log/worker.ts @@ -0,0 +1,69 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import { WriteStream } from 'fs'; + +const BUFFER = Buffer.alloc(65536 * 8); // 8M buffer for each worker process. +const BUFFER_STATUS = { + DATA_FLAG: false, + LENGTH: 0 +}; +const StreamWrapper = (ws: WriteStream) => { + ws.on('drain', () => { + if (BUFFER_STATUS.DATA_FLAG !== false) { + let buffer = BUFFER.toString('utf8', 0, BUFFER_STATUS.LENGTH); + BUFFER.fill(0); + BUFFER_STATUS.DATA_FLAG = false; + BUFFER_STATUS.LENGTH = 0; + ws.write(buffer); + } + }); +} + +let filename = 'serverhub'; +let maxsize = 65536; //bytes + + +process.argv.forEach(arg => { + if (arg.match(/^filename=[^=/\\> { + try { + if (logstream.bytesWritten > maxsize) { + const newstream = () => { + logstream.close(); + logstream = fs.createWriteStream(path.resolve(process.cwd(), `${filename}-${new Date().getTime()}.log`)) + logstream.on('open', () => { + StreamWrapper(logstream); + logstream.write(data); + }); + } + if (logstream.writable) { + newstream(); + } else logstream.on('drain', newstream); + } else { + if (logstream.writable) { + logstream.write(data); + } else { + BUFFER.write(data, BUFFER_STATUS.LENGTH, data.length); + BUFFER_STATUS.DATA_FLAG = true; + BUFFER_STATUS.LENGTH += data.length; + } + } + } catch (err) { + process.send({ + error: err + }) + } +}) diff --git a/lib/core/log/worker_manager.ts b/lib/core/log/worker_manager.ts new file mode 100644 index 0000000..bde57c3 --- /dev/null +++ b/lib/core/log/worker_manager.ts @@ -0,0 +1,96 @@ +import { fork, ChildProcess } from "child_process"; +import * as fs from "fs"; +import * as path from "path"; +import { GlobalEnvironmentVariables } from "../global"; + +enum EWorkerType { + ACCESS = 0, + ERROR = 1, + RUNTIME = 2 +} + +interface IWorkerCollection { + [x: string]: ChildProcess +} + +class WorkerManager { + private constructor() { } + private static _instance = new WorkerManager(); + private _workers = { + access: void 0, + runtime: void 0, + error: void 0 + } as IWorkerCollection; + + public static GetInstace (): WorkerManager { + return WorkerManager._instance; + } + + public async ForkWorker (type: EWorkerType) { + return new Promise((res, rej) => { + try { + let variables = global['EnvironmentVariables'] as GlobalEnvironmentVariables; + let log_path = path.resolve(variables.ServerBaseDir, variables.LogConfig.Dir); + let typestr = ''; + switch (type) { + case EWorkerType.ACCESS: typestr = 'access'; break; + case EWorkerType.ERROR: typestr = 'error'; break; + case EWorkerType.RUNTIME: typestr = 'runtime'; break; + } + + if (!fs.existsSync(log_path)) + fs.mkdirSync(log_path) + + const loop_till_created = () => { + if (childp.connected) + res(); + else process.nextTick(loop_till_created) + }; + + let childp = fork(path.resolve(__dirname, './worker.js'), [`filename=${variables.LogConfig.Filename}-${typestr}`, `maxsize=${variables.LogConfig.MaxSize}`], { + cwd: log_path + }); + childp.on('exit', (code) => { + WorkerManager.GetInstace().ForkWorker(type); + }); + childp.on('message', (m) => { + if (m instanceof Object && m.error) { + WorkerManager.GetInstace().ForkWorker(type); + } else { + console.log(m); + } + }) + childp.on('error', e => { + console.error(e); + }) + this._workers[typestr] = childp; + process.nextTick(loop_till_created); + } catch (err) { + rej(err); + } + }) + } + + public Use (type: EWorkerType, data: string): void { + switch (type) { + case EWorkerType.RUNTIME: + this._workers.runtime.send(data + '\n'); break; + case EWorkerType.ACCESS: + this._workers.access.send(data + '\n'); break; + case EWorkerType.ERROR: + this._workers.error.send(data + '\n'); break; + } + } + public Status (type: EWorkerType): boolean { + switch (type) { + case EWorkerType.RUNTIME: + return this._workers.runtime && !this._workers.runtime.killed || false; + case EWorkerType.ACCESS: + return this._workers.access && !this._workers.access.killed || false; + case EWorkerType.ERROR: + return this._workers.error && !this._workers.error.killed || false; + } + } +} + +export { EWorkerType, WorkerManager }; \ No newline at end of file diff --git a/lib/core/server/index.ts b/lib/core/server/index.ts index f2000ad..55a437d 100644 --- a/lib/core/server/index.ts +++ b/lib/core/server/index.ts @@ -7,6 +7,7 @@ */ import { Headers, IsValidHeaders, IsValidHeader, GetReasonMessage, TransformHeader, FormatDate } from "./head"; +import { ServerHubResponse } from './response' const Head = { IsValidHeaders: IsValidHeaders, IsValidHeader: IsValidHeader, @@ -14,4 +15,4 @@ const Head = { TransformHeader: TransformHeader, FormatDate: FormatDate }; -export { Head, Headers }; \ No newline at end of file +export { Head, Headers, ServerHubResponse }; \ No newline at end of file diff --git a/lib/core/server/response.ts b/lib/core/server/response.ts new file mode 100644 index 0000000..83a26bb --- /dev/null +++ b/lib/core/server/response.ts @@ -0,0 +1,150 @@ +import { ServerResponse, OutgoingHttpHeaders } from "http"; +import { Writable } from "stream"; +import * as net from 'net'; + +class ServerHubResponse implements ServerResponse { + public __length__ = 0; + get __res__ () { + return this.res; + } + private res: ServerResponse; + get statusCode () { + return this.res.statusCode; + }; + get statusMessage () { + return this.res.statusMessage; + } + get upgrading () { + return this.res.upgrading; + }; + get chunkedEncoding () { + return this.res.chunkedEncoding; + }; + get shouldKeepAlive () { + return this.res.shouldKeepAlive; + }; + get useChunkedEncodingByDefault () { + return this.res.useChunkedEncodingByDefault; + }; + get sendDate () { + return this.res.sendDate; + }; + get finished () { + return this.res.finished; + }; + get headersSent () { + return this.res.headersSent; + }; + get connection () { + return this.res.connection; + }; + get writable () { + return this.res.writable; + }; + set statusCode (number: number) { + this.res.statusCode = number; + } + set statusMessage (m: string) { + this.res.statusMessage = m; + } + set upgrading (v: boolean) { + this.res.upgrading = v; + }; + set chunkedEncoding (v: boolean) { + this.res.chunkedEncoding = v; + }; + set shouldKeepAlive (v: boolean) { + this.res.shouldKeepAlive = v; + }; + set useChunkedEncodingByDefault (v: boolean) { + this.res.useChunkedEncodingByDefault = v; + }; + set sendDate (v: boolean) { + this.res.sendDate = v; + }; + set connection (s: net.Socket) { + this.res.connection = s; + + }; + set writable (v: boolean) { + this.res.writable = v; + }; + get writableHighWaterMark () { + return this.res.writableHighWaterMark; + }; + + constructor(res: ServerResponse) { + this.res = res; + } + public write (chunk: any, cb?: Function): boolean; + public write (chunk: any, encoding?: string, cb?: Function): boolean; + public write (chunk: any, encoding?: string | Function, cb?: Function): boolean { + if (typeof chunk === 'string') + this.__length__ += chunk.length; + else if (chunk instanceof Buffer) + this.__length__ += chunk.byteLength; + else this.__length__ += chunk.toString().length; + if (encoding && typeof encoding === 'string') + return this.res.write(chunk.toString(), encoding, cb); + else return this.res.write(chunk, cb); + } + + public _write (chunk: any, encoding: string, callback: (err?: Error) => void): void { this.res._write(chunk, encoding, callback) }; + public _writev?(chunks: Array<{ chunk: any, encoding: string }>, callback: (err?: Error) => void): void { this.res._writev(chunks, callback) }; + public _destroy (err: Error, callback: Function): void { this.res._destroy(err, callback) }; + public _final (callback: Function): void { this.res._final(callback) }; + public pipe(destination: T, options?: { end?: boolean; }): T { return this.res.pipe(destination, options) }; + public setDefaultEncoding (encoding: string): this { this.res.setDefaultEncoding(encoding); return this }; + public end (cb?: Function): void; + public end (chunk: any, cb?: Function): void; + public end (chunk: any, encoding?: string, cb?: Function): void; + public end (chunk: any, encoding?: string | Function, cb?: Function): void { + if (typeof chunk === 'string') + this.__length__ += chunk.length; + else if (chunk instanceof Buffer) + this.__length__ += chunk.byteLength; + else if (chunk && !(chunk instanceof Function)) { this.__length__ += chunk.toString().length; } + else return this.res.end(chunk); // execute as callback function. + + if (encoding && typeof encoding === 'string') + return this.res.end(chunk.toString(), encoding, cb); + else return this.res.end(chunk, cb); + }; + public cork (): void { this.res.cork() }; + public uncork (): void { this.res.uncork() }; + public assignSocket (socket: net.Socket): void { this.res.assignSocket(socket) }; + public detachSocket (socket: net.Socket): void { this.res.detachSocket(socket) }; + public writeContinue (callback?: () => void): void { this.res.writeContinue(callback) }; + public writeHead (statusCode: number, headers?: OutgoingHttpHeaders): void; + public writeHead (statusCode: number, reasonPhrase?: string, headers?: OutgoingHttpHeaders): void; + public writeHead (statusCode: number, reasonPhrase?: string | OutgoingHttpHeaders, headers?: OutgoingHttpHeaders): void { + if (reasonPhrase && typeof reasonPhrase === 'string') { + this.res.writeHead(statusCode, reasonPhrase, headers); + } else this.res.writeHead(statusCode, reasonPhrase as OutgoingHttpHeaders); + }; + public setTimeout (msecs: number, callback?: () => void): this { this.res.setTimeout(msecs, callback); return this; }; + public destroy (error: Error): void { return this.res.destroy(error); }; + public setHeader (name: string, value: number | string | string[]): void { return this.res.setHeader(name, value); }; + public getHeader (name: string): number | string | string[] | undefined { return this.res.getHeader(name) }; + public getHeaders (): OutgoingHttpHeaders { return this.res.getHeaders() }; + public getHeaderNames (): string[] { return this.res.getHeaderNames(); }; + public hasHeader (name: string): boolean { return this.res.hasHeader(name); }; + public removeHeader (name: string): void { return this.res.removeHeader(name); }; + public addTrailers (headers: OutgoingHttpHeaders | Array<[string, string]>): void { return this.res.addTrailers(headers); }; + public flushHeaders (): void { return this.res.flushHeaders() }; + public addListener (event: string, listener: (...args: any[]) => void): this { this.res.addListener(event, listener); return this; }; + public emit (event: string | symbol, ...args: any[]): boolean { return this.res.emit(event, ...args); }; + public on (event: string, listener: (...args: any[]) => void): this { this.res.on(event, listener); return this; }; + public once (event: string, listener: (...args: any[]) => void): this { this.res.once(event, listener); return this; }; + public prependListener (event: string, listener: (...args: any[]) => void): this { this.res.prependListener(event, listener); return this; }; + public prependOnceListener (event: string, listener: (...args: any[]) => void): this { this.res.prependOnceListener; return this; }; + public removeListener (event: string, listener: (...args: any[]) => void): this { this.res.removeListener(event, listener); return this; }; + public removeAllListeners (event?: string | symbol): this { this.res.removeAllListeners(event); return this; }; + public setMaxListeners (n: number): this { this.res.setMaxListeners(n); return this; }; + public getMaxListeners (): number { return this.res.getMaxListeners() }; + public listeners (event: string | symbol): Function[] { return this.res.listeners(event); }; + public listenerCount (type: string | symbol): number { return this.res.listenerCount(type) }; + public eventNames (): Array { return this.res.eventNames() }; +} + +export { ServerHubResponse }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index afa101b..360b615 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverhub-mvc", - "version": "1.1.3", + "version": "1.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -402,9 +402,9 @@ "dev": true }, "buffer-from": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", - "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", "dev": true }, "cache-base": { @@ -2272,9 +2272,9 @@ } }, "iconv-lite": { - "version": "0.4.22", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.22.tgz", - "integrity": "sha512-1AinFBeDTnsvVEP+V1QBlHpM1UZZl7gWB6fcz7B1Ho+LI1dUh2sSrxoCfVt2PinRHzXAziSniEV3P7JbTDHcXA==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -2886,9 +2886,9 @@ "dev": true }, "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "requires": { "pseudomap": "^1.0.2", diff --git a/package.json b/package.json index 9830355..5f1093e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverhub-mvc", - "version": "1.1.3", + "version": "1.2.0", "description": "Fast, secure and stable Node.js MVC framework", "main": "index.js", "scripts": {