badmfck-api-server 1.5.2 → 1.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,6 +25,10 @@ export interface APIServiceOptions {
25
25
  onError?: ((...rest: any[]) => void) | null;
26
26
  isProductionEnvironment: boolean;
27
27
  interceptor?: IBaseEndpoint;
28
+ monitor?: {
29
+ login: string;
30
+ password: string;
31
+ }[];
28
32
  }
29
33
  export declare function getDefaultOptions(): APIServiceOptions;
30
34
  export declare const REQ_CREATE_NET_LOG: Req<void, APIServiceNetworkLogItem>;
@@ -34,6 +38,8 @@ export declare class APIService extends BaseService {
34
38
  private static nextLogID;
35
39
  private version;
36
40
  private options;
41
+ private monitor?;
42
+ private monitorIndexFile?;
37
43
  netLog: APIServiceNetworkLogItem[];
38
44
  constructor(options?: APIServiceOptions | null);
39
45
  init(): Promise<void>;
@@ -11,6 +11,10 @@ const BaseEndpoint_1 = require("./BaseEndpoint");
11
11
  const DefaultErrors_1 = __importDefault(require("./structures/DefaultErrors"));
12
12
  const badmfck_signal_1 = require("badmfck-signal");
13
13
  const LogService_1 = require("./LogService");
14
+ const Monitor_1 = require("./monitor/Monitor");
15
+ const path_1 = __importDefault(require("path"));
16
+ const crypto_1 = __importDefault(require("crypto"));
17
+ const fs_1 = __importDefault(require("fs"));
14
18
  function getDefaultOptions() {
15
19
  return {
16
20
  port: 8091,
@@ -43,8 +47,10 @@ async function Initializer(services) {
43
47
  exports.Initializer = Initializer;
44
48
  class APIService extends BaseService_1.BaseService {
45
49
  static nextLogID = 0;
46
- version = "1.5.2";
50
+ version = "1.5.4";
47
51
  options;
52
+ monitor;
53
+ monitorIndexFile;
48
54
  netLog = [];
49
55
  constructor(options) {
50
56
  super('HTTP Service');
@@ -54,6 +60,16 @@ class APIService extends BaseService_1.BaseService {
54
60
  const self = "http://localhost:" + this.options.port;
55
61
  if (!this.options.corsHostWhiteList.find(val => val === self))
56
62
  this.options.corsHostWhiteList.push();
63
+ if (this.options.monitor && this.options.monitor.length > 0) {
64
+ this.monitor = new Monitor_1.Monitor();
65
+ this.options.endpoints.push(this.monitor);
66
+ console.log("Service Monitor initialized");
67
+ console.log("monitor links:");
68
+ for (let i of this.options.monitor) {
69
+ const hash = crypto_1.default.createHash("sha256").update(i.login + i.password).digest().toString("hex");
70
+ console.log("/sm-" + hash);
71
+ }
72
+ }
57
73
  }
58
74
  async init() {
59
75
  exports.REQ_HTTP_LOG.listener = async (ignore) => this.netLog;
@@ -148,12 +164,22 @@ class APIService extends BaseService_1.BaseService {
148
164
  httpRequest.interceptorResult = interceptorResult;
149
165
  }
150
166
  }
167
+ if (i === this.monitor)
168
+ httpRequest.precheck = { data: this.options.monitor };
169
+ const precheck = await i.precheck(httpRequest);
170
+ if (precheck && precheck.error) {
171
+ this.sendResponse(res, precheck, tme, ep, log);
172
+ return;
173
+ }
174
+ httpRequest.precheck = precheck;
151
175
  result = await i.execute(httpRequest);
152
176
  }
153
177
  catch (e) {
154
178
  console.error(e);
155
179
  if (this.options.onError)
156
180
  this.options.onError(e);
181
+ if (this.monitor)
182
+ this.monitor.registrateFatalError(ep);
157
183
  this.sendResponse(res, {
158
184
  httpStatus: 500,
159
185
  error: {
@@ -172,6 +198,40 @@ class APIService extends BaseService_1.BaseService {
172
198
  }
173
199
  app.use((req, res, next) => {
174
200
  const tme = +new Date();
201
+ if (this.monitor && this.options && this.options.monitor) {
202
+ if (req.method === "GET") {
203
+ let monitorRequest = false;
204
+ for (let i of this.options.monitor) {
205
+ const hash = crypto_1.default.createHash("sha256").update(i.login + i.password).digest().toString("hex");
206
+ if (req.originalUrl.endsWith("/sm-" + hash)) {
207
+ monitorRequest = true;
208
+ break;
209
+ }
210
+ }
211
+ if (monitorRequest) {
212
+ const date = new Date();
213
+ try {
214
+ if (!this.monitorIndexFile)
215
+ this.monitorIndexFile = fs_1.default.readFileSync(path_1.default.resolve(__dirname, "monitor", "index.html"));
216
+ res.setHeader("Content-Type", "text/html");
217
+ res.setHeader("Content-Length", this.monitorIndexFile.byteLength);
218
+ res.status(200).send(this.monitorIndexFile);
219
+ }
220
+ catch (e) {
221
+ this.sendResponse(res, {
222
+ error: {
223
+ code: 10002,
224
+ message: "Internal server error",
225
+ details: `${e}`
226
+ },
227
+ data: null,
228
+ httpStatus: 500
229
+ }, tme, req.path);
230
+ }
231
+ return;
232
+ }
233
+ }
234
+ }
175
235
  this.sendResponse(res, {
176
236
  error: DefaultErrors_1.default.UNKNOWN_REQUEST,
177
237
  data: null,
@@ -186,6 +246,8 @@ class APIService extends BaseService_1.BaseService {
186
246
  data.responseTime = (+new Date()) - requestTime;
187
247
  data.version = this.version;
188
248
  data.endpoint = endpoint ?? "no_endpoint";
249
+ if (this.monitor)
250
+ this.monitor.registrateResponse(data.endpoint, data.responseTime);
189
251
  if (log)
190
252
  log.time = data.responseTime;
191
253
  if (res.destroyed || res.closed) {
@@ -193,6 +255,8 @@ class APIService extends BaseService_1.BaseService {
193
255
  log.error = "Connection already closed, can't send response";
194
256
  if (this.options.onError)
195
257
  this.options.onError("Connection already closed, can't send response", data);
258
+ if (this.monitor)
259
+ this.monitor.registrateError(data.endpoint);
196
260
  }
197
261
  else {
198
262
  try {
@@ -219,6 +283,8 @@ class APIService extends BaseService_1.BaseService {
219
283
  res.send(data);
220
284
  if (log)
221
285
  log.response = data;
286
+ if (data.error && this.monitor)
287
+ this.monitor.registrateAPIError(data.endpoint);
222
288
  }
223
289
  }
224
290
  }
@@ -227,6 +293,8 @@ class APIService extends BaseService_1.BaseService {
227
293
  this.options.onError("Can't send response", e);
228
294
  if (log)
229
295
  log.error = "Can't send response";
296
+ if (this.monitor)
297
+ this.monitor.registrateError(data.endpoint);
230
298
  }
231
299
  }
232
300
  if (this.options.onNetworkLog && log)
@@ -2,6 +2,7 @@ import { HTTPRequestVO, TransferPacketVO } from "./structures/Interfaces";
2
2
  export interface IBaseEndpoint {
3
3
  endpoints?: IEndpointHandler[];
4
4
  execute: (req: HTTPRequestVO) => Promise<TransferPacketVO<any>>;
5
+ precheck: (req: HTTPRequestVO) => Promise<TransferPacketVO<any> | null>;
5
6
  init: () => Promise<void>;
6
7
  ignoreHttpLogging: boolean;
7
8
  }
@@ -22,5 +23,6 @@ export declare class BaseEndpoint implements IBaseEndpoint {
22
23
  constructor(endpoint: string);
23
24
  registerEndpoints(endpoints: IEndpointHandler[]): void;
24
25
  init(): Promise<void>;
26
+ precheck(req: HTTPRequestVO): Promise<TransferPacketVO<any> | null>;
25
27
  execute(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
26
28
  }
@@ -56,6 +56,7 @@ class BaseEndpoint {
56
56
  (0, LogService_1.logInfo)("${BaseEndpoint.js}", "endpoint: " + i.endpoint + " initalized, ignoreInterceptor: " + (i.ignoreInterceptor ?? "false"));
57
57
  }
58
58
  ;
59
+ async precheck(req) { return null; }
59
60
  async execute(req) {
60
61
  if (this.endpoints && this.endpoints.length > 0) {
61
62
  for (let i of this.endpoints) {
@@ -0,0 +1,33 @@
1
+ import { BaseEndpoint } from "../BaseEndpoint";
2
+ import { HTTPRequestVO, TransferPacketVO } from "../structures/Interfaces";
3
+ export declare const indexFilePath: string;
4
+ interface IStatObject {
5
+ requests: Map<string, number>;
6
+ errors: Map<string, number>;
7
+ fatalErrors: Map<string, number>;
8
+ apiErrors: Map<string, number>;
9
+ }
10
+ export declare class Monitor extends BaseEndpoint {
11
+ ignoreHttpLogging: boolean;
12
+ private startedAt;
13
+ private httpRequests;
14
+ constructor();
15
+ registrateError(endpoint: string): void;
16
+ registrateFatalError(endpoint: string): void;
17
+ registrateAPIError(endpoint: string): void;
18
+ registrateResponse(endpoint: string, responseTime: number): void;
19
+ increaseStat(statObject: Map<string, number>, endpoint: string): void;
20
+ createStatObj(): IStatObject;
21
+ getDateIndex(d: Date): number;
22
+ getHourMinuteIndex(d: Date): string;
23
+ leadZero(i: number): string;
24
+ precheck(req: HTTPRequestVO): Promise<TransferPacketVO<any> | null>;
25
+ logs(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
26
+ netlog(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
27
+ metrics(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
28
+ mapToKeyValue(map: Map<string, number>): {
29
+ name: string;
30
+ value: number;
31
+ }[];
32
+ }
33
+ export {};
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Monitor = exports.indexFilePath = void 0;
7
+ const APIService_1 = require("../APIService");
8
+ const BaseEndpoint_1 = require("../BaseEndpoint");
9
+ const LogService_1 = require("../LogService");
10
+ const crypto_1 = __importDefault(require("crypto"));
11
+ const path_1 = __importDefault(require("path"));
12
+ exports.indexFilePath = path_1.default.resolve(__dirname, "index.html");
13
+ class Monitor extends BaseEndpoint_1.BaseEndpoint {
14
+ ignoreHttpLogging = true;
15
+ startedAt = new Date();
16
+ httpRequests = new Map();
17
+ constructor() {
18
+ super("sys-monitor");
19
+ this.registerEndpoints([
20
+ { ignoreInterceptor: true, endpoint: "log", handler: this.logs },
21
+ { ignoreInterceptor: true, endpoint: "netlog", handler: this.netlog },
22
+ { ignoreInterceptor: true, endpoint: "metrics", handler: this.metrics }
23
+ ]);
24
+ }
25
+ registrateError(endpoint) {
26
+ const so = this.createStatObj();
27
+ this.increaseStat(so.errors, endpoint);
28
+ }
29
+ registrateFatalError(endpoint) {
30
+ const so = this.createStatObj();
31
+ this.increaseStat(so.fatalErrors, endpoint);
32
+ }
33
+ registrateAPIError(endpoint) {
34
+ const so = this.createStatObj();
35
+ this.increaseStat(so.apiErrors, endpoint);
36
+ }
37
+ registrateResponse(endpoint, responseTime) {
38
+ const so = this.createStatObj();
39
+ this.increaseStat(so.requests, endpoint);
40
+ }
41
+ increaseStat(statObject, endpoint) {
42
+ let reqep = statObject.get(endpoint);
43
+ if (!reqep)
44
+ reqep = 0;
45
+ reqep += 1;
46
+ statObject.set(endpoint, reqep);
47
+ }
48
+ createStatObj() {
49
+ const d = new Date();
50
+ const dtm = this.getDateIndex(d);
51
+ let day = this.httpRequests.get(dtm);
52
+ if (!day) {
53
+ day = new Map();
54
+ this.httpRequests.set(dtm, day);
55
+ if (this.httpRequests.size > 14) {
56
+ for (let i of this.httpRequests) {
57
+ this.httpRequests.delete(i[0]);
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ const hourMinute = this.getHourMinuteIndex(d);
63
+ let hm = day.get(hourMinute);
64
+ if (!hm) {
65
+ hm = {
66
+ errors: new Map(),
67
+ requests: new Map(),
68
+ fatalErrors: new Map(),
69
+ apiErrors: new Map()
70
+ };
71
+ }
72
+ return hm;
73
+ }
74
+ getDateIndex(d) {
75
+ const dtm = d;
76
+ return parseInt(dtm.getFullYear().toString().substring(2) + this.leadZero(dtm.getMonth() + 1) + this.leadZero(dtm.getDate()));
77
+ }
78
+ getHourMinuteIndex(d) {
79
+ const dtm = d;
80
+ return this.leadZero(dtm.getHours()) + this.leadZero(dtm.getMinutes());
81
+ }
82
+ leadZero(i) {
83
+ if (i > 9)
84
+ return i + "";
85
+ return "0" + i;
86
+ }
87
+ async precheck(req) {
88
+ if (!req.headers)
89
+ return { error: { code: 10001, message: "No authorization", httpStatus: 400 } };
90
+ if (!req.headers['authorization'])
91
+ return { error: { code: 10002, message: "No authorization found", httpStatus: 400 } };
92
+ const auth = req.headers['authorization'];
93
+ if (!Array.isArray(req.precheck)) {
94
+ return { error: { code: 10003, message: "No authorization records found", httpStatus: 400 } };
95
+ }
96
+ let authorized = false;
97
+ for (let i of req.precheck) {
98
+ let expectationStr = "";
99
+ expectationStr += JSON.stringify(i);
100
+ expectationStr += JSON.stringify(req.method);
101
+ expectationStr += JSON.stringify(req.data);
102
+ expectationStr += JSON.stringify(req.params);
103
+ const expectation = "sha256 " + crypto_1.default.createHash("sha256").update(expectationStr).digest().toString("hex");
104
+ if (auth === expectation) {
105
+ authorized = true;
106
+ break;
107
+ }
108
+ }
109
+ if (!authorized) {
110
+ console.error("Wrong token: " + auth);
111
+ return { error: { code: 10004, message: "Unauthorized access", httpStatus: 401 } };
112
+ }
113
+ return null;
114
+ }
115
+ async logs(req) {
116
+ const log = await LogService_1.REQ_LOG.request(null);
117
+ return { data: log };
118
+ }
119
+ async netlog(req) {
120
+ const log = await APIService_1.REQ_HTTP_LOG.request();
121
+ return { data: log };
122
+ }
123
+ async metrics(req) {
124
+ const date = new Date();
125
+ const di = this.getDateIndex(date);
126
+ const stat = this.httpRequests.get(di);
127
+ if (!stat)
128
+ return { data: [] };
129
+ let result = [];
130
+ for (let minutes of stat) {
131
+ result.push({
132
+ m: minutes[0],
133
+ ae: this.mapToKeyValue(minutes[1].apiErrors),
134
+ e: this.mapToKeyValue(minutes[1].errors),
135
+ r: this.mapToKeyValue(minutes[1].requests),
136
+ fe: this.mapToKeyValue(minutes[1].fatalErrors),
137
+ });
138
+ }
139
+ return { data: { stat: result, date: +date } };
140
+ }
141
+ mapToKeyValue(map) {
142
+ const res = [];
143
+ for (let i of map)
144
+ res.push({ name: i[0], value: i[1] });
145
+ return res;
146
+ }
147
+ }
148
+ exports.Monitor = Monitor;
@@ -21,6 +21,7 @@ export interface HTTPRequestVO {
21
21
  headers: any;
22
22
  endpoint: string;
23
23
  interceptorResult?: TransferPacketVO<any>;
24
+ precheck?: TransferPacketVO<any> | null;
24
25
  }
25
26
  export interface IError {
26
27
  code: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badmfck-api-server",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Simple API http server based on express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",