@flancer32/teq-web 0.3.1 → 0.5.0
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/CHANGELOG.md +26 -1
- package/README.md +236 -127
- package/ai/AGENTS.md +36 -0
- package/ai/abstractions.md +75 -0
- package/ai/examples/minimal-server.md +78 -0
- package/ai/overview.md +27 -0
- package/ai/rules.md +41 -0
- package/package.json +43 -28
- package/src/Back/Api/Handler.mjs +26 -0
- package/src/Back/{Defaults.js → Defaults.mjs} +6 -1
- package/src/Back/Dto/Info.mjs +66 -0
- package/src/Back/Dto/{Handler/Source.js → Source.mjs} +26 -23
- package/src/Back/Enum/Server/Type.mjs +13 -0
- package/src/Back/Enum/Stage.mjs +13 -0
- package/src/Back/Handler/Pre/Log.mjs +59 -0
- package/src/Back/Handler/Static/A/{Config.js → Config.mjs} +14 -3
- package/src/Back/Handler/Static/A/{Fallback.js → Fallback.mjs} +16 -4
- package/src/Back/Handler/Static/A/{FileService.js → FileService.mjs} +31 -14
- package/src/Back/Handler/Static/A/{Registry.js → Registry.mjs} +18 -6
- package/src/Back/Handler/Static/A/{Resolver.js → Resolver.mjs} +13 -2
- package/src/Back/Handler/Static.mjs +83 -0
- package/src/Back/Helper/Cast.mjs +116 -0
- package/src/Back/Helper/{Mime.js → Mime.mjs} +2 -0
- package/src/Back/Helper/Order/Kahn.mjs +69 -0
- package/src/Back/Helper/{Respond.js → Respond.mjs} +14 -3
- package/src/Back/Logger.mjs +57 -0
- package/src/Back/PipelineEngine.mjs +228 -0
- package/src/Back/Server/Config/{Tls.js → Tls.mjs} +35 -33
- package/src/Back/Server/Config.mjs +69 -0
- package/src/Back/{Server.js → Server.mjs} +35 -24
- package/types.d.ts +30 -0
- package/.github/workflows/npm-publish.yml +0 -48
- package/AGENTS.md +0 -101
- package/eslint.config.js +0 -37
- package/src/AGENTS.md +0 -108
- package/src/Back/Api/Handler.js +0 -26
- package/src/Back/Dispatcher.js +0 -115
- package/src/Back/Dto/Handler/Info.js +0 -68
- package/src/Back/Enum/Server/Type.js +0 -12
- package/src/Back/Enum/Stage.js +0 -10
- package/src/Back/Handler/Pre/Log.js +0 -45
- package/src/Back/Handler/Static.js +0 -63
- package/src/Back/Helper/Cast.js +0 -114
- package/src/Back/Helper/Order/Kahn.js +0 -66
- package/src/Back/Logger.js +0 -53
- package/src/Back/Server/Config.js +0 -69
- package/teqfw.json +0 -8
- package/test/accept/ExternalServer.test.mjs +0 -66
- package/test/accept/Server.test.mjs +0 -203
- package/test/certs/ca.pem +0 -19
- package/test/certs/cert.pem +0 -19
- package/test/certs/key.pem +0 -28
- package/test/dev/app/Plugin/Start.js +0 -40
- package/test/dev/app.mjs +0 -65
- package/test/dev/web/favicon.ico +0 -0
- package/test/dev/web/index.html +0 -11
- package/test/unit/AGENTS.md +0 -106
- package/test/unit/Back/Dispatcher.test.mjs +0 -150
- package/test/unit/Back/Dto/Handler/Source.test.mjs +0 -40
- package/test/unit/Back/Handler/Pre/Log.test.mjs +0 -22
- package/test/unit/Back/Handler/Static/A/Config.test.mjs +0 -52
- package/test/unit/Back/Handler/Static/A/Fallback.test.mjs +0 -60
- package/test/unit/Back/Handler/Static/A/FileService.test.mjs +0 -225
- package/test/unit/Back/Handler/Static/A/Registry.test.mjs +0 -83
- package/test/unit/Back/Handler/Static/A/Resolver.test.mjs +0 -73
- package/test/unit/Back/Handler/Static/Static.test.mjs +0 -235
- package/test/unit/Back/Helper/Order/Kahn.test.mjs +0 -59
- package/test/unit/Back/Helper/Respond.test.mjs +0 -83
- package/test/unit/Back/Server.test.mjs +0 -112
- package/test/unit/common.js +0 -22
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Logs basic request information at the beginning of the request lifecycle.
|
|
3
|
-
* @implements Fl32_Web_Back_Api_Handler
|
|
4
|
-
*/
|
|
5
|
-
export default class Fl32_Web_Back_Handler_Pre_Log {
|
|
6
|
-
/* eslint-disable jsdoc/require-param-description,jsdoc/check-param-names */
|
|
7
|
-
/**
|
|
8
|
-
* @param {Fl32_Web_Back_Logger} logger
|
|
9
|
-
* @param {Fl32_Web_Back_Dto_Handler_Info} dtoInfo
|
|
10
|
-
* @param {typeof Fl32_Web_Back_Enum_Stage} STAGE
|
|
11
|
-
*/
|
|
12
|
-
constructor(
|
|
13
|
-
{
|
|
14
|
-
Fl32_Web_Back_Logger$: logger,
|
|
15
|
-
Fl32_Web_Back_Dto_Handler_Info$: dtoInfo,
|
|
16
|
-
Fl32_Web_Back_Enum_Stage$: STAGE,
|
|
17
|
-
}
|
|
18
|
-
) {
|
|
19
|
-
/* eslint-enable jsdoc/check-param-names */
|
|
20
|
-
// VARS
|
|
21
|
-
const _info = dtoInfo.create();
|
|
22
|
-
_info.name = this.constructor.name;
|
|
23
|
-
_info.stage = STAGE.PRE;
|
|
24
|
-
Object.freeze(_info);
|
|
25
|
-
|
|
26
|
-
// MAIN
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Log request method and URL.
|
|
30
|
-
*
|
|
31
|
-
* @param {module:http.IncomingMessage|module:http2.Http2ServerRequest} req
|
|
32
|
-
* @returns {Promise<void>}
|
|
33
|
-
*/
|
|
34
|
-
this.handle = async function (req) {
|
|
35
|
-
logger.debug(`${req.method} ${req.url}`);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Return handler registration info.
|
|
40
|
-
*
|
|
41
|
-
* @returns {Fl32_Web_Back_Dto_Handler_Info.Dto}
|
|
42
|
-
*/
|
|
43
|
-
this.getRegistrationInfo = () => _info;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Universal file handler serving files from multiple sources using helper modules.
|
|
3
|
-
*
|
|
4
|
-
* @implements Fl32_Web_Back_Api_Handler
|
|
5
|
-
*/
|
|
6
|
-
export default class Fl32_Web_Back_Handler_Static {
|
|
7
|
-
/* eslint-disable jsdoc/require-param-description,jsdoc/check-param-names */
|
|
8
|
-
/**
|
|
9
|
-
* @param {Fl32_Web_Back_Handler_Static_A_Registry} registry
|
|
10
|
-
* @param {Fl32_Web_Back_Handler_Static_A_FileService} fileService
|
|
11
|
-
* @param {Fl32_Web_Back_Helper_Respond} respond
|
|
12
|
-
* @param {Fl32_Web_Back_Logger} logger
|
|
13
|
-
* @param {Fl32_Web_Back_Dto_Handler_Info} dtoInfo
|
|
14
|
-
* @param {typeof Fl32_Web_Back_Enum_Stage} STAGE
|
|
15
|
-
*/
|
|
16
|
-
constructor(
|
|
17
|
-
{
|
|
18
|
-
Fl32_Web_Back_Handler_Static_A_Registry$: registry,
|
|
19
|
-
Fl32_Web_Back_Handler_Static_A_FileService$: fileService,
|
|
20
|
-
Fl32_Web_Back_Helper_Respond$: respond,
|
|
21
|
-
Fl32_Web_Back_Logger$: logger,
|
|
22
|
-
Fl32_Web_Back_Dto_Handler_Info$: dtoInfo,
|
|
23
|
-
Fl32_Web_Back_Enum_Stage$: STAGE,
|
|
24
|
-
}
|
|
25
|
-
) {
|
|
26
|
-
/* eslint-enable jsdoc/check-param-names */
|
|
27
|
-
|
|
28
|
-
const _info = dtoInfo.create();
|
|
29
|
-
_info.name = this.constructor.name;
|
|
30
|
-
_info.stage = STAGE.PROCESS;
|
|
31
|
-
Object.freeze(_info);
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Initialize registry with provided sources.
|
|
35
|
-
*
|
|
36
|
-
* @param {{sources: Fl32_Web_Back_Dto_Handler_Source.Dto[]}} params
|
|
37
|
-
* @returns {Promise<void>}
|
|
38
|
-
*/
|
|
39
|
-
this.init = async ({sources = []} = {}) => {
|
|
40
|
-
registry.addConfigs(sources);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Attempt to handle incoming request.
|
|
45
|
-
*
|
|
46
|
-
* @param {module:http.IncomingMessage|module:http2.Http2ServerRequest} req
|
|
47
|
-
* @param {module:http.ServerResponse|module:http2.Http2ServerResponse} res
|
|
48
|
-
* @returns {Promise<boolean>} True if file served
|
|
49
|
-
*/
|
|
50
|
-
this.handle = async (req, res) => {
|
|
51
|
-
if (!respond.isWritable(res)) return false;
|
|
52
|
-
const urlPath = decodeURIComponent(req.url.split('?')[0]);
|
|
53
|
-
const match = registry.find(urlPath);
|
|
54
|
-
if (!match) return false;
|
|
55
|
-
return fileService.serve(match.config, match.rel, req, res);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* @returns {Fl32_Web_Back_Dto_Handler_Info.Dto}
|
|
60
|
-
*/
|
|
61
|
-
this.getRegistrationInfo = () => _info;
|
|
62
|
-
}
|
|
63
|
-
}
|
package/src/Back/Helper/Cast.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
export default class Fl32_Web_Back_Helper_Cast {
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cast input data into an array. Ensures the result is always an array.
|
|
5
|
-
* Optionally casts each item using the provided itemCast function.
|
|
6
|
-
*
|
|
7
|
-
* @param {*} data - Input data to be cast to array.
|
|
8
|
-
* @param {function(*): *} [itemCast] - Optional function to cast each item.
|
|
9
|
-
* @returns {Array}
|
|
10
|
-
*/
|
|
11
|
-
array(data, itemCast) {
|
|
12
|
-
let arr = [];
|
|
13
|
-
|
|
14
|
-
if (Array.isArray(data)) {
|
|
15
|
-
arr = data;
|
|
16
|
-
} else if (data !== null) {
|
|
17
|
-
arr = [data];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return (typeof itemCast === 'function')
|
|
21
|
-
? arr.map(itemCast).filter(v => v !== undefined)
|
|
22
|
-
: arr;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Cast input data into decimal 'number' data type.
|
|
28
|
-
* @param {*} data
|
|
29
|
-
* @returns {number|undefined}
|
|
30
|
-
*/
|
|
31
|
-
decimal(data) {
|
|
32
|
-
const res = Number.parseFloat(data);
|
|
33
|
-
return ((typeof res === 'number') && (!isNaN(res))) ? res : undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Cast input data into a valid enumeration value.
|
|
38
|
-
* Supports case normalization (upper/lower).
|
|
39
|
-
* If both `upper` and `lower` are true, `upper` takes precedence.
|
|
40
|
-
*
|
|
41
|
-
* @param {*} data - The input to cast.
|
|
42
|
-
* @param {object} enu - Object whose values represent valid enum values.
|
|
43
|
-
* @param {object} [params] - Parameters object.
|
|
44
|
-
* @param {boolean} [params.lower] - Normalize input to lower case before comparison.
|
|
45
|
-
* @param {boolean} [params.upper] - Normalize input to upper case before comparison.
|
|
46
|
-
* @returns {string|undefined}
|
|
47
|
-
*/
|
|
48
|
-
enum(data, enu, {lower, upper} = {}) {
|
|
49
|
-
let norm = data;
|
|
50
|
-
|
|
51
|
-
if (typeof data === 'string') {
|
|
52
|
-
if (upper) norm = data.toUpperCase();
|
|
53
|
-
else if (lower) norm = data.toLowerCase();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const values = Object.values(enu);
|
|
57
|
-
return values.includes(norm) ? norm : undefined;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Cast input data into integer 'number' data type.
|
|
62
|
-
* @param {*} data - Input data to be cast to integer.
|
|
63
|
-
* @returns {number|undefined}
|
|
64
|
-
*/
|
|
65
|
-
int(data) {
|
|
66
|
-
const norm = (typeof data === 'string') ? data.trim() : data;
|
|
67
|
-
const res = Number.parseInt(norm);
|
|
68
|
-
return ((typeof res === 'number') && (!isNaN(res))) ? res : undefined;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Cast input data into 'string' data type.
|
|
73
|
-
* @param {*} data - Input data to be cast to string.
|
|
74
|
-
* @returns {string|undefined}
|
|
75
|
-
*/
|
|
76
|
-
string(data) {
|
|
77
|
-
if (typeof data === 'string') {
|
|
78
|
-
return data;
|
|
79
|
-
} else if (typeof data === 'number') {
|
|
80
|
-
return String(data);
|
|
81
|
-
} else if (typeof data === 'boolean') {
|
|
82
|
-
return (data) ? 'true' : 'false';
|
|
83
|
-
}
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Cast an object to a map with string keys and array-of-string values.
|
|
89
|
-
* Throws error on invalid structure or values.
|
|
90
|
-
*
|
|
91
|
-
* @param {*} data - Raw input to cast.
|
|
92
|
-
* @returns {Record<string, string[]>}
|
|
93
|
-
*/
|
|
94
|
-
stringArrayMap(data) {
|
|
95
|
-
if (data === undefined) return {};
|
|
96
|
-
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
97
|
-
throw new Error('Invalid value for allow');
|
|
98
|
-
}
|
|
99
|
-
const res = {};
|
|
100
|
-
for (const [key, arr] of Object.entries(data)) {
|
|
101
|
-
if (!Array.isArray(arr)) throw new Error(`Invalid allow list for ${key}`);
|
|
102
|
-
const k = this.string(key);
|
|
103
|
-
if (!k) throw new Error('Invalid allow key');
|
|
104
|
-
const items = [];
|
|
105
|
-
for (const item of arr) {
|
|
106
|
-
const val = this.string(item);
|
|
107
|
-
if (!val) throw new Error(`Invalid allow list for ${k}`);
|
|
108
|
-
items.push(val);
|
|
109
|
-
}
|
|
110
|
-
res[k] = items;
|
|
111
|
-
}
|
|
112
|
-
return res;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sorts named handlers by relative `before` / `after` constraints using Kahn's algorithm.
|
|
3
|
-
*/
|
|
4
|
-
export default class Fl32_Web_Back_Helper_Order_Kahn {
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Topologically sorts handlers with `name`, `before`, `after` fields.
|
|
8
|
-
*
|
|
9
|
-
* @param {Fl32_Web_Back_Api_Handler[]} handlers - Handlers to sort.
|
|
10
|
-
* @returns {Fl32_Web_Back_Api_Handler[]} - Sorted list.
|
|
11
|
-
* @throws {Error} - If circular dependency is detected.
|
|
12
|
-
*/
|
|
13
|
-
sort(handlers) {
|
|
14
|
-
const nameToHandler = new Map();
|
|
15
|
-
const graph = new Map(); // name => Set of downstream nodes
|
|
16
|
-
const inDegree = new Map(); // name => number of incoming edges
|
|
17
|
-
|
|
18
|
-
// Initialize maps
|
|
19
|
-
for (const h of handlers) {
|
|
20
|
-
const info = h.getRegistrationInfo();
|
|
21
|
-
const name = info.name;
|
|
22
|
-
nameToHandler.set(name, h);
|
|
23
|
-
graph.set(name, new Set());
|
|
24
|
-
inDegree.set(name, 0);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Build graph
|
|
28
|
-
for (const h of handlers) {
|
|
29
|
-
const {name, after = [], before = []} = h.getRegistrationInfo();
|
|
30
|
-
|
|
31
|
-
for (const dep of after) {
|
|
32
|
-
if (!graph.has(dep)) continue;
|
|
33
|
-
graph.get(dep).add(name);
|
|
34
|
-
inDegree.set(name, inDegree.get(name) + 1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
for (const dep of before) {
|
|
38
|
-
if (!graph.has(dep)) continue;
|
|
39
|
-
graph.get(name).add(dep);
|
|
40
|
-
inDegree.set(dep, inDegree.get(dep) + 1);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Kahn's algorithm
|
|
45
|
-
const queue = [];
|
|
46
|
-
for (const [name, count] of inDegree.entries()) {
|
|
47
|
-
if (count === 0) queue.push(name);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const sorted = [];
|
|
51
|
-
while (queue.length > 0) {
|
|
52
|
-
const name = queue.shift();
|
|
53
|
-
sorted.push(nameToHandler.get(name));
|
|
54
|
-
for (const neighbor of graph.get(name)) {
|
|
55
|
-
inDegree.set(neighbor, inDegree.get(neighbor) - 1);
|
|
56
|
-
if (inDegree.get(neighbor) === 0) queue.push(neighbor);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (sorted.length !== handlers.length) {
|
|
61
|
-
throw new Error('Circular dependency detected among handlers');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return sorted;
|
|
65
|
-
}
|
|
66
|
-
}
|
package/src/Back/Logger.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple logger implementation that delegates to the native console.
|
|
3
|
-
*/
|
|
4
|
-
export default class Fl32_Web_Back_Logger {
|
|
5
|
-
/**
|
|
6
|
-
* Logs an error message.
|
|
7
|
-
* @param {...any} args - The error message or data.
|
|
8
|
-
*/
|
|
9
|
-
error(...args) {
|
|
10
|
-
console.error('[ERROR]', ...args);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Logs a warning message.
|
|
15
|
-
* @param {...any} args - The warning message or data.
|
|
16
|
-
*/
|
|
17
|
-
warn(...args) {
|
|
18
|
-
console.warn('[WARN]', ...args);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Logs an informational message.
|
|
23
|
-
* @param {...any} args - The informational message or data.
|
|
24
|
-
*/
|
|
25
|
-
info(...args) {
|
|
26
|
-
console.info('[INFO]', ...args);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Logs a debug message.
|
|
31
|
-
* @param {...any} args - The debug message or data.
|
|
32
|
-
*/
|
|
33
|
-
debug(...args) {
|
|
34
|
-
console.debug('[DEBUG]', ...args);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Logs a trace message.
|
|
39
|
-
* @param {...any} args - The trace message or data.
|
|
40
|
-
*/
|
|
41
|
-
trace(...args) {
|
|
42
|
-
console.trace('[TRACE]', ...args);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Logs an exception with optional additional context.
|
|
47
|
-
* @param {Error} exception - The exception to log.
|
|
48
|
-
* @param {...any} context - Additional context or metadata.
|
|
49
|
-
*/
|
|
50
|
-
exception(exception, ...context) {
|
|
51
|
-
console.error('[EXCEPTION]', exception.stack || exception.toString(), ...context);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Factory for server configuration DTO.
|
|
3
|
-
* Supports HTTP, HTTPS and HTTP2 server types with TLS configuration.
|
|
4
|
-
*/
|
|
5
|
-
export default class Fl32_Web_Back_Server_Config {
|
|
6
|
-
/* eslint-disable jsdoc/require-param-description,jsdoc/check-param-names */
|
|
7
|
-
/**
|
|
8
|
-
* @param {Fl32_Web_Back_Helper_Cast} cast
|
|
9
|
-
* @param {typeof Fl32_Web_Back_Enum_Server_Type} SERVER_TYPE
|
|
10
|
-
* @param {Fl32_Web_Back_Server_Config_Tls} tlsFactory
|
|
11
|
-
*/
|
|
12
|
-
constructor(
|
|
13
|
-
{
|
|
14
|
-
Fl32_Web_Back_Helper_Cast$: cast,
|
|
15
|
-
Fl32_Web_Back_Enum_Server_Type$: SERVER_TYPE,
|
|
16
|
-
Fl32_Web_Back_Server_Config_Tls$: tlsFactory,
|
|
17
|
-
}
|
|
18
|
-
) {
|
|
19
|
-
/* eslint-enable jsdoc/require-param-description,jsdoc/check-param-names */
|
|
20
|
-
// INSTANCE METHODS
|
|
21
|
-
/**
|
|
22
|
-
* Creates a new DTO instance with properly casted attributes.
|
|
23
|
-
* Ensures valid values for enums and numerical fields.
|
|
24
|
-
* Validates TLS configuration when type is HTTPS.
|
|
25
|
-
*
|
|
26
|
-
* @param {Fl32_Web_Back_Server_Config.Dto|object} [data] - Raw input data for the DTO.
|
|
27
|
-
* @returns {Dto} - A properly structured DTO instance.
|
|
28
|
-
* @throws {Error} When HTTPS type is specified without TLS configuration.
|
|
29
|
-
*/
|
|
30
|
-
this.create = function (data) {
|
|
31
|
-
const res = Object.assign(new Dto(), data);
|
|
32
|
-
if (data) {
|
|
33
|
-
res.port = cast.int(data.port);
|
|
34
|
-
res.type = cast.enum(data.type, SERVER_TYPE, {lower: true});
|
|
35
|
-
|
|
36
|
-
if (data.tls) {
|
|
37
|
-
res.tls = tlsFactory.create(data.tls);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (res.type === SERVER_TYPE.HTTPS && !res.tls) {
|
|
41
|
-
throw new Error('TLS configuration is required for HTTPS server type');
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return res;
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* @memberOf Fl32_Web_Back_Server_Config
|
|
51
|
-
*/
|
|
52
|
-
class Dto {
|
|
53
|
-
/**
|
|
54
|
-
* Port to listening (3000).
|
|
55
|
-
*
|
|
56
|
-
* @type {number}
|
|
57
|
-
*/
|
|
58
|
-
port;
|
|
59
|
-
/**
|
|
60
|
-
* @type {string}
|
|
61
|
-
* @see Fl32_Web_Back_Enum_Server_Type
|
|
62
|
-
*/
|
|
63
|
-
type;
|
|
64
|
-
/**
|
|
65
|
-
* TLS configuration for HTTPS server.
|
|
66
|
-
* @type {Fl32_Web_Back_Server_Config_Tls.Dto|undefined}
|
|
67
|
-
*/
|
|
68
|
-
tls;
|
|
69
|
-
}
|
package/teqfw.json
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import {describe, it} from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import http from 'node:http';
|
|
4
|
-
import {buildTestContainer} from '../unit/common.js';
|
|
5
|
-
import Express from 'express';
|
|
6
|
-
import Fastify from 'fastify';
|
|
7
|
-
|
|
8
|
-
async function startExpress(port, dispatcher) {
|
|
9
|
-
const app = Express();
|
|
10
|
-
app.use(async (req, res) => {
|
|
11
|
-
await dispatcher.onEventRequest(req, res);
|
|
12
|
-
});
|
|
13
|
-
const server = await new Promise((resolve, reject) => {
|
|
14
|
-
const srv = app.listen(port, err => err ? reject(err) : resolve(srv));
|
|
15
|
-
});
|
|
16
|
-
return server;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function startFastify(port, dispatcher) {
|
|
20
|
-
const fastify = Fastify();
|
|
21
|
-
fastify.all('*', async (request, reply) => {
|
|
22
|
-
const req = request.raw;
|
|
23
|
-
const res = reply.raw;
|
|
24
|
-
await dispatcher.onEventRequest(req, res);
|
|
25
|
-
});
|
|
26
|
-
await fastify.listen({port});
|
|
27
|
-
return fastify;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe('Dispatcher integration with external servers', () => {
|
|
31
|
-
it('should respond via express', async () => {
|
|
32
|
-
const container = buildTestContainer();
|
|
33
|
-
const dispatcher = await container.get('Fl32_Web_Back_Dispatcher$');
|
|
34
|
-
const port = 3054;
|
|
35
|
-
const server = await startExpress(port, dispatcher);
|
|
36
|
-
|
|
37
|
-
const status = await new Promise((resolve, reject) => {
|
|
38
|
-
http.get(`http://localhost:${port}`, res => {
|
|
39
|
-
const {statusCode} = res;
|
|
40
|
-
res.resume();
|
|
41
|
-
res.on('end', () => resolve(statusCode));
|
|
42
|
-
}).on('error', reject);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
assert.strictEqual(status, 404);
|
|
46
|
-
await new Promise(resolve => server.close(resolve));
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should respond via fastify', async () => {
|
|
50
|
-
const container = buildTestContainer();
|
|
51
|
-
const dispatcher = await container.get('Fl32_Web_Back_Dispatcher$');
|
|
52
|
-
const port = 3055;
|
|
53
|
-
const fastify = await startFastify(port, dispatcher);
|
|
54
|
-
|
|
55
|
-
const status = await new Promise((resolve, reject) => {
|
|
56
|
-
http.get(`http://localhost:${port}`, res => {
|
|
57
|
-
const {statusCode} = res;
|
|
58
|
-
res.resume();
|
|
59
|
-
res.on('end', () => resolve(statusCode));
|
|
60
|
-
}).on('error', reject);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
assert.strictEqual(status, 404);
|
|
64
|
-
await fastify.close();
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import {describe, it} from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import {readFileSync} from 'node:fs';
|
|
4
|
-
import {join, dirname} from 'node:path';
|
|
5
|
-
import {fileURLToPath} from 'node:url';
|
|
6
|
-
import {buildTestContainer} from '../unit/common.js';
|
|
7
|
-
|
|
8
|
-
async function waitListening(server) {
|
|
9
|
-
if (!server.getInstance().listening) {
|
|
10
|
-
await new Promise(res => server.getInstance().once('listening', res));
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe('Fl32_Web_Back_Server', () => {
|
|
15
|
-
it('should start and respond in HTTP/1 mode', async () => {
|
|
16
|
-
const container = buildTestContainer();
|
|
17
|
-
const server = await container.get('Fl32_Web_Back_Server$');
|
|
18
|
-
const SERVER_TYPE = await container.get('Fl32_Web_Back_Enum_Server_Type$');
|
|
19
|
-
const Config = await container.get('Fl32_Web_Back_Server_Config$');
|
|
20
|
-
const http = await container.get('node:http');
|
|
21
|
-
|
|
22
|
-
const cfg = Config.create({ port: 3051, type: SERVER_TYPE.HTTP });
|
|
23
|
-
await server.start(cfg);
|
|
24
|
-
await waitListening(server);
|
|
25
|
-
|
|
26
|
-
const status = await new Promise((resolve, reject) => {
|
|
27
|
-
http.get(`http://localhost:${cfg.port}`, res => {
|
|
28
|
-
const {statusCode} = res;
|
|
29
|
-
res.resume();
|
|
30
|
-
res.on('end', () => resolve(statusCode));
|
|
31
|
-
}).on('error', reject);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
assert.strictEqual(status, 404);
|
|
35
|
-
await server.stop();
|
|
36
|
-
assert.strictEqual(server.getInstance(), undefined);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should start and respond in HTTP/2 mode', async () => {
|
|
40
|
-
const container = buildTestContainer();
|
|
41
|
-
const server = await container.get('Fl32_Web_Back_Server$');
|
|
42
|
-
const SERVER_TYPE = await container.get('Fl32_Web_Back_Enum_Server_Type$');
|
|
43
|
-
const Config = await container.get('Fl32_Web_Back_Server_Config$');
|
|
44
|
-
const http2 = await container.get('node:http2');
|
|
45
|
-
|
|
46
|
-
const cfg = Config.create({ port: 3052, type: SERVER_TYPE.HTTP2 });
|
|
47
|
-
await server.start(cfg);
|
|
48
|
-
await waitListening(server);
|
|
49
|
-
|
|
50
|
-
const status = await new Promise((resolve, reject) => {
|
|
51
|
-
const client = http2.connect(`http://localhost:${cfg.port}`);
|
|
52
|
-
client.on('error', reject);
|
|
53
|
-
const req = client.request({ ':path': '/' });
|
|
54
|
-
req.on('response', headers => {
|
|
55
|
-
resolve(headers[':status']);
|
|
56
|
-
});
|
|
57
|
-
req.on('end', () => client.close());
|
|
58
|
-
req.end();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
assert.strictEqual(status, 404);
|
|
62
|
-
await server.stop();
|
|
63
|
-
assert.strictEqual(server.getInstance(), undefined);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should start and respond in HTTPS mode', async () => {
|
|
67
|
-
const container = buildTestContainer();
|
|
68
|
-
const server = await container.get('Fl32_Web_Back_Server$');
|
|
69
|
-
const SERVER_TYPE = await container.get('Fl32_Web_Back_Enum_Server_Type$');
|
|
70
|
-
const Config = await container.get('Fl32_Web_Back_Server_Config$');
|
|
71
|
-
const http2 = await container.get('node:http2');
|
|
72
|
-
|
|
73
|
-
const dir = dirname(fileURLToPath(import.meta.url));
|
|
74
|
-
const certDir = join(dir, '..', 'certs');
|
|
75
|
-
const key = readFileSync(join(certDir, 'key.pem'), 'utf8');
|
|
76
|
-
const cert = readFileSync(join(certDir, 'cert.pem'), 'utf8');
|
|
77
|
-
let ca;
|
|
78
|
-
try { ca = readFileSync(join(certDir, 'ca.pem'), 'utf8'); } catch {}
|
|
79
|
-
|
|
80
|
-
const cfg = Config.create({
|
|
81
|
-
port: 3053,
|
|
82
|
-
type: SERVER_TYPE.HTTPS,
|
|
83
|
-
tls: {key, cert, ca}
|
|
84
|
-
});
|
|
85
|
-
await server.start(cfg);
|
|
86
|
-
await waitListening(server);
|
|
87
|
-
|
|
88
|
-
const status = await new Promise((resolve, reject) => {
|
|
89
|
-
const client = http2.connect(`https://localhost:${cfg.port}`, {
|
|
90
|
-
rejectUnauthorized: false,
|
|
91
|
-
});
|
|
92
|
-
client.on('error', reject);
|
|
93
|
-
const req = client.request({ ':path': '/' });
|
|
94
|
-
req.on('response', headers => {
|
|
95
|
-
resolve(headers[':status']);
|
|
96
|
-
});
|
|
97
|
-
req.on('end', () => client.close());
|
|
98
|
-
req.end();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
assert.strictEqual(status, 404);
|
|
102
|
-
await server.stop();
|
|
103
|
-
assert.strictEqual(server.getInstance(), undefined);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('Fl32_Web_Back_Api_Handler', () => {
|
|
108
|
-
it('should serve allowed NPM file', async () => {
|
|
109
|
-
const container = buildTestContainer();
|
|
110
|
-
const dispatcher = await container.get('Fl32_Web_Back_Dispatcher$');
|
|
111
|
-
const handler = await container.get('Fl32_Web_Back_Handler_Static$');
|
|
112
|
-
const Cfg = await container.get('Fl32_Web_Back_Dto_Handler_Source$');
|
|
113
|
-
await handler.init({
|
|
114
|
-
sources: [Cfg.create({
|
|
115
|
-
root: 'node_modules',
|
|
116
|
-
prefix: '/npm/',
|
|
117
|
-
allow: {
|
|
118
|
-
'@teqfw/di/src': ['.'],
|
|
119
|
-
},
|
|
120
|
-
})],
|
|
121
|
-
});
|
|
122
|
-
dispatcher.addHandler(handler);
|
|
123
|
-
dispatcher.orderHandlers();
|
|
124
|
-
|
|
125
|
-
const server = await container.get('Fl32_Web_Back_Server$');
|
|
126
|
-
const SERVER_TYPE = await container.get('Fl32_Web_Back_Enum_Server_Type$');
|
|
127
|
-
const Config = await container.get('Fl32_Web_Back_Server_Config$');
|
|
128
|
-
const http = await container.get('node:http');
|
|
129
|
-
|
|
130
|
-
const cfg = Config.create({ port: 3056, type: SERVER_TYPE.HTTP });
|
|
131
|
-
await server.start(cfg);
|
|
132
|
-
await waitListening(server);
|
|
133
|
-
|
|
134
|
-
const result = await new Promise((resolve, reject) => {
|
|
135
|
-
const req = http.get({
|
|
136
|
-
hostname: 'localhost',
|
|
137
|
-
port: cfg.port,
|
|
138
|
-
path: '/npm/@teqfw/di/src/Api/Container/Parser/Chunk.js',
|
|
139
|
-
}, res => {
|
|
140
|
-
const chunks = [];
|
|
141
|
-
res.on('data', ch => chunks.push(ch));
|
|
142
|
-
res.on('end', () => {
|
|
143
|
-
resolve({ status: res.statusCode, body: Buffer.concat(chunks).toString() });
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
req.on('error', reject);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
assert.strictEqual(result.status, 200);
|
|
150
|
-
assert.match(result.body, /class TeqFw_Di_Api_Container_Parser_Chunk/);
|
|
151
|
-
|
|
152
|
-
await server.stop();
|
|
153
|
-
assert.strictEqual(server.getInstance(), undefined);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('should serve allowed source file', async () => {
|
|
157
|
-
const container = buildTestContainer();
|
|
158
|
-
const dispatcher = await container.get('Fl32_Web_Back_Dispatcher$');
|
|
159
|
-
const handler = await container.get('Fl32_Web_Back_Handler_Static$');
|
|
160
|
-
const Cfg = await container.get('Fl32_Web_Back_Dto_Handler_Source$');
|
|
161
|
-
await handler.init({
|
|
162
|
-
sources: [Cfg.create({
|
|
163
|
-
root: 'src',
|
|
164
|
-
prefix: '/sources/',
|
|
165
|
-
allow: {
|
|
166
|
-
Back: ['Server.js'],
|
|
167
|
-
},
|
|
168
|
-
})],
|
|
169
|
-
});
|
|
170
|
-
dispatcher.addHandler(handler);
|
|
171
|
-
dispatcher.orderHandlers();
|
|
172
|
-
|
|
173
|
-
const server = await container.get('Fl32_Web_Back_Server$');
|
|
174
|
-
const SERVER_TYPE = await container.get('Fl32_Web_Back_Enum_Server_Type$');
|
|
175
|
-
const Config = await container.get('Fl32_Web_Back_Server_Config$');
|
|
176
|
-
const http = await container.get('node:http');
|
|
177
|
-
|
|
178
|
-
const cfg = Config.create({ port: 3057, type: SERVER_TYPE.HTTP });
|
|
179
|
-
await server.start(cfg);
|
|
180
|
-
await waitListening(server);
|
|
181
|
-
|
|
182
|
-
const result = await new Promise((resolve, reject) => {
|
|
183
|
-
const req = http.get({
|
|
184
|
-
hostname: 'localhost',
|
|
185
|
-
port: cfg.port,
|
|
186
|
-
path: '/sources/Back/Server.js',
|
|
187
|
-
}, res => {
|
|
188
|
-
const chunks = [];
|
|
189
|
-
res.on('data', ch => chunks.push(ch));
|
|
190
|
-
res.on('end', () => {
|
|
191
|
-
resolve({ status: res.statusCode, body: Buffer.concat(chunks).toString() });
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
req.on('error', reject);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
assert.strictEqual(result.status, 200);
|
|
198
|
-
assert.match(result.body, /class Fl32_Web_Back_Server/);
|
|
199
|
-
|
|
200
|
-
await server.stop();
|
|
201
|
-
assert.strictEqual(server.getInstance(), undefined);
|
|
202
|
-
});
|
|
203
|
-
});
|