qhttpx 1.8.12 → 1.9.1
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/package.json +26 -4
- package/prebuilds/darwin-arm64/qhttpx.node +0 -0
- package/prebuilds/linux-x64/qhttpx.node +0 -0
- package/prebuilds/win32-x64/qhttpx.node +0 -0
- package/src/native/index.ts +104 -24
- package/src/native/picohttpparser.h +5 -0
- package/src/native/server.cc +2 -0
- package/dist/examples/api-server.d.ts +0 -1
- package/dist/examples/api-server.js +0 -77
- package/dist/examples/basic.d.ts +0 -1
- package/dist/examples/basic.js +0 -10
- package/dist/examples/compression.d.ts +0 -1
- package/dist/examples/compression.js +0 -17
- package/dist/examples/cors.d.ts +0 -1
- package/dist/examples/cors.js +0 -19
- package/dist/examples/errors.d.ts +0 -1
- package/dist/examples/errors.js +0 -25
- package/dist/examples/file-upload.d.ts +0 -1
- package/dist/examples/file-upload.js +0 -24
- package/dist/examples/fusion.d.ts +0 -1
- package/dist/examples/fusion.js +0 -21
- package/dist/examples/rate-limiting.d.ts +0 -1
- package/dist/examples/rate-limiting.js +0 -17
- package/dist/examples/validation.d.ts +0 -1
- package/dist/examples/validation.js +0 -23
- package/dist/examples/websockets.d.ts +0 -1
- package/dist/examples/websockets.js +0 -20
- package/dist/package.json +0 -101
- package/dist/src/benchmarks/quantam-users.d.ts +0 -1
- package/dist/src/benchmarks/quantam-users.js +0 -56
- package/dist/src/benchmarks/simple-json.d.ts +0 -1
- package/dist/src/benchmarks/simple-json.js +0 -60
- package/dist/src/benchmarks/ultra-mode.d.ts +0 -1
- package/dist/src/benchmarks/ultra-mode.js +0 -94
- package/dist/src/cli/index.d.ts +0 -2
- package/dist/src/cli/index.js +0 -222
- package/dist/src/client/index.d.ts +0 -17
- package/dist/src/client/index.js +0 -72
- package/dist/src/core/batch.d.ts +0 -24
- package/dist/src/core/batch.js +0 -97
- package/dist/src/core/body-parser.d.ts +0 -15
- package/dist/src/core/body-parser.js +0 -121
- package/dist/src/core/buffer-pool.d.ts +0 -41
- package/dist/src/core/buffer-pool.js +0 -70
- package/dist/src/core/config.d.ts +0 -7
- package/dist/src/core/config.js +0 -50
- package/dist/src/core/errors.d.ts +0 -34
- package/dist/src/core/errors.js +0 -70
- package/dist/src/core/fusion.d.ts +0 -14
- package/dist/src/core/fusion.js +0 -183
- package/dist/src/core/logger.d.ts +0 -22
- package/dist/src/core/logger.js +0 -49
- package/dist/src/core/metrics.d.ts +0 -45
- package/dist/src/core/metrics.js +0 -111
- package/dist/src/core/native-adapter.d.ts +0 -11
- package/dist/src/core/native-adapter.js +0 -211
- package/dist/src/core/resources.d.ts +0 -9
- package/dist/src/core/resources.js +0 -25
- package/dist/src/core/scheduler.d.ts +0 -34
- package/dist/src/core/scheduler.js +0 -85
- package/dist/src/core/scope.d.ts +0 -26
- package/dist/src/core/scope.js +0 -68
- package/dist/src/core/serializer.d.ts +0 -10
- package/dist/src/core/serializer.js +0 -44
- package/dist/src/core/server.d.ts +0 -138
- package/dist/src/core/server.js +0 -1082
- package/dist/src/core/stream.d.ts +0 -15
- package/dist/src/core/stream.js +0 -71
- package/dist/src/core/tasks.d.ts +0 -29
- package/dist/src/core/tasks.js +0 -87
- package/dist/src/core/types.d.ts +0 -173
- package/dist/src/core/types.js +0 -19
- package/dist/src/core/websocket.d.ts +0 -25
- package/dist/src/core/websocket.js +0 -86
- package/dist/src/core/worker-queue.d.ts +0 -41
- package/dist/src/core/worker-queue.js +0 -73
- package/dist/src/database/adapters/memory.d.ts +0 -21
- package/dist/src/database/adapters/memory.js +0 -90
- package/dist/src/database/adapters/mongo.d.ts +0 -11
- package/dist/src/database/adapters/mongo.js +0 -141
- package/dist/src/database/adapters/postgres.d.ts +0 -10
- package/dist/src/database/adapters/postgres.js +0 -111
- package/dist/src/database/adapters/sqlite.d.ts +0 -10
- package/dist/src/database/adapters/sqlite.js +0 -42
- package/dist/src/database/coalescer.d.ts +0 -14
- package/dist/src/database/coalescer.js +0 -134
- package/dist/src/database/manager.d.ts +0 -35
- package/dist/src/database/manager.js +0 -87
- package/dist/src/database/types.d.ts +0 -20
- package/dist/src/database/types.js +0 -2
- package/dist/src/index.d.ts +0 -50
- package/dist/src/index.js +0 -91
- package/dist/src/middleware/compression.d.ts +0 -2
- package/dist/src/middleware/compression.js +0 -133
- package/dist/src/middleware/cors.d.ts +0 -2
- package/dist/src/middleware/cors.js +0 -66
- package/dist/src/middleware/presets.d.ts +0 -16
- package/dist/src/middleware/presets.js +0 -52
- package/dist/src/middleware/rate-limit.d.ts +0 -14
- package/dist/src/middleware/rate-limit.js +0 -83
- package/dist/src/middleware/security.d.ts +0 -21
- package/dist/src/middleware/security.js +0 -69
- package/dist/src/middleware/static.d.ts +0 -11
- package/dist/src/middleware/static.js +0 -191
- package/dist/src/native/index.d.ts +0 -29
- package/dist/src/native/index.js +0 -64
- package/dist/src/openapi/generator.d.ts +0 -19
- package/dist/src/openapi/generator.js +0 -149
- package/dist/src/router/radix-router.d.ts +0 -18
- package/dist/src/router/radix-router.js +0 -89
- package/dist/src/router/radix-tree.d.ts +0 -18
- package/dist/src/router/radix-tree.js +0 -131
- package/dist/src/router/router.d.ts +0 -34
- package/dist/src/router/router.js +0 -186
- package/dist/src/testing/index.d.ts +0 -25
- package/dist/src/testing/index.js +0 -84
- package/dist/src/utils/cookies.d.ts +0 -3
- package/dist/src/utils/cookies.js +0 -59
- package/dist/src/utils/logger.d.ts +0 -12
- package/dist/src/utils/logger.js +0 -45
- package/dist/src/utils/signals.d.ts +0 -6
- package/dist/src/utils/signals.js +0 -31
- package/dist/src/utils/sse.d.ts +0 -6
- package/dist/src/utils/sse.js +0 -32
- package/dist/src/validation/index.d.ts +0 -3
- package/dist/src/validation/index.js +0 -19
- package/dist/src/validation/simple.d.ts +0 -5
- package/dist/src/validation/simple.js +0 -102
- package/dist/src/validation/types.d.ts +0 -32
- package/dist/src/validation/types.js +0 -12
- package/dist/src/validation/zod.d.ts +0 -4
- package/dist/src/validation/zod.js +0 -18
- package/dist/src/views/index.d.ts +0 -1
- package/dist/src/views/index.js +0 -17
- package/dist/src/views/types.d.ts +0 -3
- package/dist/src/views/types.js +0 -2
- package/dist/tests/adapters.test.d.ts +0 -1
- package/dist/tests/adapters.test.js +0 -106
- package/dist/tests/batch.test.d.ts +0 -1
- package/dist/tests/batch.test.js +0 -117
- package/dist/tests/body-parser.test.d.ts +0 -1
- package/dist/tests/body-parser.test.js +0 -52
- package/dist/tests/compression-sse.test.d.ts +0 -1
- package/dist/tests/compression-sse.test.js +0 -87
- package/dist/tests/cookies.test.d.ts +0 -1
- package/dist/tests/cookies.test.js +0 -63
- package/dist/tests/cors.test.d.ts +0 -1
- package/dist/tests/cors.test.js +0 -55
- package/dist/tests/database.test.d.ts +0 -1
- package/dist/tests/database.test.js +0 -80
- package/dist/tests/dx.test.d.ts +0 -1
- package/dist/tests/dx.test.js +0 -114
- package/dist/tests/ecosystem.test.d.ts +0 -1
- package/dist/tests/ecosystem.test.js +0 -133
- package/dist/tests/features.test.d.ts +0 -1
- package/dist/tests/features.test.js +0 -47
- package/dist/tests/fusion.test.d.ts +0 -1
- package/dist/tests/fusion.test.js +0 -92
- package/dist/tests/http-basic.test.d.ts +0 -1
- package/dist/tests/http-basic.test.js +0 -124
- package/dist/tests/logger.test.d.ts +0 -1
- package/dist/tests/logger.test.js +0 -33
- package/dist/tests/middleware.test.d.ts +0 -1
- package/dist/tests/middleware.test.js +0 -109
- package/dist/tests/native-adapter.test.d.ts +0 -1
- package/dist/tests/native-adapter.test.js +0 -71
- package/dist/tests/observability.test.d.ts +0 -1
- package/dist/tests/observability.test.js +0 -59
- package/dist/tests/openapi.test.d.ts +0 -1
- package/dist/tests/openapi.test.js +0 -64
- package/dist/tests/plugin.test.d.ts +0 -1
- package/dist/tests/plugin.test.js +0 -65
- package/dist/tests/plugins.test.d.ts +0 -1
- package/dist/tests/plugins.test.js +0 -71
- package/dist/tests/rate-limit.test.d.ts +0 -1
- package/dist/tests/rate-limit.test.js +0 -77
- package/dist/tests/resources.test.d.ts +0 -1
- package/dist/tests/resources.test.js +0 -47
- package/dist/tests/scheduler.test.d.ts +0 -1
- package/dist/tests/scheduler.test.js +0 -46
- package/dist/tests/schema-routes.test.d.ts +0 -1
- package/dist/tests/schema-routes.test.js +0 -77
- package/dist/tests/security.test.d.ts +0 -1
- package/dist/tests/security.test.js +0 -83
- package/dist/tests/server-db.test.d.ts +0 -1
- package/dist/tests/server-db.test.js +0 -72
- package/dist/tests/smoke.test.d.ts +0 -1
- package/dist/tests/smoke.test.js +0 -10
- package/dist/tests/sqlite-fusion.test.d.ts +0 -1
- package/dist/tests/sqlite-fusion.test.js +0 -92
- package/dist/tests/static.test.d.ts +0 -1
- package/dist/tests/static.test.js +0 -102
- package/dist/tests/stream.test.d.ts +0 -1
- package/dist/tests/stream.test.js +0 -44
- package/dist/tests/task-metrics.test.d.ts +0 -1
- package/dist/tests/task-metrics.test.js +0 -53
- package/dist/tests/tasks.test.d.ts +0 -1
- package/dist/tests/tasks.test.js +0 -62
- package/dist/tests/testing.test.d.ts +0 -1
- package/dist/tests/testing.test.js +0 -47
- package/dist/tests/validation.test.d.ts +0 -1
- package/dist/tests/validation.test.js +0 -107
- package/dist/tests/websocket.test.d.ts +0 -1
- package/dist/tests/websocket.test.js +0 -146
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -9
package/dist/src/core/server.js
DELETED
|
@@ -1,1082 +0,0 @@
|
|
|
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.QHTTPX = exports.QHTTPXContextImpl = void 0;
|
|
7
|
-
const http_1 = __importDefault(require("http"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const url_1 = require("url");
|
|
10
|
-
const querystring_1 = require("querystring");
|
|
11
|
-
const package_json_1 = __importDefault(require("../../package.json"));
|
|
12
|
-
const router_1 = require("../router/router");
|
|
13
|
-
const scheduler_1 = require("./scheduler");
|
|
14
|
-
const tasks_1 = require("./tasks");
|
|
15
|
-
const resources_1 = require("./resources");
|
|
16
|
-
const metrics_1 = require("./metrics");
|
|
17
|
-
const body_parser_1 = require("./body-parser");
|
|
18
|
-
const buffer_pool_1 = require("./buffer-pool");
|
|
19
|
-
const websocket_1 = require("./websocket");
|
|
20
|
-
const serializer_1 = require("./serializer");
|
|
21
|
-
const batch_1 = require("./batch");
|
|
22
|
-
const fusion_1 = require("./fusion");
|
|
23
|
-
const simple_1 = require("../validation/simple");
|
|
24
|
-
const generator_1 = require("../openapi/generator");
|
|
25
|
-
const types_1 = require("./types");
|
|
26
|
-
const cookies_1 = require("../utils/cookies");
|
|
27
|
-
const scope_1 = require("./scope");
|
|
28
|
-
const logger_1 = require("./logger");
|
|
29
|
-
const EMPTY_QUERY = Object.freeze({});
|
|
30
|
-
class QHTTPXContextImpl {
|
|
31
|
-
constructor(app) {
|
|
32
|
-
this._url = null;
|
|
33
|
-
this._app = app;
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
-
this._appJsonSerializer = app.options.jsonSerializer;
|
|
36
|
-
this.json = this.json.bind(this);
|
|
37
|
-
this.send = this.send.bind(this);
|
|
38
|
-
this.html = this.html.bind(this);
|
|
39
|
-
this.redirect = this.redirect.bind(this);
|
|
40
|
-
this.setCookie = this.setCookie.bind(this);
|
|
41
|
-
this.render = this.render.bind(this);
|
|
42
|
-
this.validate = this.validate.bind(this);
|
|
43
|
-
}
|
|
44
|
-
get bufferPool() {
|
|
45
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
-
return this._app.bufferPool;
|
|
47
|
-
}
|
|
48
|
-
get db() {
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
-
return this._app.options.database;
|
|
51
|
-
}
|
|
52
|
-
get state() {
|
|
53
|
-
if (!this._state) {
|
|
54
|
-
this._state = {};
|
|
55
|
-
}
|
|
56
|
-
return this._state;
|
|
57
|
-
}
|
|
58
|
-
set state(v) {
|
|
59
|
-
this._state = v;
|
|
60
|
-
}
|
|
61
|
-
get ip() {
|
|
62
|
-
if (this._ip)
|
|
63
|
-
return this._ip;
|
|
64
|
-
this._ip = this.req.socket.remoteAddress || '';
|
|
65
|
-
return this._ip;
|
|
66
|
-
}
|
|
67
|
-
// Setter for manual override if needed (though usually read-only from socket)
|
|
68
|
-
set ip(v) {
|
|
69
|
-
this._ip = v;
|
|
70
|
-
}
|
|
71
|
-
get url() {
|
|
72
|
-
if (this._url)
|
|
73
|
-
return this._url;
|
|
74
|
-
const host = this.headers.host || 'localhost';
|
|
75
|
-
this._url = new url_1.URL(this.req.url || '/', `http://${host}`);
|
|
76
|
-
return this._url;
|
|
77
|
-
}
|
|
78
|
-
set url(v) {
|
|
79
|
-
this._url = v;
|
|
80
|
-
}
|
|
81
|
-
json(payload, status = 200) {
|
|
82
|
-
const res = this.res;
|
|
83
|
-
let body;
|
|
84
|
-
if (this.serializer) {
|
|
85
|
-
body = this.serializer(payload);
|
|
86
|
-
}
|
|
87
|
-
else if (this._appJsonSerializer) {
|
|
88
|
-
body = this._appJsonSerializer(payload);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
// Native JSON.stringify is faster for schema-less objects
|
|
92
|
-
body = JSON.stringify(payload);
|
|
93
|
-
}
|
|
94
|
-
if (!res.headersSent) {
|
|
95
|
-
res.statusCode = status;
|
|
96
|
-
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
97
|
-
}
|
|
98
|
-
res.end(body);
|
|
99
|
-
}
|
|
100
|
-
send(payload, status = 200) {
|
|
101
|
-
const res = this.res;
|
|
102
|
-
if (!res.headersSent) {
|
|
103
|
-
res.statusCode = status;
|
|
104
|
-
}
|
|
105
|
-
res.end(payload);
|
|
106
|
-
}
|
|
107
|
-
html(payload, status = 200) {
|
|
108
|
-
const res = this.res;
|
|
109
|
-
if (!res.headersSent) {
|
|
110
|
-
res.statusCode = status;
|
|
111
|
-
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
112
|
-
}
|
|
113
|
-
res.end(payload);
|
|
114
|
-
}
|
|
115
|
-
redirect(url, status = 302) {
|
|
116
|
-
const res = this.res;
|
|
117
|
-
if (!res.headersSent) {
|
|
118
|
-
res.statusCode = status;
|
|
119
|
-
res.setHeader('Location', url);
|
|
120
|
-
}
|
|
121
|
-
res.end();
|
|
122
|
-
}
|
|
123
|
-
setCookie(name, value, options) {
|
|
124
|
-
const res = this.res;
|
|
125
|
-
const serialized = (0, cookies_1.serializeCookie)(name, value, options);
|
|
126
|
-
let existing = res.getHeader('Set-Cookie');
|
|
127
|
-
if (Array.isArray(existing)) {
|
|
128
|
-
existing.push(serialized);
|
|
129
|
-
res.setHeader('Set-Cookie', existing);
|
|
130
|
-
}
|
|
131
|
-
else if (existing) {
|
|
132
|
-
res.setHeader('Set-Cookie', [existing, serialized]);
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
res.setHeader('Set-Cookie', serialized);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
async render(view, locals) {
|
|
139
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
|
-
const engine = this._app.options.viewEngine;
|
|
141
|
-
if (!engine) {
|
|
142
|
-
throw new Error('No view engine registered');
|
|
143
|
-
}
|
|
144
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
-
const viewsPath = this._app.options.viewsPath || process.cwd();
|
|
146
|
-
const fullPath = path_1.default.resolve(viewsPath, view);
|
|
147
|
-
const html = await engine.render(fullPath, locals || {});
|
|
148
|
-
const res = this.res;
|
|
149
|
-
if (!res.headersSent) {
|
|
150
|
-
res.statusCode = 200;
|
|
151
|
-
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
152
|
-
}
|
|
153
|
-
res.end(html);
|
|
154
|
-
}
|
|
155
|
-
async validate(schema, data) {
|
|
156
|
-
const target = data ?? this.body;
|
|
157
|
-
const result = await this._app.validator.validate(schema, target);
|
|
158
|
-
if (result.success) {
|
|
159
|
-
return result.data;
|
|
160
|
-
}
|
|
161
|
-
throw new types_1.HttpError(400, 'Validation Error', {
|
|
162
|
-
code: 'VALIDATION_ERROR',
|
|
163
|
-
details: result.error,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
reset() {
|
|
167
|
-
this.req = null;
|
|
168
|
-
this.res = null;
|
|
169
|
-
this.headers = null;
|
|
170
|
-
this._url = null;
|
|
171
|
-
this.method = null;
|
|
172
|
-
this._ip = undefined;
|
|
173
|
-
this.params = null;
|
|
174
|
-
this.query = null;
|
|
175
|
-
this.body = undefined;
|
|
176
|
-
this.files = undefined;
|
|
177
|
-
this.cookies = null;
|
|
178
|
-
this._state = undefined;
|
|
179
|
-
this.requestId = undefined;
|
|
180
|
-
this.requestStart = undefined;
|
|
181
|
-
this.serializer = undefined;
|
|
182
|
-
this.path = '';
|
|
183
|
-
this.disableAutoEnd = false;
|
|
184
|
-
this.error = undefined;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
exports.QHTTPXContextImpl = QHTTPXContextImpl;
|
|
188
|
-
class QHTTPX {
|
|
189
|
-
constructor(options = {}) {
|
|
190
|
-
this.middlewares = [];
|
|
191
|
-
this.contextPool = [];
|
|
192
|
-
this.pipelineRunner = null;
|
|
193
|
-
this.onStartHooks = [];
|
|
194
|
-
this.onBeforeShutdownHooks = [];
|
|
195
|
-
this.onShutdownHooks = [];
|
|
196
|
-
this.nextRequestId = 1;
|
|
197
|
-
this.requestCounter = 0;
|
|
198
|
-
this.options = options;
|
|
199
|
-
this.logger = new logger_1.Logger({
|
|
200
|
-
name: options.name,
|
|
201
|
-
level: options.performanceMode === 'ultra' ? 'error' : 'info'
|
|
202
|
-
});
|
|
203
|
-
this.ultraMode = options.performanceMode === 'ultra';
|
|
204
|
-
this.errorHandler = this.ultraMode ? undefined : options.errorHandler;
|
|
205
|
-
this.notFoundHandler = this.ultraMode ? undefined : options.notFoundHandler;
|
|
206
|
-
this.methodNotAllowedHandler = this.ultraMode ? undefined : options.methodNotAllowedHandler;
|
|
207
|
-
this.tracer = this.ultraMode ? undefined : options.tracer;
|
|
208
|
-
this.workerCount = (0, resources_1.calculateWorkerCount)(options.workers ?? 'auto');
|
|
209
|
-
this.router = new router_1.Router();
|
|
210
|
-
this.wsManager = new websocket_1.WebSocketManager(this.generateRequestId.bind(this));
|
|
211
|
-
this.bufferPool = new buffer_pool_1.BufferPool(options.bufferPoolConfig);
|
|
212
|
-
const maxConcurrency = options.maxConcurrency ?? 1024;
|
|
213
|
-
this.poolLimit = maxConcurrency * 2;
|
|
214
|
-
this.scheduler = new scheduler_1.Scheduler({
|
|
215
|
-
maxConcurrency,
|
|
216
|
-
});
|
|
217
|
-
this.tasks = new tasks_1.TaskEngine(this.scheduler);
|
|
218
|
-
if (options.enableBatching) {
|
|
219
|
-
this.batchExecutor = new batch_1.BatchExecutor(options.database);
|
|
220
|
-
}
|
|
221
|
-
if (options.enableRequestFusion) {
|
|
222
|
-
this.fusion = new fusion_1.RequestFusion(options.enableRequestFusion);
|
|
223
|
-
}
|
|
224
|
-
this.validator = options.validator ?? new simple_1.SimpleValidator();
|
|
225
|
-
this.metrics = new metrics_1.Metrics(this.scheduler, {
|
|
226
|
-
enabled: !this.ultraMode && (this.options.metricsEnabled ?? true),
|
|
227
|
-
}, this.tasks);
|
|
228
|
-
for (let i = 0; i < maxConcurrency; i += 1) {
|
|
229
|
-
this.contextPool.push(this.createContext());
|
|
230
|
-
}
|
|
231
|
-
this.registerInternalRoutes();
|
|
232
|
-
this.compileMiddlewarePipeline();
|
|
233
|
-
this.server = http_1.default.createServer(this.handleRequest.bind(this));
|
|
234
|
-
if (this.options.keepAliveTimeoutMs !== undefined) {
|
|
235
|
-
this.server.keepAliveTimeout = this.options.keepAliveTimeoutMs;
|
|
236
|
-
}
|
|
237
|
-
else if (this.ultraMode) {
|
|
238
|
-
this.server.keepAliveTimeout = 0;
|
|
239
|
-
}
|
|
240
|
-
if (this.options.headersTimeoutMs !== undefined) {
|
|
241
|
-
this.server.headersTimeout = this.options.headersTimeoutMs;
|
|
242
|
-
}
|
|
243
|
-
else if (this.ultraMode) {
|
|
244
|
-
this.server.headersTimeout = 0;
|
|
245
|
-
}
|
|
246
|
-
if (this.ultraMode) {
|
|
247
|
-
this.server.requestTimeout = 0;
|
|
248
|
-
this.server.maxHeadersCount = 0;
|
|
249
|
-
this.server.timeout = 0;
|
|
250
|
-
}
|
|
251
|
-
this.server.on('upgrade', (req, socket, head) => {
|
|
252
|
-
void this.handleUpgrade(req, socket, head);
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
get serverInstance() {
|
|
256
|
-
return this.server;
|
|
257
|
-
}
|
|
258
|
-
get websocket() {
|
|
259
|
-
return this.wsManager;
|
|
260
|
-
}
|
|
261
|
-
setErrorHandler(handler) {
|
|
262
|
-
this.errorHandler = handler;
|
|
263
|
-
}
|
|
264
|
-
setNotFoundHandler(handler) {
|
|
265
|
-
this.notFoundHandler = handler;
|
|
266
|
-
}
|
|
267
|
-
setMethodNotAllowedHandler(handler) {
|
|
268
|
-
this.methodNotAllowedHandler = handler;
|
|
269
|
-
}
|
|
270
|
-
set404Handler(handler) {
|
|
271
|
-
this.setNotFoundHandler(handler);
|
|
272
|
-
}
|
|
273
|
-
set405Handler(handler) {
|
|
274
|
-
this.setMethodNotAllowedHandler(handler);
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Alias for setErrorHandler
|
|
278
|
-
*/
|
|
279
|
-
onError(handler) {
|
|
280
|
-
this.setErrorHandler(handler);
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Alias for setNotFoundHandler
|
|
284
|
-
*/
|
|
285
|
-
notFound(handler) {
|
|
286
|
-
this.setNotFoundHandler(handler);
|
|
287
|
-
}
|
|
288
|
-
onStart(hook) {
|
|
289
|
-
this.onStartHooks.push(hook);
|
|
290
|
-
}
|
|
291
|
-
onBeforeShutdown(hook) {
|
|
292
|
-
this.onBeforeShutdownHooks.push(hook);
|
|
293
|
-
}
|
|
294
|
-
onShutdown(hook) {
|
|
295
|
-
this.onShutdownHooks.push(hook);
|
|
296
|
-
}
|
|
297
|
-
upgrade(path, handler) {
|
|
298
|
-
this.wsManager.register(path, handler);
|
|
299
|
-
}
|
|
300
|
-
use(middleware) {
|
|
301
|
-
this.middlewares.push(middleware);
|
|
302
|
-
// Recompile pipeline after middleware is added
|
|
303
|
-
this.compileMiddlewarePipeline();
|
|
304
|
-
}
|
|
305
|
-
compileMiddlewarePipeline() {
|
|
306
|
-
const middlewares = this.middlewares;
|
|
307
|
-
if (middlewares.length === 0) {
|
|
308
|
-
this.pipelineRunner = async (ctx, handler) => {
|
|
309
|
-
const result = handler(ctx);
|
|
310
|
-
if (result && typeof result.then === 'function') {
|
|
311
|
-
await result;
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
// Flatten middleware pipeline into a single function chain
|
|
317
|
-
// This eliminates Promise nesting, recursive dispatch overhead, and microtask backlogs
|
|
318
|
-
// Each middleware executes directly without closure allocation overhead
|
|
319
|
-
this.pipelineRunner = async (ctx, handler) => {
|
|
320
|
-
let index = 0;
|
|
321
|
-
const executeNext = async () => {
|
|
322
|
-
if (index >= middlewares.length) {
|
|
323
|
-
// All middlewares done, execute handler
|
|
324
|
-
const result = handler(ctx);
|
|
325
|
-
if (result && typeof result.then === 'function') {
|
|
326
|
-
await result;
|
|
327
|
-
}
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
const currentIndex = index;
|
|
331
|
-
index += 1;
|
|
332
|
-
ctx.next = executeNext;
|
|
333
|
-
const result = middlewares[currentIndex](ctx, executeNext);
|
|
334
|
-
if (result && typeof result.then === 'function') {
|
|
335
|
-
await result;
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
await executeNext();
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
compileRoutePipeline(handler,
|
|
342
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
343
|
-
schema) {
|
|
344
|
-
const middlewares = this.middlewares;
|
|
345
|
-
// Heuristic: Is it a structured RouteSchema or a legacy ResponseSchema?
|
|
346
|
-
let responseSchema;
|
|
347
|
-
let requestSchema;
|
|
348
|
-
if (schema) {
|
|
349
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
350
|
-
const s = schema;
|
|
351
|
-
if (s.body || s.query || s.params || s.headers || s.response) {
|
|
352
|
-
// It is a RouteSchema
|
|
353
|
-
requestSchema = s;
|
|
354
|
-
responseSchema = s.response;
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
// It is a legacy ResponseSchema
|
|
358
|
-
responseSchema = s;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const stringifier = responseSchema ? (0, serializer_1.getStringifier)(responseSchema) : undefined;
|
|
362
|
-
// Build middleware chain
|
|
363
|
-
let pipeline = handler;
|
|
364
|
-
// Wrap with validation if needed (before handler, after global middleware)
|
|
365
|
-
if (requestSchema) {
|
|
366
|
-
const inner = pipeline;
|
|
367
|
-
pipeline = async (ctx) => {
|
|
368
|
-
// Validate Body
|
|
369
|
-
if (requestSchema.body) {
|
|
370
|
-
const result = await this.validator.validate(requestSchema.body, ctx.body);
|
|
371
|
-
if (!result.success) {
|
|
372
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
373
|
-
const err = result.error;
|
|
374
|
-
ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
ctx.body = result.data;
|
|
378
|
-
}
|
|
379
|
-
// Validate Query
|
|
380
|
-
if (requestSchema.query) {
|
|
381
|
-
const result = await this.validator.validate(requestSchema.query, ctx.query);
|
|
382
|
-
if (!result.success) {
|
|
383
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
384
|
-
const err = result.error;
|
|
385
|
-
ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
389
|
-
ctx.query = result.data;
|
|
390
|
-
}
|
|
391
|
-
// Validate Params
|
|
392
|
-
if (requestSchema.params) {
|
|
393
|
-
const result = await this.validator.validate(requestSchema.params, ctx.params);
|
|
394
|
-
if (!result.success) {
|
|
395
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
396
|
-
const err = result.error;
|
|
397
|
-
ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
401
|
-
ctx.params = result.data;
|
|
402
|
-
}
|
|
403
|
-
return inner(ctx);
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
// Wrap with Request Fusion if enabled
|
|
407
|
-
if (this.fusion) {
|
|
408
|
-
const inner = pipeline;
|
|
409
|
-
pipeline = (ctx) => this.fusion.coalesce(ctx, inner);
|
|
410
|
-
}
|
|
411
|
-
// We iterate backwards to wrap the handler
|
|
412
|
-
// pipeline = m[last](ctx, () => pipeline(ctx))
|
|
413
|
-
for (let i = middlewares.length - 1; i >= 0; i -= 1) {
|
|
414
|
-
const middleware = middlewares[i];
|
|
415
|
-
const next = pipeline;
|
|
416
|
-
pipeline = (ctx) => {
|
|
417
|
-
const nextFn = async () => {
|
|
418
|
-
const result = next(ctx);
|
|
419
|
-
if (result && typeof result.then === 'function') {
|
|
420
|
-
await result;
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
// Attach next to ctx for destructuring support
|
|
424
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
425
|
-
ctx.next = nextFn;
|
|
426
|
-
return middleware(ctx, nextFn);
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
if (stringifier) {
|
|
430
|
-
const inner = pipeline;
|
|
431
|
-
return (ctx) => {
|
|
432
|
-
ctx.serializer = stringifier;
|
|
433
|
-
return inner(ctx);
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
return pipeline;
|
|
437
|
-
}
|
|
438
|
-
// Internal registration to be accessed by Scopes
|
|
439
|
-
_registerRoute(method, path, handlerOrOptions) {
|
|
440
|
-
this.registerRoute(method, path, handlerOrOptions);
|
|
441
|
-
}
|
|
442
|
-
async register(plugin, options) {
|
|
443
|
-
const scope = new scope_1.QHTTPXScope(this, options?.prefix);
|
|
444
|
-
await plugin(scope, options);
|
|
445
|
-
}
|
|
446
|
-
registerRoute(method, path, handlerOrOptions, handlerIfOptions) {
|
|
447
|
-
let handler;
|
|
448
|
-
let schema;
|
|
449
|
-
const options = {};
|
|
450
|
-
if (typeof handlerOrOptions === 'function') {
|
|
451
|
-
handler = handlerOrOptions;
|
|
452
|
-
}
|
|
453
|
-
else if (handlerIfOptions) {
|
|
454
|
-
handler = handlerIfOptions;
|
|
455
|
-
schema = handlerOrOptions.schema;
|
|
456
|
-
options.priority = handlerOrOptions.priority;
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
const opts = handlerOrOptions;
|
|
460
|
-
handler = opts.handler;
|
|
461
|
-
schema = opts.schema;
|
|
462
|
-
options.priority = opts.priority;
|
|
463
|
-
}
|
|
464
|
-
const compiled = this.compileRoutePipeline(handler, schema);
|
|
465
|
-
this.router.register(method, path, compiled, { ...options, schema });
|
|
466
|
-
}
|
|
467
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
468
|
-
get(path, arg1, arg2) {
|
|
469
|
-
this.registerRoute('GET', path, arg1, arg2);
|
|
470
|
-
}
|
|
471
|
-
post(path, handlerOrOptions, handler) {
|
|
472
|
-
this.registerRoute('POST', path, handlerOrOptions, handler);
|
|
473
|
-
}
|
|
474
|
-
put(path, handlerOrOptions, handler) {
|
|
475
|
-
this.registerRoute('PUT', path, handlerOrOptions, handler);
|
|
476
|
-
}
|
|
477
|
-
delete(path, handlerOrOptions, handler) {
|
|
478
|
-
this.registerRoute('DELETE', path, handlerOrOptions, handler);
|
|
479
|
-
}
|
|
480
|
-
route(path) {
|
|
481
|
-
const register = (method, handler) => {
|
|
482
|
-
this.registerRoute(method, path, handler);
|
|
483
|
-
};
|
|
484
|
-
const builder = {
|
|
485
|
-
get(handler) {
|
|
486
|
-
register('GET', handler);
|
|
487
|
-
return this;
|
|
488
|
-
},
|
|
489
|
-
post(handler) {
|
|
490
|
-
register('POST', handler);
|
|
491
|
-
return this;
|
|
492
|
-
},
|
|
493
|
-
put(handler) {
|
|
494
|
-
register('PUT', handler);
|
|
495
|
-
return this;
|
|
496
|
-
},
|
|
497
|
-
delete(handler) {
|
|
498
|
-
register('DELETE', handler);
|
|
499
|
-
return this;
|
|
500
|
-
},
|
|
501
|
-
};
|
|
502
|
-
return builder;
|
|
503
|
-
}
|
|
504
|
-
task(name, handler, options) {
|
|
505
|
-
this.tasks.register(name, handler, options);
|
|
506
|
-
}
|
|
507
|
-
enqueue(name, payload) {
|
|
508
|
-
return this.tasks.enqueue(name, payload);
|
|
509
|
-
}
|
|
510
|
-
op(name, handler) {
|
|
511
|
-
if (!this.batchExecutor) {
|
|
512
|
-
// Auto-enable batch executor if op is called?
|
|
513
|
-
// Or throw?
|
|
514
|
-
// Better to throw or warn if not enabled.
|
|
515
|
-
// But for ease of use, let's create it if missing (but we miss DB context if done this way without options)
|
|
516
|
-
// The constructor handles options. If enableBatching is false, we shouldn't be here.
|
|
517
|
-
throw new Error('Batching is not enabled. Pass enableBatching: true to QHTTPX options.');
|
|
518
|
-
}
|
|
519
|
-
this.batchExecutor.register(name, handler);
|
|
520
|
-
}
|
|
521
|
-
registerInternalRoutes() {
|
|
522
|
-
this.router.register('GET', '/__qhttpx/health', (ctx) => {
|
|
523
|
-
const version = typeof package_json_1.default.version === 'string' ? package_json_1.default.version : '';
|
|
524
|
-
const name = typeof package_json_1.default.name === 'string' ? package_json_1.default.name : '';
|
|
525
|
-
ctx.json({
|
|
526
|
-
status: 'ok',
|
|
527
|
-
name,
|
|
528
|
-
version,
|
|
529
|
-
workers: this.workerCount,
|
|
530
|
-
});
|
|
531
|
-
});
|
|
532
|
-
this.router.register('GET', '/__qhttpx/metrics', (ctx) => {
|
|
533
|
-
const snapshot = this.metrics.snapshot();
|
|
534
|
-
ctx.json({
|
|
535
|
-
...snapshot,
|
|
536
|
-
workers: this.workerCount,
|
|
537
|
-
});
|
|
538
|
-
});
|
|
539
|
-
this.router.register('GET', '/__qhttpx/runtime', (ctx) => {
|
|
540
|
-
const schedulerStats = this.scheduler.getStats();
|
|
541
|
-
ctx.json({
|
|
542
|
-
workers: this.workerCount,
|
|
543
|
-
router: {
|
|
544
|
-
frozen: this.router.isFrozenRouter(),
|
|
545
|
-
},
|
|
546
|
-
scheduler: schedulerStats,
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
if (this.options.enableBatching && this.batchExecutor) {
|
|
550
|
-
const endpoint = typeof this.options.enableBatching === 'object'
|
|
551
|
-
? this.options.enableBatching.endpoint
|
|
552
|
-
: '/qhttpx';
|
|
553
|
-
this.router.register('POST', endpoint, async (ctx) => {
|
|
554
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
555
|
-
const body = ctx.body;
|
|
556
|
-
if (!body || !Array.isArray(body.batch)) {
|
|
557
|
-
ctx.json({ error: 'Invalid batch format' }, 400);
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
try {
|
|
561
|
-
const result = await this.batchExecutor.handleBatch(ctx, body.batch);
|
|
562
|
-
ctx.json(result);
|
|
563
|
-
}
|
|
564
|
-
catch (err) {
|
|
565
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
566
|
-
ctx.json({ error: err.message }, 500);
|
|
567
|
-
}
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
getOpenAPI(options) {
|
|
572
|
-
const generator = new generator_1.OpenAPIGenerator(this.router, options);
|
|
573
|
-
return generator.generate();
|
|
574
|
-
}
|
|
575
|
-
async listen(port, hostnameOrCallback, callback) {
|
|
576
|
-
let hostname;
|
|
577
|
-
let cb;
|
|
578
|
-
if (typeof hostnameOrCallback === 'function') {
|
|
579
|
-
cb = hostnameOrCallback;
|
|
580
|
-
hostname = undefined;
|
|
581
|
-
}
|
|
582
|
-
else {
|
|
583
|
-
hostname = hostnameOrCallback;
|
|
584
|
-
cb = callback;
|
|
585
|
-
}
|
|
586
|
-
if (this.options.database) {
|
|
587
|
-
await this.options.database.connect();
|
|
588
|
-
}
|
|
589
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
590
|
-
if (this.options.plugins) {
|
|
591
|
-
// Plugins are registered synchronously in constructor, but their async init happens now?
|
|
592
|
-
// Actually, plugins are registered via app.register() which is async.
|
|
593
|
-
// So they should be ready.
|
|
594
|
-
}
|
|
595
|
-
return new Promise((resolve, reject) => {
|
|
596
|
-
const onError = (error) => {
|
|
597
|
-
this.server.off('error', onError);
|
|
598
|
-
reject(error);
|
|
599
|
-
};
|
|
600
|
-
this.server.once('error', onError);
|
|
601
|
-
this.server.listen(port, hostname, () => {
|
|
602
|
-
this.server.off('error', onError);
|
|
603
|
-
// Freeze router after server starts to prevent further registrations
|
|
604
|
-
this.router.freeze();
|
|
605
|
-
void this.runLifecycleHooks(this.onStartHooks);
|
|
606
|
-
const address = this.server.address();
|
|
607
|
-
if (cb)
|
|
608
|
-
cb();
|
|
609
|
-
if (address && typeof address === 'object') {
|
|
610
|
-
resolve({ port: address.port });
|
|
611
|
-
}
|
|
612
|
-
else {
|
|
613
|
-
resolve({ port });
|
|
614
|
-
}
|
|
615
|
-
});
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
close() {
|
|
619
|
-
return new Promise((resolve, reject) => {
|
|
620
|
-
this.server.close((err) => {
|
|
621
|
-
if (err) {
|
|
622
|
-
reject(err);
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
resolve();
|
|
626
|
-
});
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
async shutdown() {
|
|
630
|
-
await this.runLifecycleHooks(this.onBeforeShutdownHooks);
|
|
631
|
-
await this.close();
|
|
632
|
-
await this.runLifecycleHooks(this.onShutdownHooks);
|
|
633
|
-
}
|
|
634
|
-
createContext() {
|
|
635
|
-
return new QHTTPXContextImpl(this);
|
|
636
|
-
}
|
|
637
|
-
acquireContext(req, res, urlOrPath, params, query, requestId, body,
|
|
638
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
639
|
-
files) {
|
|
640
|
-
const ctx = this.contextPool.pop() ?? this.createContext();
|
|
641
|
-
// Reset and populate properties
|
|
642
|
-
// We use type assertions to write to readonly/managed properties for performance
|
|
643
|
-
const mutableCtx = ctx;
|
|
644
|
-
mutableCtx.req = req;
|
|
645
|
-
mutableCtx.res = res;
|
|
646
|
-
mutableCtx.headers = req.headers;
|
|
647
|
-
if (typeof urlOrPath === 'string') {
|
|
648
|
-
// Ultra mode or fast path: Lazy URL instantiation
|
|
649
|
-
mutableCtx.path = urlOrPath;
|
|
650
|
-
// Class-based getter handles URL parsing lazily using this.req
|
|
651
|
-
}
|
|
652
|
-
else {
|
|
653
|
-
mutableCtx.url = urlOrPath;
|
|
654
|
-
mutableCtx.path = urlOrPath.pathname;
|
|
655
|
-
}
|
|
656
|
-
mutableCtx.method = req.method;
|
|
657
|
-
mutableCtx.params = params;
|
|
658
|
-
mutableCtx.query = query;
|
|
659
|
-
mutableCtx.body = body;
|
|
660
|
-
mutableCtx.files = files;
|
|
661
|
-
mutableCtx.requestId = requestId;
|
|
662
|
-
mutableCtx.serializer = undefined;
|
|
663
|
-
// In ultra mode, skip cookie parsing to save overhead
|
|
664
|
-
if (!this.ultraMode) {
|
|
665
|
-
mutableCtx.cookies = (0, cookies_1.parseCookies)(req.headers.cookie);
|
|
666
|
-
}
|
|
667
|
-
mutableCtx.disableAutoEnd = false;
|
|
668
|
-
return mutableCtx;
|
|
669
|
-
}
|
|
670
|
-
releaseContext(ctx) {
|
|
671
|
-
const mutableCtx = ctx;
|
|
672
|
-
mutableCtx.reset();
|
|
673
|
-
if (this.contextPool.length < this.poolLimit) {
|
|
674
|
-
this.contextPool.push(ctx);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
async handleNoMatch(ctx, allowedMethods) {
|
|
678
|
-
const res = ctx.res;
|
|
679
|
-
const hasAnyMethod = allowedMethods.length > 0;
|
|
680
|
-
if (hasAnyMethod && this.methodNotAllowedHandler) {
|
|
681
|
-
const result = this.methodNotAllowedHandler(ctx, allowedMethods);
|
|
682
|
-
if (result && typeof result.then === 'function') {
|
|
683
|
-
await result;
|
|
684
|
-
}
|
|
685
|
-
if (!res.writableEnded && !res.headersSent) {
|
|
686
|
-
res.statusCode = 405;
|
|
687
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
688
|
-
res.end('Method Not Allowed');
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
else if (!hasAnyMethod && this.notFoundHandler) {
|
|
692
|
-
const result = this.notFoundHandler(ctx);
|
|
693
|
-
if (result && typeof result.then === 'function') {
|
|
694
|
-
await result;
|
|
695
|
-
}
|
|
696
|
-
if (!res.writableEnded && !res.headersSent) {
|
|
697
|
-
res.statusCode = 404;
|
|
698
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
699
|
-
res.end('Not Found');
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
else if (hasAnyMethod) {
|
|
703
|
-
if (!res.headersSent) {
|
|
704
|
-
res.statusCode = 405;
|
|
705
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
706
|
-
}
|
|
707
|
-
res.end('Method Not Allowed');
|
|
708
|
-
}
|
|
709
|
-
else {
|
|
710
|
-
if (!res.headersSent) {
|
|
711
|
-
res.statusCode = 404;
|
|
712
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
713
|
-
}
|
|
714
|
-
res.end('Not Found');
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
handleRequest(req, res) {
|
|
718
|
-
// ⚡ Ultra Mode Optimization: Flattened & Inlined Logic
|
|
719
|
-
if (this.ultraMode) {
|
|
720
|
-
const rawUrl = req.url || '/';
|
|
721
|
-
const method = (req.method || 'GET');
|
|
722
|
-
// Single-pass pathname extraction
|
|
723
|
-
let pathname;
|
|
724
|
-
const qIndex = rawUrl.indexOf('?');
|
|
725
|
-
pathname = qIndex === -1 ? rawUrl : rawUrl.substring(0, qIndex);
|
|
726
|
-
const match = this.router.match(method, pathname);
|
|
727
|
-
if (match) {
|
|
728
|
-
// Acquire Context - reuse pooled object (no allocations)
|
|
729
|
-
const ctx = this.contextPool.pop() ?? this.createContext();
|
|
730
|
-
const mutableCtx = ctx;
|
|
731
|
-
mutableCtx.req = req;
|
|
732
|
-
mutableCtx.res = res;
|
|
733
|
-
mutableCtx.path = pathname;
|
|
734
|
-
mutableCtx.method = method;
|
|
735
|
-
mutableCtx.params = match.params || QHTTPX.EMPTY_PARAMS;
|
|
736
|
-
mutableCtx.query = EMPTY_QUERY;
|
|
737
|
-
mutableCtx.requestId = undefined;
|
|
738
|
-
mutableCtx.body = undefined;
|
|
739
|
-
mutableCtx.files = undefined;
|
|
740
|
-
mutableCtx.disableAutoEnd = false;
|
|
741
|
-
// Don't allocate state - leave as undefined
|
|
742
|
-
const releaseCtx = () => {
|
|
743
|
-
mutableCtx.reset();
|
|
744
|
-
if (this.contextPool.length < this.poolLimit) {
|
|
745
|
-
this.contextPool.push(ctx);
|
|
746
|
-
}
|
|
747
|
-
};
|
|
748
|
-
try {
|
|
749
|
-
const result = match.handler(ctx);
|
|
750
|
-
// Optimized promise detection
|
|
751
|
-
if (result?.then) {
|
|
752
|
-
result.then(() => {
|
|
753
|
-
if (!res.writableEnded && !ctx.disableAutoEnd) {
|
|
754
|
-
res.end();
|
|
755
|
-
}
|
|
756
|
-
releaseCtx();
|
|
757
|
-
}).catch((err) => {
|
|
758
|
-
console.error(err);
|
|
759
|
-
if (!res.headersSent) {
|
|
760
|
-
res.statusCode = 500;
|
|
761
|
-
res.end();
|
|
762
|
-
}
|
|
763
|
-
releaseCtx();
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
else {
|
|
767
|
-
if (!res.writableEnded && !ctx.disableAutoEnd) {
|
|
768
|
-
res.end();
|
|
769
|
-
}
|
|
770
|
-
releaseCtx();
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
catch (err) {
|
|
774
|
-
console.error(err);
|
|
775
|
-
if (!res.headersSent) {
|
|
776
|
-
res.statusCode = 500;
|
|
777
|
-
res.end();
|
|
778
|
-
}
|
|
779
|
-
releaseCtx();
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
else {
|
|
783
|
-
// No match - Ultra Mode Fast 404
|
|
784
|
-
if (!res.headersSent) {
|
|
785
|
-
res.statusCode = 404;
|
|
786
|
-
res.end('Not Found');
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
// === Balanced Mode Logic (Legacy) ===
|
|
792
|
-
const rawMethod = (req.method || 'GET').toUpperCase();
|
|
793
|
-
const method = rawMethod;
|
|
794
|
-
const rawUrl = req.url || '/';
|
|
795
|
-
// Fast path: parse URL without new URL() constructor (50-70x faster)
|
|
796
|
-
let pathname = rawUrl;
|
|
797
|
-
let query = EMPTY_QUERY;
|
|
798
|
-
const queryIndex = rawUrl.indexOf('?');
|
|
799
|
-
if (queryIndex !== -1) {
|
|
800
|
-
// Extract pathname
|
|
801
|
-
pathname = rawUrl.slice(0, queryIndex);
|
|
802
|
-
// Parse query string only if present
|
|
803
|
-
const queryString = rawUrl.slice(queryIndex + 1);
|
|
804
|
-
if (queryString.length > 0) {
|
|
805
|
-
const parsed = (0, querystring_1.parse)(queryString);
|
|
806
|
-
query = parsed;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
else {
|
|
810
|
-
// No query string, just extract pathname
|
|
811
|
-
pathname = rawUrl;
|
|
812
|
-
}
|
|
813
|
-
// Request ID generation
|
|
814
|
-
let requestId;
|
|
815
|
-
if (!this.ultraMode) {
|
|
816
|
-
const incomingRequestIdHeader = req.headers['x-request-id'];
|
|
817
|
-
if (typeof incomingRequestIdHeader === 'string') {
|
|
818
|
-
requestId = incomingRequestIdHeader;
|
|
819
|
-
}
|
|
820
|
-
else if (Array.isArray(incomingRequestIdHeader)) {
|
|
821
|
-
requestId = incomingRequestIdHeader[0];
|
|
822
|
-
}
|
|
823
|
-
if (!requestId) {
|
|
824
|
-
requestId = this.generateRequestId();
|
|
825
|
-
}
|
|
826
|
-
if (!res.headersSent && requestId) {
|
|
827
|
-
res.setHeader('x-request-id', requestId);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
let match = this.router.match(method, pathname);
|
|
831
|
-
const hasRoute = !!match;
|
|
832
|
-
if (!match) {
|
|
833
|
-
match = QHTTPX.EMPTY_MATCH;
|
|
834
|
-
}
|
|
835
|
-
const allowedMethods = this.router.getAllowedMethods(pathname);
|
|
836
|
-
if (!hasRoute && !res.headersSent) {
|
|
837
|
-
const hasAnyMethod = allowedMethods.length > 0;
|
|
838
|
-
res.statusCode = hasAnyMethod ? 405 : 404;
|
|
839
|
-
}
|
|
840
|
-
// Memory Checks (throttled to every 100 requests for performance)
|
|
841
|
-
if (this.options.maxMemoryBytes !== undefined && ++this.requestCounter % 100 === 0) {
|
|
842
|
-
const sampleRss = process.memoryUsage().rss;
|
|
843
|
-
const overloadedByMemory = (0, resources_1.isResourceOverloaded)({ rssBytes: sampleRss }, { maxRssBytes: this.options.maxMemoryBytes });
|
|
844
|
-
if (overloadedByMemory) {
|
|
845
|
-
const overloaded = () => {
|
|
846
|
-
if (res.writableEnded) {
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
if (!res.headersSent) {
|
|
850
|
-
res.statusCode = 503;
|
|
851
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
852
|
-
}
|
|
853
|
-
res.end('Server overloaded');
|
|
854
|
-
};
|
|
855
|
-
overloaded();
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
else {
|
|
860
|
-
++this.requestCounter;
|
|
861
|
-
}
|
|
862
|
-
if (method !== 'GET' && method !== 'HEAD') {
|
|
863
|
-
body_parser_1.BodyParser.parse(req, {
|
|
864
|
-
maxBodyBytes: this.options.maxBodyBytes,
|
|
865
|
-
}).then((parsed) => {
|
|
866
|
-
this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, parsed.body, parsed.files);
|
|
867
|
-
}).catch((err) => {
|
|
868
|
-
if (err instanceof Error && err.message === 'QHTTPX_INVALID_JSON') {
|
|
869
|
-
if (!res.headersSent) {
|
|
870
|
-
res.statusCode = 400;
|
|
871
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
872
|
-
}
|
|
873
|
-
res.end('Invalid JSON');
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
if (err instanceof Error && err.message === 'QHTTPX_BODY_TOO_LARGE') {
|
|
877
|
-
if (!res.headersSent) {
|
|
878
|
-
res.statusCode = 413;
|
|
879
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
880
|
-
}
|
|
881
|
-
res.end('Payload Too Large');
|
|
882
|
-
return;
|
|
883
|
-
}
|
|
884
|
-
if (!res.headersSent) {
|
|
885
|
-
res.statusCode = 500;
|
|
886
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
887
|
-
}
|
|
888
|
-
res.end('Internal Server Error');
|
|
889
|
-
});
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, undefined, undefined);
|
|
893
|
-
}
|
|
894
|
-
dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, body, files) {
|
|
895
|
-
const ctx = this.acquireContext(req, res, pathname, match.params, query, requestId, body, files);
|
|
896
|
-
// Balanced Mode Logic
|
|
897
|
-
const overloaded = () => {
|
|
898
|
-
if (res.writableEnded) {
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
if (!res.headersSent) {
|
|
902
|
-
res.statusCode = 503;
|
|
903
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
904
|
-
}
|
|
905
|
-
res.end('Server overloaded');
|
|
906
|
-
};
|
|
907
|
-
let timedOut = false;
|
|
908
|
-
const onTimeout = () => {
|
|
909
|
-
timedOut = true;
|
|
910
|
-
if (res.writableEnded) {
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
|
-
if (!res.headersSent) {
|
|
914
|
-
res.statusCode = 504;
|
|
915
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
916
|
-
}
|
|
917
|
-
res.end('Request timed out');
|
|
918
|
-
};
|
|
919
|
-
const start = Date.now();
|
|
920
|
-
ctx.requestStart = start;
|
|
921
|
-
this.metrics.onRequestStart();
|
|
922
|
-
if (this.tracer) {
|
|
923
|
-
const event = {
|
|
924
|
-
type: 'request_start',
|
|
925
|
-
method,
|
|
926
|
-
path: pathname,
|
|
927
|
-
requestId,
|
|
928
|
-
};
|
|
929
|
-
const result = this.tracer(event);
|
|
930
|
-
if (result && typeof result.then === 'function') {
|
|
931
|
-
void result;
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
const finish = () => {
|
|
935
|
-
const duration = Date.now() - start;
|
|
936
|
-
this.metrics.onRequestEnd(duration, res.statusCode);
|
|
937
|
-
if (timedOut) {
|
|
938
|
-
this.metrics.onTimeout();
|
|
939
|
-
}
|
|
940
|
-
if (this.tracer) {
|
|
941
|
-
const event = {
|
|
942
|
-
type: 'request_end',
|
|
943
|
-
method,
|
|
944
|
-
path: pathname,
|
|
945
|
-
statusCode: res.statusCode,
|
|
946
|
-
durationMs: duration,
|
|
947
|
-
requestId,
|
|
948
|
-
};
|
|
949
|
-
const result = this.tracer(event);
|
|
950
|
-
if (result && typeof result.then === 'function') {
|
|
951
|
-
void result;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
this.releaseContext(ctx);
|
|
955
|
-
};
|
|
956
|
-
const handle = async () => {
|
|
957
|
-
const handler = match.handler;
|
|
958
|
-
try {
|
|
959
|
-
if (hasRoute) {
|
|
960
|
-
const result = handler(ctx);
|
|
961
|
-
if (result && typeof result.then === 'function') {
|
|
962
|
-
await result;
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
else if (this.pipelineRunner) {
|
|
966
|
-
await this.pipelineRunner(ctx, handler);
|
|
967
|
-
}
|
|
968
|
-
if (!res.writableEnded) {
|
|
969
|
-
if (!hasRoute) {
|
|
970
|
-
await this.handleNoMatch(ctx, allowedMethods);
|
|
971
|
-
}
|
|
972
|
-
else if (!ctx.disableAutoEnd) {
|
|
973
|
-
res.end();
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
catch (err) {
|
|
978
|
-
await this.handleError(err, ctx);
|
|
979
|
-
}
|
|
980
|
-
};
|
|
981
|
-
// Ultra mode: skip scheduler for minimal overhead
|
|
982
|
-
if (this.ultraMode) {
|
|
983
|
-
handle().then(finish).catch((err) => {
|
|
984
|
-
console.error('Request handler error:', err);
|
|
985
|
-
finish();
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
else {
|
|
989
|
-
this.scheduler.run(handle, {
|
|
990
|
-
priority: match.priority,
|
|
991
|
-
onOverloaded: overloaded,
|
|
992
|
-
timeoutMs: this.options.requestTimeoutMs,
|
|
993
|
-
onTimeout,
|
|
994
|
-
}).then(finish).catch((err) => {
|
|
995
|
-
console.error('Scheduler error:', err);
|
|
996
|
-
finish();
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
async handleUpgrade(req, socket, head) {
|
|
1001
|
-
await this.wsManager.handleUpgrade(req, socket, head);
|
|
1002
|
-
}
|
|
1003
|
-
async runLifecycleHooks(hooks) {
|
|
1004
|
-
if (hooks.length === 0) {
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
for (const hook of hooks) {
|
|
1008
|
-
try {
|
|
1009
|
-
const result = hook();
|
|
1010
|
-
if (result && typeof result.then === 'function') {
|
|
1011
|
-
await result;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
catch {
|
|
1015
|
-
// Ignore hook errors to avoid impacting core server flow
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
handleError(err, ctx) {
|
|
1020
|
-
const res = ctx.res;
|
|
1021
|
-
if (res.writableEnded) {
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
if (this.errorHandler) {
|
|
1025
|
-
try {
|
|
1026
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1027
|
-
const errorContext = ctx;
|
|
1028
|
-
errorContext.error = err;
|
|
1029
|
-
const result = this.errorHandler(errorContext);
|
|
1030
|
-
if (result && typeof result.then === 'function') {
|
|
1031
|
-
return result.then(() => {
|
|
1032
|
-
// Ensure response is sent if handler didn't
|
|
1033
|
-
});
|
|
1034
|
-
}
|
|
1035
|
-
if (res.writableEnded) {
|
|
1036
|
-
return;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
catch (handlerErr) {
|
|
1040
|
-
// Fall through to default error handling below
|
|
1041
|
-
console.error('Error in error handler:', handlerErr);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
if (res.writableEnded) {
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
if (err instanceof types_1.HttpError) {
|
|
1048
|
-
if (!res.headersSent) {
|
|
1049
|
-
res.statusCode = err.status;
|
|
1050
|
-
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
1051
|
-
}
|
|
1052
|
-
const payload = {
|
|
1053
|
-
error: {
|
|
1054
|
-
message: err.message,
|
|
1055
|
-
code: err.code ?? 'HTTP_ERROR',
|
|
1056
|
-
details: err.details,
|
|
1057
|
-
},
|
|
1058
|
-
};
|
|
1059
|
-
const serializer = this.options.jsonSerializer;
|
|
1060
|
-
const body = serializer !== undefined ? serializer(payload) : JSON.stringify(payload);
|
|
1061
|
-
res.end(body);
|
|
1062
|
-
return;
|
|
1063
|
-
}
|
|
1064
|
-
if (!res.headersSent) {
|
|
1065
|
-
res.statusCode = 500;
|
|
1066
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
1067
|
-
}
|
|
1068
|
-
res.end('Internal Server Error');
|
|
1069
|
-
}
|
|
1070
|
-
generateRequestId() {
|
|
1071
|
-
const id = this.nextRequestId;
|
|
1072
|
-
this.nextRequestId += 1;
|
|
1073
|
-
return `${Date.now().toString(36)}-${id.toString(36)}`;
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
exports.QHTTPX = QHTTPX;
|
|
1077
|
-
QHTTPX.EMPTY_PARAMS = Object.freeze({});
|
|
1078
|
-
QHTTPX.EMPTY_MATCH = Object.freeze({
|
|
1079
|
-
handler: () => { },
|
|
1080
|
-
params: QHTTPX.EMPTY_PARAMS,
|
|
1081
|
-
priority: types_1.RoutePriority.STANDARD,
|
|
1082
|
-
});
|