badmfck-api-server 1.5.2 → 1.5.3

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,7 @@ export declare class APIService extends BaseService {
34
38
  private static nextLogID;
35
39
  private version;
36
40
  private options;
41
+ private monitor?;
37
42
  netLog: APIServiceNetworkLogItem[];
38
43
  constructor(options?: APIServiceOptions | null);
39
44
  init(): Promise<void>;
@@ -11,6 +11,7 @@ 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");
14
15
  function getDefaultOptions() {
15
16
  return {
16
17
  port: 8091,
@@ -43,8 +44,9 @@ async function Initializer(services) {
43
44
  exports.Initializer = Initializer;
44
45
  class APIService extends BaseService_1.BaseService {
45
46
  static nextLogID = 0;
46
- version = "1.5.2";
47
+ version = "1.5.3";
47
48
  options;
49
+ monitor;
48
50
  netLog = [];
49
51
  constructor(options) {
50
52
  super('HTTP Service');
@@ -54,6 +56,11 @@ class APIService extends BaseService_1.BaseService {
54
56
  const self = "http://localhost:" + this.options.port;
55
57
  if (!this.options.corsHostWhiteList.find(val => val === self))
56
58
  this.options.corsHostWhiteList.push();
59
+ if (this.options.monitor && this.options.monitor.length > 0) {
60
+ this.monitor = new Monitor_1.Monitor();
61
+ this.options.endpoints.push(this.monitor);
62
+ console.log("Service Monitor initialized");
63
+ }
57
64
  }
58
65
  async init() {
59
66
  exports.REQ_HTTP_LOG.listener = async (ignore) => this.netLog;
@@ -148,12 +155,22 @@ class APIService extends BaseService_1.BaseService {
148
155
  httpRequest.interceptorResult = interceptorResult;
149
156
  }
150
157
  }
158
+ if (i === this.monitor)
159
+ httpRequest.precheck = { data: this.options.monitor };
160
+ const precheck = await i.precheck(httpRequest);
161
+ if (precheck && precheck.error) {
162
+ this.sendResponse(res, precheck, tme, ep, log);
163
+ return;
164
+ }
165
+ httpRequest.precheck = precheck;
151
166
  result = await i.execute(httpRequest);
152
167
  }
153
168
  catch (e) {
154
169
  console.error(e);
155
170
  if (this.options.onError)
156
171
  this.options.onError(e);
172
+ if (this.monitor)
173
+ this.monitor.registrateFatalError(ep);
157
174
  this.sendResponse(res, {
158
175
  httpStatus: 500,
159
176
  error: {
@@ -186,6 +203,8 @@ class APIService extends BaseService_1.BaseService {
186
203
  data.responseTime = (+new Date()) - requestTime;
187
204
  data.version = this.version;
188
205
  data.endpoint = endpoint ?? "no_endpoint";
206
+ if (this.monitor)
207
+ this.monitor.registrateResponse(data.endpoint, data.responseTime);
189
208
  if (log)
190
209
  log.time = data.responseTime;
191
210
  if (res.destroyed || res.closed) {
@@ -193,6 +212,8 @@ class APIService extends BaseService_1.BaseService {
193
212
  log.error = "Connection already closed, can't send response";
194
213
  if (this.options.onError)
195
214
  this.options.onError("Connection already closed, can't send response", data);
215
+ if (this.monitor)
216
+ this.monitor.registrateError(data.endpoint);
196
217
  }
197
218
  else {
198
219
  try {
@@ -219,6 +240,8 @@ class APIService extends BaseService_1.BaseService {
219
240
  res.send(data);
220
241
  if (log)
221
242
  log.response = data;
243
+ if (data.error && this.monitor)
244
+ this.monitor.registrateAPIError(data.endpoint);
222
245
  }
223
246
  }
224
247
  }
@@ -227,6 +250,8 @@ class APIService extends BaseService_1.BaseService {
227
250
  this.options.onError("Can't send response", e);
228
251
  if (log)
229
252
  log.error = "Can't send response";
253
+ if (this.monitor)
254
+ this.monitor.registrateError(data.endpoint);
230
255
  }
231
256
  }
232
257
  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,32 @@
1
+ import { BaseEndpoint } from "../BaseEndpoint";
2
+ import { HTTPRequestVO, TransferPacketVO } from "../structures/Interfaces";
3
+ interface IStatObject {
4
+ requests: Map<string, number>;
5
+ errors: Map<string, number>;
6
+ fatalErrors: Map<string, number>;
7
+ apiErrors: Map<string, number>;
8
+ }
9
+ export declare class Monitor extends BaseEndpoint {
10
+ ignoreHttpLogging: boolean;
11
+ private startedAt;
12
+ private httpRequests;
13
+ constructor();
14
+ registrateError(endpoint: string): void;
15
+ registrateFatalError(endpoint: string): void;
16
+ registrateAPIError(endpoint: string): void;
17
+ registrateResponse(endpoint: string, responseTime: number): void;
18
+ increaseStat(statObject: Map<string, number>, endpoint: string): void;
19
+ createStatObj(): IStatObject;
20
+ getDateIndex(d: Date): number;
21
+ getHourMinuteIndex(d: Date): string;
22
+ leadZero(i: number): string;
23
+ precheck(req: HTTPRequestVO): Promise<TransferPacketVO<any> | null>;
24
+ logs(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
25
+ netlog(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
26
+ metrics(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
27
+ mapToKeyValue(map: Map<string, number>): {
28
+ name: string;
29
+ value: number;
30
+ }[];
31
+ }
32
+ export {};
@@ -0,0 +1,146 @@
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 = 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
+ class Monitor extends BaseEndpoint_1.BaseEndpoint {
12
+ ignoreHttpLogging = true;
13
+ startedAt = new Date();
14
+ httpRequests = new Map();
15
+ constructor() {
16
+ super("sys-monitor");
17
+ this.registerEndpoints([
18
+ { ignoreInterceptor: true, endpoint: "log", handler: this.logs },
19
+ { ignoreInterceptor: true, endpoint: "netlog", handler: this.netlog },
20
+ { ignoreInterceptor: true, endpoint: "metrics", handler: this.metrics }
21
+ ]);
22
+ }
23
+ registrateError(endpoint) {
24
+ const so = this.createStatObj();
25
+ this.increaseStat(so.errors, endpoint);
26
+ }
27
+ registrateFatalError(endpoint) {
28
+ const so = this.createStatObj();
29
+ this.increaseStat(so.fatalErrors, endpoint);
30
+ }
31
+ registrateAPIError(endpoint) {
32
+ const so = this.createStatObj();
33
+ this.increaseStat(so.apiErrors, endpoint);
34
+ }
35
+ registrateResponse(endpoint, responseTime) {
36
+ const so = this.createStatObj();
37
+ this.increaseStat(so.requests, endpoint);
38
+ }
39
+ increaseStat(statObject, endpoint) {
40
+ let reqep = statObject.get(endpoint);
41
+ if (!reqep)
42
+ reqep = 0;
43
+ reqep += 1;
44
+ statObject.set(endpoint, reqep);
45
+ }
46
+ createStatObj() {
47
+ const d = new Date();
48
+ const dtm = this.getDateIndex(d);
49
+ let day = this.httpRequests.get(dtm);
50
+ if (!day) {
51
+ day = new Map();
52
+ this.httpRequests.set(dtm, day);
53
+ if (this.httpRequests.size > 14) {
54
+ for (let i of this.httpRequests) {
55
+ this.httpRequests.delete(i[0]);
56
+ break;
57
+ }
58
+ }
59
+ }
60
+ const hourMinute = this.getHourMinuteIndex(d);
61
+ let hm = day.get(hourMinute);
62
+ if (!hm) {
63
+ hm = {
64
+ errors: new Map(),
65
+ requests: new Map(),
66
+ fatalErrors: new Map(),
67
+ apiErrors: new Map()
68
+ };
69
+ }
70
+ return hm;
71
+ }
72
+ getDateIndex(d) {
73
+ const dtm = d;
74
+ return parseInt(dtm.getFullYear().toString().substring(2) + this.leadZero(dtm.getMonth() + 1) + this.leadZero(dtm.getDate()));
75
+ }
76
+ getHourMinuteIndex(d) {
77
+ const dtm = d;
78
+ return this.leadZero(dtm.getHours()) + this.leadZero(dtm.getMinutes());
79
+ }
80
+ leadZero(i) {
81
+ if (i > 9)
82
+ return i + "";
83
+ return "0" + i;
84
+ }
85
+ async precheck(req) {
86
+ if (!req.headers)
87
+ return { error: { code: 10001, message: "No authorization", httpStatus: 400 } };
88
+ if (!req.headers['authorization'])
89
+ return { error: { code: 10002, message: "No authorization found", httpStatus: 400 } };
90
+ const auth = req.headers['authorization'];
91
+ if (!Array.isArray(req.precheck)) {
92
+ return { error: { code: 10003, message: "No authorization records found", httpStatus: 400 } };
93
+ }
94
+ let authorized = false;
95
+ for (let i of req.precheck) {
96
+ let expectationStr = "";
97
+ expectationStr += JSON.stringify(i);
98
+ expectationStr += JSON.stringify(req.method);
99
+ expectationStr += JSON.stringify(req.data);
100
+ expectationStr += JSON.stringify(req.params);
101
+ const expectation = "sha256 " + crypto_1.default.createHash("sha256").update(expectationStr).digest().toString("hex");
102
+ if (auth === expectation) {
103
+ authorized = true;
104
+ break;
105
+ }
106
+ }
107
+ if (!authorized) {
108
+ console.error("Wrong token: " + auth);
109
+ return { error: { code: 10004, message: "Unauthorized access", httpStatus: 401 } };
110
+ }
111
+ return null;
112
+ }
113
+ async logs(req) {
114
+ const log = await LogService_1.REQ_LOG.request(null);
115
+ return { data: log };
116
+ }
117
+ async netlog(req) {
118
+ const log = await APIService_1.REQ_HTTP_LOG.request();
119
+ return { data: log };
120
+ }
121
+ async metrics(req) {
122
+ const date = new Date();
123
+ const di = this.getDateIndex(date);
124
+ const stat = this.httpRequests.get(di);
125
+ if (!stat)
126
+ return { data: [] };
127
+ let result = [];
128
+ for (let minutes of stat) {
129
+ result.push({
130
+ m: minutes[0],
131
+ ae: this.mapToKeyValue(minutes[1].apiErrors),
132
+ e: this.mapToKeyValue(minutes[1].errors),
133
+ r: this.mapToKeyValue(minutes[1].requests),
134
+ fe: this.mapToKeyValue(minutes[1].fatalErrors),
135
+ });
136
+ }
137
+ return { data: { stat: result, date: +date } };
138
+ }
139
+ mapToKeyValue(map) {
140
+ const res = [];
141
+ for (let i of map)
142
+ res.push({ name: i[0], value: i[1] });
143
+ return res;
144
+ }
145
+ }
146
+ 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.3",
4
4
  "description": "Simple API http server based on express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",