badmfck-api-server 1.5.2 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apiServer/APIService.d.ts +6 -0
- package/dist/apiServer/APIService.js +69 -1
- package/dist/apiServer/BaseEndpoint.d.ts +2 -0
- package/dist/apiServer/BaseEndpoint.js +1 -0
- package/dist/apiServer/monitor/Monitor.d.ts +33 -0
- package/dist/apiServer/monitor/Monitor.js +148 -0
- package/dist/apiServer/structures/Interfaces.d.ts +1 -0
- package/package.json +1 -1
@@ -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.
|
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;
|