web_plsql 1.0.0 → 1.3.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/README.md +108 -13
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/frontend/assets/main-0oxRTIkL.js +147 -0
- package/dist/frontend/assets/main-0oxRTIkL.js.br +0 -0
- package/dist/frontend/assets/main-0oxRTIkL.js.gz +0 -0
- package/dist/frontend/assets/main-TYgLak6O.css +1 -0
- package/dist/frontend/assets/main-TYgLak6O.css.br +0 -0
- package/dist/frontend/assets/main-TYgLak6O.css.gz +0 -0
- package/dist/frontend/index.html +516 -0
- package/dist/frontend/index.html.br +0 -0
- package/dist/frontend/index.html.gz +0 -0
- package/dist/index.d.mts +646 -0
- package/dist/index.mjs +3548 -0
- package/dist/index.mjs.map +1 -0
- package/dist/oracledb-mock-Dn8aHtd7.mjs +68 -0
- package/dist/oracledb-mock-Dn8aHtd7.mjs.map +1 -0
- package/examples/config-apex.js +1 -1
- package/examples/config-docker.js +1 -2
- package/package.json +45 -32
- package/examples/config-native.js +0 -68
- package/src/admin/client/charts.ts +0 -299
- package/src/admin/client/main.js +0 -3
- package/src/admin/client/tailwind.css +0 -41
- package/src/admin/index.html +0 -315
- package/src/admin/js/api.ts +0 -95
- package/src/admin/js/app.ts +0 -306
- package/src/admin/js/eslint.config.js +0 -74
- package/src/admin/js/schemas.ts +0 -153
- package/src/admin/js/templates/config.ts +0 -146
- package/src/admin/js/templates/errorRow.ts +0 -18
- package/src/admin/js/templates/index.ts +0 -3
- package/src/admin/js/templates/poolCard.ts +0 -61
- package/src/admin/js/tsconfig.json +0 -24
- package/src/admin/js/types.ts +0 -223
- package/src/admin/js/ui/theme.ts +0 -93
- package/src/admin/js/ui/views.ts +0 -164
- package/src/admin/js/util/format.ts +0 -27
- package/src/admin/lib/assets/main-zpdhQ1gD.css +0 -1
- package/src/admin/lib/chart.bundle.js +0 -139
- package/src/admin/style.css +0 -1321
- package/src/bin/load-test.js +0 -202
- package/src/handler/handlerAdmin.js +0 -198
- package/src/handler/handlerLogger.js +0 -21
- package/src/handler/handlerMetrics.js +0 -68
- package/src/handler/handlerUpload.js +0 -26
- package/src/handler/plsql/cgi.js +0 -131
- package/src/handler/plsql/errorPage.js +0 -117
- package/src/handler/plsql/handlerPlSql.js +0 -90
- package/src/handler/plsql/parsePage.js +0 -213
- package/src/handler/plsql/procedure.js +0 -356
- package/src/handler/plsql/procedureError.js +0 -40
- package/src/handler/plsql/procedureNamed.js +0 -268
- package/src/handler/plsql/procedureSanitize.js +0 -262
- package/src/handler/plsql/procedureVariable.js +0 -54
- package/src/handler/plsql/request.js +0 -124
- package/src/handler/plsql/requestError.js +0 -23
- package/src/handler/plsql/sendResponse.js +0 -136
- package/src/handler/plsql/stream.js +0 -28
- package/src/handler/plsql/upload.js +0 -109
- package/src/index.js +0 -20
- package/src/server/config.js +0 -57
- package/src/server/connections.js +0 -40
- package/src/server/server.js +0 -289
- package/src/types.js +0 -150
- package/src/util/cache.js +0 -123
- package/src/util/errorToString.js +0 -24
- package/src/util/file.js +0 -91
- package/src/util/html.js +0 -53
- package/src/util/jsonLogger.js +0 -47
- package/src/util/oracle.js +0 -80
- package/src/util/shutdown.js +0 -44
- package/src/util/trace.js +0 -325
- package/src/util/type.js +0 -6
- package/src/util/util.js +0 -66
- package/src/version.js +0 -5
- package/types/handler/handlerAdmin.d.ts +0 -9
- package/types/handler/handlerLogger.d.ts +0 -2
- package/types/handler/handlerMetrics.d.ts +0 -25
- package/types/handler/handlerUpload.d.ts +0 -7
- package/types/handler/plsql/cgi.d.ts +0 -4
- package/types/handler/plsql/errorPage.d.ts +0 -11
- package/types/handler/plsql/handlerPlSql.d.ts +0 -13
- package/types/handler/plsql/parsePage.d.ts +0 -3
- package/types/handler/plsql/procedure.d.ts +0 -12
- package/types/handler/plsql/procedureError.d.ts +0 -23
- package/types/handler/plsql/procedureNamed.d.ts +0 -14
- package/types/handler/plsql/procedureSanitize.d.ts +0 -11
- package/types/handler/plsql/procedureVariable.d.ts +0 -10
- package/types/handler/plsql/request.d.ts +0 -9
- package/types/handler/plsql/requestError.d.ts +0 -8
- package/types/handler/plsql/sendResponse.d.ts +0 -6
- package/types/handler/plsql/upload.d.ts +0 -5
- package/types/index.d.ts +0 -11
- package/types/server/config.d.ts +0 -2
- package/types/server/server.d.ts +0 -74
- package/types/types.d.ts +0 -328
- package/types/util/cache.d.ts +0 -69
- package/types/util/errorToString.d.ts +0 -1
- package/types/util/file.d.ts +0 -6
- package/types/util/html.d.ts +0 -3
- package/types/util/jsonLogger.d.ts +0 -45
- package/types/util/oracle.d.ts +0 -5
- package/types/util/shutdown.d.ts +0 -2
- package/types/util/trace.d.ts +0 -22
- package/types/util/type.d.ts +0 -1
- package/types/util/util.d.ts +0 -3
- package/types/version.d.ts +0 -1
- /package/{src/admin/favicon.svg → dist/frontend/assets/favicon-mQAM4tVu.svg} +0 -0
package/src/bin/load-test.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Load testing CLI tool for web_plsql.
|
|
3
|
-
* This tool simulates parallel traffic to the middleware and provides real-time statistics.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {performance} from 'node:perf_hooks';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {object} Metrics
|
|
10
|
-
* @property {number} total Total requests made.
|
|
11
|
-
* @property {number} success Number of successful requests (2xx).
|
|
12
|
-
* @property {number} error Number of failed requests.
|
|
13
|
-
* @property {number} minLatency Minimum latency in milliseconds.
|
|
14
|
-
* @property {number} maxLatency Maximum latency in milliseconds.
|
|
15
|
-
* @property {number} sumLatency Sum of all latencies for average calculation.
|
|
16
|
-
* @property {Map<string|number, number>} statusCodes Frequency of status codes.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/** @type {Metrics} */
|
|
20
|
-
const metrics = {
|
|
21
|
-
total: 0,
|
|
22
|
-
success: 0,
|
|
23
|
-
error: 0,
|
|
24
|
-
minLatency: Number.MAX_SAFE_INTEGER,
|
|
25
|
-
maxLatency: 0,
|
|
26
|
-
sumLatency: 0,
|
|
27
|
-
statusCodes: new Map(),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
let stopped = false;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Parse CLI arguments.
|
|
34
|
-
* @returns {{ url: string, concurrency: number, duration: number }} Options object.
|
|
35
|
-
*/
|
|
36
|
-
function parseArgs() {
|
|
37
|
-
const args = process.argv.slice(2);
|
|
38
|
-
const options = {
|
|
39
|
-
url: '',
|
|
40
|
-
concurrency: 10,
|
|
41
|
-
duration: 30,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
for (let i = 0; i < args.length; i++) {
|
|
45
|
-
const arg = args[i];
|
|
46
|
-
const nextArg = args[i + 1] ?? '';
|
|
47
|
-
|
|
48
|
-
if (arg === '--url' || arg === '-u') {
|
|
49
|
-
options.url = nextArg;
|
|
50
|
-
i++;
|
|
51
|
-
} else if (arg === '--concurrency' || arg === '-c') {
|
|
52
|
-
options.concurrency = parseInt(nextArg, 10);
|
|
53
|
-
i++;
|
|
54
|
-
} else if (arg === '--duration' || arg === '-d') {
|
|
55
|
-
options.duration = parseInt(nextArg, 10);
|
|
56
|
-
i++;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!options.url) {
|
|
61
|
-
console.error('Usage: node src/bin/load-test.js --url <url> [-c concurrency] [-d duration]');
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return options;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Record a single request's result.
|
|
70
|
-
* @param {boolean} ok Whether the request succeeded.
|
|
71
|
-
* @param {number} latency Latency in milliseconds.
|
|
72
|
-
* @param {number|string} status Status code or error string.
|
|
73
|
-
*/
|
|
74
|
-
function recordMetrics(ok, latency, status) {
|
|
75
|
-
metrics.total++;
|
|
76
|
-
if (ok) {
|
|
77
|
-
metrics.success++;
|
|
78
|
-
} else {
|
|
79
|
-
metrics.error++;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
metrics.minLatency = Math.min(metrics.minLatency, latency);
|
|
83
|
-
metrics.maxLatency = Math.max(metrics.maxLatency, latency);
|
|
84
|
-
metrics.sumLatency += latency;
|
|
85
|
-
|
|
86
|
-
const count = metrics.statusCodes.get(status) ?? 0;
|
|
87
|
-
metrics.statusCodes.set(status, count + 1);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Clear the console and move cursor to the top.
|
|
92
|
-
*/
|
|
93
|
-
function clearConsole() {
|
|
94
|
-
process.stdout.write('\x1b[2J\x1b[0;0H');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Render the dashboard.
|
|
99
|
-
* @param {number} elapsed Elapsed time in seconds.
|
|
100
|
-
* @param {number} duration Total duration in seconds.
|
|
101
|
-
* @param {string} url Target URL.
|
|
102
|
-
* @param {number} concurrency Concurrency level.
|
|
103
|
-
*/
|
|
104
|
-
function renderDashboard(elapsed, duration, url, concurrency) {
|
|
105
|
-
clearConsole();
|
|
106
|
-
const rps = elapsed > 0 ? (metrics.total / elapsed).toFixed(1) : '0.0';
|
|
107
|
-
const avgLatency = metrics.total > 0 ? (metrics.sumLatency / metrics.total).toFixed(1) : '0.0';
|
|
108
|
-
const progress = Math.min(100, (elapsed / duration) * 100).toFixed(1);
|
|
109
|
-
const barWidth = 30;
|
|
110
|
-
const filledWidth = Math.floor((barWidth * parseFloat(progress)) / 100);
|
|
111
|
-
const bar = '='.repeat(filledWidth) + '>'.padEnd(barWidth - filledWidth, ' ');
|
|
112
|
-
|
|
113
|
-
console.log('\x1b[1m\x1b[36mWeb PL/SQL Load Test\x1b[0m');
|
|
114
|
-
console.log('='.repeat(40));
|
|
115
|
-
console.log(`\x1b[33mURL:\x1b[0m ${url}`);
|
|
116
|
-
console.log(`\x1b[33mConcurrency:\x1b[0m ${concurrency}`);
|
|
117
|
-
console.log(`\x1b[33mDuration:\x1b[0m ${duration}s`);
|
|
118
|
-
console.log('='.repeat(40));
|
|
119
|
-
console.log(`\x1b[32mProgress:\x1b[0m [${bar}] ${progress}% (${elapsed.toFixed(1)}s)`);
|
|
120
|
-
console.log(`\x1b[32mRPS:\x1b[0m ${rps}`);
|
|
121
|
-
console.log(`\x1b[32mTotal:\x1b[0m ${metrics.total}`);
|
|
122
|
-
console.log(`\x1b[32mSuccess:\x1b[0m ${metrics.success}`);
|
|
123
|
-
console.log(`\x1b[31mErrors:\x1b[0m ${metrics.error}`);
|
|
124
|
-
console.log('='.repeat(40));
|
|
125
|
-
console.log('\x1b[1mLatency (ms)\x1b[0m');
|
|
126
|
-
console.log(`\x1b[34mAvg:\x1b[0m ${avgLatency.padStart(8)}`);
|
|
127
|
-
console.log(`\x1b[34mMin:\x1b[0m ${(metrics.minLatency === Number.MAX_SAFE_INTEGER ? 0 : metrics.minLatency).toFixed(1).padStart(8)}`);
|
|
128
|
-
console.log(`\x1b[34mMax:\x1b[0m ${metrics.maxLatency.toFixed(1).padStart(8)}`);
|
|
129
|
-
console.log('='.repeat(40));
|
|
130
|
-
|
|
131
|
-
if (metrics.statusCodes.size > 0) {
|
|
132
|
-
console.log('\x1b[1mStatus Codes\x1b[0m');
|
|
133
|
-
for (const [code, count] of metrics.statusCodes.entries()) {
|
|
134
|
-
const numericCode = typeof code === 'number' ? code : 0;
|
|
135
|
-
const color = numericCode >= 200 && numericCode < 300 ? '\x1b[32m' : '\x1b[31m';
|
|
136
|
-
console.log(`${color}${code}\x1b[0m: ${count}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Worker function to maintain concurrency.
|
|
143
|
-
* @param {string} url The target URL.
|
|
144
|
-
* @param {number} endTime The end time in milliseconds.
|
|
145
|
-
*/
|
|
146
|
-
async function runWorker(url, endTime) {
|
|
147
|
-
while (performance.now() < endTime && !stopped) {
|
|
148
|
-
const start = performance.now();
|
|
149
|
-
try {
|
|
150
|
-
const res = await fetch(url, {
|
|
151
|
-
signal: AbortSignal.timeout(10000), // 10s timeout
|
|
152
|
-
});
|
|
153
|
-
const latency = performance.now() - start;
|
|
154
|
-
recordMetrics(res.ok, latency, res.status);
|
|
155
|
-
// Consume body to free up connection
|
|
156
|
-
await res.arrayBuffer();
|
|
157
|
-
} catch (err) {
|
|
158
|
-
const latency = performance.now() - start;
|
|
159
|
-
const errorName = err instanceof Error ? err.name : 'UnknownError';
|
|
160
|
-
recordMetrics(false, latency, errorName === 'TimeoutError' ? 'Timeout' : 'NetworkErr');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Main function.
|
|
167
|
-
*/
|
|
168
|
-
async function main() {
|
|
169
|
-
const options = parseArgs();
|
|
170
|
-
const startTime = performance.now();
|
|
171
|
-
const endTime = startTime + options.duration * 1000;
|
|
172
|
-
|
|
173
|
-
console.log(`Starting load test on ${options.url}...`);
|
|
174
|
-
|
|
175
|
-
// Start workers
|
|
176
|
-
const workers = Array.from({length: options.concurrency}, () => runWorker(options.url, endTime));
|
|
177
|
-
|
|
178
|
-
// Dashboard refresh interval
|
|
179
|
-
const uiInterval = setInterval(() => {
|
|
180
|
-
const elapsed = (performance.now() - startTime) / 1000;
|
|
181
|
-
renderDashboard(elapsed, options.duration, options.url, options.concurrency);
|
|
182
|
-
}, 200);
|
|
183
|
-
|
|
184
|
-
// Handle Ctrl+C
|
|
185
|
-
process.on('SIGINT', () => {
|
|
186
|
-
stopped = true;
|
|
187
|
-
console.log('\nStopping...');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
await Promise.all(workers);
|
|
191
|
-
clearInterval(uiInterval);
|
|
192
|
-
|
|
193
|
-
// Final render
|
|
194
|
-
const finalElapsed = (performance.now() - startTime) / 1000;
|
|
195
|
-
renderDashboard(finalElapsed, options.duration, options.url, options.concurrency);
|
|
196
|
-
console.log('\n\x1b[1m\x1b[32mTest Complete!\x1b[0m\n');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
main().catch((/** @type {unknown} */ err) => {
|
|
200
|
-
console.error('Fatal error:', err);
|
|
201
|
-
process.exit(1);
|
|
202
|
-
});
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import readline from 'node:readline';
|
|
4
|
-
import {AdminContext} from '../server/server.js';
|
|
5
|
-
import {getVersion} from '../version.js';
|
|
6
|
-
|
|
7
|
-
const version = getVersion();
|
|
8
|
-
import {forceShutdown} from '../util/shutdown.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {import('express').Request} Request
|
|
12
|
-
* @typedef {import('express').Response} Response
|
|
13
|
-
* @typedef {import('express').NextFunction} NextFunction
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
export const handlerAdmin = express.Router();
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Helper to read last N lines of a file
|
|
20
|
-
* @param {string} filePath - Path to file
|
|
21
|
-
* @param {number} n - Number of lines
|
|
22
|
-
* @returns {Promise<string[]>} - The lines
|
|
23
|
-
*/
|
|
24
|
-
const readLastLines = async (filePath, n = 100) => {
|
|
25
|
-
if (!fs.existsSync(filePath)) {
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const fileStream = fs.createReadStream(filePath);
|
|
30
|
-
const rl = readline.createInterface({
|
|
31
|
-
input: fileStream,
|
|
32
|
-
crlfDelay: Infinity,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
/** @type {string[]} */
|
|
36
|
-
const lines = [];
|
|
37
|
-
for await (const line of rl) {
|
|
38
|
-
lines.push(line);
|
|
39
|
-
if (lines.length > n) {
|
|
40
|
-
lines.shift();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return lines;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// GET /api/status
|
|
47
|
-
handlerAdmin.get('/api/status', (_req, res) => {
|
|
48
|
-
const uptime = (new Date().getTime() - AdminContext.startTime.getTime()) / 1000;
|
|
49
|
-
|
|
50
|
-
const poolStats = AdminContext.pools.map((pool, index) => {
|
|
51
|
-
const name = AdminContext.caches[index]?.poolName ?? `pool-${index}`;
|
|
52
|
-
const p = /** @type {import('oracledb').Pool & {getStatistics?: () => Record<string, unknown>}} */ (pool);
|
|
53
|
-
return {
|
|
54
|
-
name,
|
|
55
|
-
stats: typeof p.getStatistics === 'function' ? p.getStatistics() : null,
|
|
56
|
-
connectionsOpen: pool.connectionsOpen,
|
|
57
|
-
connectionsInUse: pool.connectionsInUse,
|
|
58
|
-
};
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const memUsage = process.memoryUsage();
|
|
62
|
-
const cpuUsage = process.cpuUsage();
|
|
63
|
-
|
|
64
|
-
res.json({
|
|
65
|
-
version,
|
|
66
|
-
status: AdminContext.paused ? 'paused' : 'running',
|
|
67
|
-
uptime,
|
|
68
|
-
startTime: AdminContext.startTime,
|
|
69
|
-
metrics: {
|
|
70
|
-
...AdminContext.metrics,
|
|
71
|
-
avgResponseTime: AdminContext.metrics.requestCount > 0 ? AdminContext.metrics.totalDuration / AdminContext.metrics.requestCount : 0,
|
|
72
|
-
},
|
|
73
|
-
pools: poolStats,
|
|
74
|
-
system: {
|
|
75
|
-
nodeVersion: process.version,
|
|
76
|
-
platform: process.platform,
|
|
77
|
-
arch: process.arch,
|
|
78
|
-
memory: {
|
|
79
|
-
rss: memUsage.rss,
|
|
80
|
-
heapTotal: memUsage.heapTotal,
|
|
81
|
-
heapUsed: memUsage.heapUsed,
|
|
82
|
-
external: memUsage.external,
|
|
83
|
-
},
|
|
84
|
-
cpu: {
|
|
85
|
-
user: cpuUsage.user,
|
|
86
|
-
system: cpuUsage.system,
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
config: AdminContext.config
|
|
90
|
-
? {
|
|
91
|
-
...AdminContext.config,
|
|
92
|
-
adminPassword: AdminContext.config.adminPassword ? '********' : undefined,
|
|
93
|
-
routePlSql: AdminContext.config.routePlSql.map((p) => ({
|
|
94
|
-
...p,
|
|
95
|
-
password: '********',
|
|
96
|
-
})),
|
|
97
|
-
}
|
|
98
|
-
: null,
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// GET /api/logs/error
|
|
103
|
-
handlerAdmin.get('/api/logs/error', async (req, res) => {
|
|
104
|
-
try {
|
|
105
|
-
const limit = Number(req.query.limit) || 100;
|
|
106
|
-
const logFile = 'error.json.log';
|
|
107
|
-
const lines = await readLastLines(logFile, limit);
|
|
108
|
-
|
|
109
|
-
/** @type {Record<string, unknown>[]} */
|
|
110
|
-
const logs = [];
|
|
111
|
-
for (const line of lines) {
|
|
112
|
-
try {
|
|
113
|
-
const parsed = /** @type {unknown} */ (JSON.parse(line));
|
|
114
|
-
if (parsed && typeof parsed === 'object') {
|
|
115
|
-
logs.push(/** @type {Record<string, unknown>} */ (parsed));
|
|
116
|
-
}
|
|
117
|
-
} catch (e) {
|
|
118
|
-
// ignore
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
res.json(logs.reverse());
|
|
123
|
-
} catch (err) {
|
|
124
|
-
res.status(500).json({error: String(err)});
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// GET /api/logs/access
|
|
129
|
-
handlerAdmin.get('/api/logs/access', async (req, res) => {
|
|
130
|
-
try {
|
|
131
|
-
const limit = Number(req.query.limit) || 100;
|
|
132
|
-
const logFile = AdminContext.config?.loggerFilename ?? 'access.log';
|
|
133
|
-
|
|
134
|
-
if (!AdminContext.config?.loggerFilename) {
|
|
135
|
-
res.json({message: 'Access logging not enabled'});
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const lines = await readLastLines(logFile, limit);
|
|
140
|
-
res.json(lines.reverse());
|
|
141
|
-
} catch (err) {
|
|
142
|
-
res.status(500).json({error: String(err)});
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// GET /api/cache
|
|
147
|
-
handlerAdmin.get('/api/cache', (_req, res) => {
|
|
148
|
-
const caches = AdminContext.caches.map((c) => ({
|
|
149
|
-
poolName: c.poolName,
|
|
150
|
-
procedureNameCache: {
|
|
151
|
-
size: c.procedureNameCache.keys().length,
|
|
152
|
-
stats: c.procedureNameCache.getStats(),
|
|
153
|
-
},
|
|
154
|
-
argumentCache: {
|
|
155
|
-
size: c.argumentCache.keys().length,
|
|
156
|
-
stats: c.argumentCache.getStats(),
|
|
157
|
-
},
|
|
158
|
-
}));
|
|
159
|
-
res.json(caches);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// POST /api/cache/clear
|
|
163
|
-
handlerAdmin.post('/api/cache/clear', (req, res) => {
|
|
164
|
-
const body = /** @type {unknown} */ (req.body);
|
|
165
|
-
const poolName = body && typeof body === 'object' && 'poolName' in body && typeof body.poolName === 'string' ? body.poolName : undefined;
|
|
166
|
-
|
|
167
|
-
let cleared = 0;
|
|
168
|
-
AdminContext.caches.forEach((c) => {
|
|
169
|
-
if (!poolName || c.poolName === poolName) {
|
|
170
|
-
c.procedureNameCache.clear();
|
|
171
|
-
cleared++;
|
|
172
|
-
c.argumentCache.clear();
|
|
173
|
-
cleared++;
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
res.json({message: `Cleared ${cleared} caches`});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// POST /api/server/:action
|
|
181
|
-
handlerAdmin.post('/api/server/:action', (req, res) => {
|
|
182
|
-
const action = req.params.action;
|
|
183
|
-
|
|
184
|
-
if (action === 'stop') {
|
|
185
|
-
res.json({message: 'Server shutting down...'});
|
|
186
|
-
setTimeout(() => {
|
|
187
|
-
forceShutdown();
|
|
188
|
-
}, 100);
|
|
189
|
-
} else if (action === 'pause') {
|
|
190
|
-
AdminContext.paused = true;
|
|
191
|
-
res.json({message: 'Server paused', status: 'paused'});
|
|
192
|
-
} else if (action === 'resume') {
|
|
193
|
-
AdminContext.paused = false;
|
|
194
|
-
res.json({message: 'Server resumed', status: 'running'});
|
|
195
|
-
} else {
|
|
196
|
-
res.status(400).json({error: 'Invalid action'});
|
|
197
|
-
}
|
|
198
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import debugModule from 'debug';
|
|
2
|
-
const debug = debugModule('webplsql:handlerLogger');
|
|
3
|
-
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import morgan from 'morgan';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {import('express').RequestHandler} RequestHandler
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create the upload middleware.
|
|
14
|
-
* @param {string} filename - Output filename.
|
|
15
|
-
* @returns {RequestHandler} - Request handler.
|
|
16
|
-
*/
|
|
17
|
-
export const handlerLogger = (filename) => {
|
|
18
|
-
debug('register');
|
|
19
|
-
|
|
20
|
-
return morgan('combined', {stream: fs.createWriteStream(path.join(process.cwd(), filename), {flags: 'a'})});
|
|
21
|
-
};
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import debugModule from 'debug';
|
|
2
|
-
const debug = debugModule('webplsql:handlerMetrics');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @typedef {import('express').RequestHandler} RequestHandler
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {object} metricsType
|
|
10
|
-
* @property {Date} started - When was the server started.
|
|
11
|
-
* @property {number} totalRequests - Total number of requests.
|
|
12
|
-
* @property {number} totalRequestDuration - Total amount of time in ms in request.
|
|
13
|
-
* @property {number} minRequestDuration - Min amount of time in ms in request.
|
|
14
|
-
* @property {number} maxRequestDuration - Max amount of time in ms in request.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Convert hrtime to ms.
|
|
19
|
-
* @param {[number, number]} hrtime - Time
|
|
20
|
-
* @returns {number} Millisecoinds.
|
|
21
|
-
*/
|
|
22
|
-
const hrtime2ms = (hrtime) => hrtime[0] * 1000 + hrtime[1] / 1_000_000;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* metrics initializer.
|
|
26
|
-
* @returns {metricsType} The metrics.
|
|
27
|
-
*/
|
|
28
|
-
export const initMetrics = () => {
|
|
29
|
-
return {
|
|
30
|
-
started: new Date(),
|
|
31
|
-
totalRequests: 0,
|
|
32
|
-
totalRequestDuration: 0,
|
|
33
|
-
minRequestDuration: -1,
|
|
34
|
-
maxRequestDuration: -1,
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* The metrics handler.
|
|
40
|
-
* @param {metricsType} metrics - the nmetrics.
|
|
41
|
-
* @returns {RequestHandler} - Request handler.
|
|
42
|
-
*/
|
|
43
|
-
export const handlerMetrics = (metrics) => {
|
|
44
|
-
debug('register');
|
|
45
|
-
|
|
46
|
-
return (req, res, next) => {
|
|
47
|
-
metrics.totalRequests++;
|
|
48
|
-
|
|
49
|
-
const start = hrtime2ms(process.hrtime());
|
|
50
|
-
res.on('finish', () => {
|
|
51
|
-
const duration = hrtime2ms(process.hrtime()) - start;
|
|
52
|
-
|
|
53
|
-
metrics.totalRequestDuration += duration;
|
|
54
|
-
if (metrics.minRequestDuration < 0 || duration < metrics.minRequestDuration) {
|
|
55
|
-
metrics.minRequestDuration = duration;
|
|
56
|
-
}
|
|
57
|
-
if (metrics.maxRequestDuration < 0 || duration > metrics.maxRequestDuration) {
|
|
58
|
-
metrics.maxRequestDuration = duration;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (debug.enabled) {
|
|
62
|
-
debug(`Request to ${req.params?.name} ${req.url} took ${duration.toFixed(3)}ms`);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
next();
|
|
67
|
-
};
|
|
68
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import multer from 'multer';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @typedef {import('express').RequestHandler} RequestHandler
|
|
5
|
-
* @typedef {import('express').Response} Response
|
|
6
|
-
* @typedef {import('express').NextFunction} NextFunction
|
|
7
|
-
* @typedef {import('oracledb').Pool} Pool
|
|
8
|
-
* @typedef {import('../types.js').environmentType} environmentType
|
|
9
|
-
* @typedef {import('../types.js').configType} configType
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create the upload middleware.
|
|
14
|
-
* @param {number} [uploadFileSizeLimit] - Maximum size of each uploaded file in bytes or no limit if omitted.
|
|
15
|
-
* @returns {RequestHandler} - Request handler.
|
|
16
|
-
*/
|
|
17
|
-
export const handlerUpload = (uploadFileSizeLimit = Infinity) => {
|
|
18
|
-
const upload = multer({
|
|
19
|
-
storage: multer.diskStorage({}),
|
|
20
|
-
limits: {
|
|
21
|
-
fileSize: uploadFileSizeLimit,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return upload.any();
|
|
26
|
-
};
|
package/src/handler/plsql/cgi.js
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Prepare the CGI information
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import os from 'node:os';
|
|
6
|
-
import {URL} from 'node:url';
|
|
7
|
-
import debugModule from 'debug';
|
|
8
|
-
const debug = debugModule('webplsql:cgi');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {import('express').Request} Request
|
|
12
|
-
* @typedef {import('../../types.js').environmentType} environmentType
|
|
13
|
-
* @typedef {import('../../types.js').configPlSqlType} configPlSqlType
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/** @type {environmentType} */
|
|
17
|
-
const DEFAULT_CGI = {
|
|
18
|
-
PLSQL_GATEWAY: 'WebDb',
|
|
19
|
-
GATEWAY_IVERSION: '2',
|
|
20
|
-
SERVER_SOFTWARE: 'web_plsql',
|
|
21
|
-
GATEWAY_INTERFACE: 'CGI/1.1',
|
|
22
|
-
SERVER_PORT: '',
|
|
23
|
-
SERVER_NAME: os.hostname(),
|
|
24
|
-
REQUEST_METHOD: '',
|
|
25
|
-
PATH_INFO: '',
|
|
26
|
-
SCRIPT_NAME: '',
|
|
27
|
-
REMOTE_ADDR: '',
|
|
28
|
-
SERVER_PROTOCOL: '',
|
|
29
|
-
REQUEST_PROTOCOL: '',
|
|
30
|
-
REMOTE_USER: '',
|
|
31
|
-
HTTP_COOKIE: '',
|
|
32
|
-
HTTP_USER_AGENT: '',
|
|
33
|
-
HTTP_HOST: '',
|
|
34
|
-
HTTP_ACCEPT: '',
|
|
35
|
-
HTTP_ACCEPT_ENCODING: '',
|
|
36
|
-
HTTP_ACCEPT_LANGUAGE: '',
|
|
37
|
-
HTTP_REFERER: '',
|
|
38
|
-
HTTP_X_FORWARDED_FOR: '',
|
|
39
|
-
WEB_AUTHENT_PREFIX: '',
|
|
40
|
-
DAD_NAME: '',
|
|
41
|
-
DOC_ACCESS_PATH: 'doc',
|
|
42
|
-
DOCUMENT_TABLE: '',
|
|
43
|
-
PATH_ALIAS: '',
|
|
44
|
-
REQUEST_CHARSET: 'UTF8',
|
|
45
|
-
REQUEST_IANA_CHARSET: 'UTF-8',
|
|
46
|
-
SCRIPT_PREFIX: '',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Create the HTTP_COOKIE string
|
|
51
|
-
*
|
|
52
|
-
* @param {Request} req - The req object represents the HTTP request.
|
|
53
|
-
* @returns {string} - The string representation of the cookies.
|
|
54
|
-
*/
|
|
55
|
-
const getCookieString = (req) => {
|
|
56
|
-
let cookieString = '';
|
|
57
|
-
|
|
58
|
-
for (const propName in req.cookies) {
|
|
59
|
-
cookieString += `${propName}=${req.cookies[propName]};`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
debug('getCookieString', req.cookies, cookieString);
|
|
63
|
-
|
|
64
|
-
return cookieString;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get the script name and DAD name from a URL
|
|
69
|
-
*
|
|
70
|
-
* @param {Request} req - The req object represents the HTTP request.
|
|
71
|
-
* @returns {{script: string; prefix: string; dad: string}} - The DAD structure.
|
|
72
|
-
*/
|
|
73
|
-
const getPath = (req) => {
|
|
74
|
-
// create a valid url
|
|
75
|
-
const validUrl = `${req.protocol}://${os.hostname()}${req.originalUrl}`;
|
|
76
|
-
|
|
77
|
-
// get the pathname from the url (new URL('https://example.org/abc/xyz?123').pathname => /abc/xyz)
|
|
78
|
-
const pathname = new URL(validUrl).pathname;
|
|
79
|
-
|
|
80
|
-
const tmp = trimPath(pathname.substring(0, pathname.lastIndexOf('/') + 1));
|
|
81
|
-
const script = `/${tmp}`;
|
|
82
|
-
const prefix = `/${tmp.substring(0, tmp.lastIndexOf('/'))}`;
|
|
83
|
-
const dad = tmp.substring(tmp.indexOf('/') + 1);
|
|
84
|
-
|
|
85
|
-
return {script, prefix, dad};
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get a path that is not enclodes in slashes
|
|
90
|
-
*
|
|
91
|
-
* @param {string} value - The value to trim.
|
|
92
|
-
* @returns {string} - The trimmed value.
|
|
93
|
-
*/
|
|
94
|
-
const trimPath = (value) => value.replace(/^\/+|\/+$/g, '');
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Create a CGI object
|
|
98
|
-
*
|
|
99
|
-
* @param {Request} req - The req object represents the HTTP request.
|
|
100
|
-
* @param {string} doctable - The document table.
|
|
101
|
-
* @param {environmentType} cgi - The additional cgi.
|
|
102
|
-
* @returns {environmentType} CGI object
|
|
103
|
-
*/
|
|
104
|
-
export const getCGI = (req, doctable, cgi) => {
|
|
105
|
-
const PROTOCOL = req.protocol ? req.protocol.toUpperCase() : '';
|
|
106
|
-
const PATH = getPath(req);
|
|
107
|
-
|
|
108
|
-
/** @type {environmentType} */
|
|
109
|
-
const CGI = {
|
|
110
|
-
SERVER_PORT: typeof req.socket.localPort === 'number' ? req.socket.localPort.toString() : '',
|
|
111
|
-
REQUEST_METHOD: req.method,
|
|
112
|
-
PATH_INFO: Array.isArray(req.params.name) ? (req.params.name[0] ?? '') : (req.params.name ?? ''),
|
|
113
|
-
SCRIPT_NAME: PATH.script,
|
|
114
|
-
REMOTE_ADDR: (req.ip ?? '').replace('::ffff:', ''),
|
|
115
|
-
SERVER_PROTOCOL: `${PROTOCOL}/${req.httpVersion}`,
|
|
116
|
-
REQUEST_PROTOCOL: PROTOCOL,
|
|
117
|
-
HTTP_COOKIE: getCookieString(req),
|
|
118
|
-
HTTP_USER_AGENT: req.get('user-agent') ?? '',
|
|
119
|
-
HTTP_HOST: req.get('host') ?? '',
|
|
120
|
-
HTTP_ACCEPT: req.get('accept') ?? '',
|
|
121
|
-
HTTP_ACCEPT_ENCODING: req.get('accept-encoding') ?? '',
|
|
122
|
-
HTTP_ACCEPT_LANGUAGE: req.get('accept-language') ?? '',
|
|
123
|
-
HTTP_REFERER: req.get('referer') ?? '',
|
|
124
|
-
HTTP_X_FORWARDED_FOR: req.get('x-forwarded-for') ?? '',
|
|
125
|
-
DAD_NAME: PATH.dad,
|
|
126
|
-
DOCUMENT_TABLE: doctable,
|
|
127
|
-
SCRIPT_PREFIX: PATH.prefix,
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
return Object.assign({}, DEFAULT_CGI, CGI, cgi);
|
|
131
|
-
};
|