qhttpx 1.9.4 → 2.0.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/CHANGELOG.md +21 -0
- package/README.md +28 -35
- package/dist/examples/api-server.js +38 -35
- package/dist/examples/basic.js +3 -4
- package/dist/examples/compression.js +6 -8
- package/dist/examples/cors.js +5 -6
- package/dist/examples/errors.js +12 -11
- package/dist/examples/file-upload.js +4 -6
- package/dist/examples/fusion.js +6 -6
- package/dist/examples/rate-limiting.js +10 -10
- package/dist/examples/validation.js +5 -6
- package/dist/examples/websockets.js +3 -4
- package/dist/package.json +3 -8
- package/dist/src/benchmarks/quantam-users.js +2 -2
- package/dist/src/benchmarks/quick-bench.js +57 -0
- package/dist/src/benchmarks/simple-json.js +133 -22
- package/dist/src/benchmarks/ultra-mode.js +8 -38
- package/dist/src/core/context-pool.d.ts +12 -0
- package/dist/src/core/context-pool.js +34 -0
- package/dist/src/core/fusion.js +0 -2
- package/dist/src/core/metrics.d.ts +3 -1
- package/dist/src/core/metrics.js +11 -5
- package/dist/src/core/scheduler.d.ts +4 -0
- package/dist/src/core/scheduler.js +75 -34
- package/dist/src/core/scope.d.ts +23 -8
- package/dist/src/core/scope.js +53 -14
- package/dist/src/core/serializer.d.ts +1 -1
- package/dist/src/core/serializer.js +45 -7
- package/dist/src/core/server.d.ts +51 -10
- package/dist/src/core/server.js +688 -259
- package/dist/src/core/timer.d.ts +11 -0
- package/dist/src/core/timer.js +29 -0
- package/dist/src/core/types.d.ts +41 -13
- package/dist/src/core/types.js +6 -6
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.js +19 -20
- package/dist/src/middleware/security.js +6 -1
- package/dist/src/router/radix-tree.d.ts +5 -2
- package/dist/src/router/radix-tree.js +58 -14
- package/dist/src/router/router.d.ts +5 -2
- package/dist/src/router/router.js +80 -63
- package/dist/tests/fusion.test.js +4 -4
- package/dist/tests/rate-limit.test.js +2 -2
- package/dist/tests/schema-routes.test.js +3 -1
- package/docs/AEGIS.md +18 -28
- package/docs/BENCHMARKS.md +8 -6
- package/docs/DATABASE.md +4 -4
- package/docs/MIDDLEWARE.md +3 -3
- package/docs/ROUTING.md +21 -13
- package/docs/VALIDATION.md +9 -31
- package/package.json +3 -8
- package/binding.gyp +0 -18
- package/dist/src/benchmarks/compare-frameworks.js +0 -119
- package/dist/src/benchmarks/compare.js +0 -288
- package/dist/src/buffer-pool.js +0 -70
- package/dist/src/config.js +0 -50
- package/dist/src/cookies.js +0 -59
- package/dist/src/core/native-adapter.d.ts +0 -11
- package/dist/src/core/native-adapter.js +0 -211
- package/dist/src/cors.js +0 -66
- package/dist/src/logger.js +0 -45
- package/dist/src/metrics.js +0 -111
- package/dist/src/native/index.d.ts +0 -32
- package/dist/src/native/index.js +0 -141
- package/dist/src/presets.js +0 -33
- package/dist/src/radix-router.js +0 -89
- package/dist/src/radix-tree.js +0 -81
- package/dist/src/resources.js +0 -25
- package/dist/src/router.js +0 -138
- package/dist/src/scheduler.js +0 -85
- package/dist/src/security.js +0 -69
- package/dist/src/server.js +0 -685
- package/dist/src/signals.js +0 -31
- package/dist/src/static.js +0 -107
- package/dist/src/stream.js +0 -71
- package/dist/src/tasks.js +0 -87
- package/dist/src/testing.js +0 -40
- package/dist/src/types.js +0 -19
- package/dist/src/utils/testing.js +0 -40
- package/dist/src/worker-queue.js +0 -73
- package/dist/tests/native-adapter.test.d.ts +0 -1
- package/dist/tests/native-adapter.test.js +0 -71
- 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/scripts/install-native.js +0 -26
- package/src/native/README.md +0 -31
- package/src/native/addon.cc +0 -8
- package/src/native/index.ts +0 -158
- package/src/native/picohttpparser.c +0 -608
- package/src/native/picohttpparser.h +0 -76
- package/src/native/server.cc +0 -264
- package/src/native/server.h +0 -30
- /package/dist/src/benchmarks/{compare.d.ts → quick-bench.d.ts} +0 -0
package/dist/src/signals.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.attachSignalHandlers = attachSignalHandlers;
|
|
4
|
-
function attachSignalHandlers(app, options = {}) {
|
|
5
|
-
const signals = options.signals ?? ['SIGINT', 'SIGTERM'];
|
|
6
|
-
const timeoutMs = options.timeoutMs ?? 10000;
|
|
7
|
-
let shuttingDown = false;
|
|
8
|
-
const handler = async (signal) => {
|
|
9
|
-
if (shuttingDown) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
shuttingDown = true;
|
|
13
|
-
console.log(`Received ${signal}, shutting down...`);
|
|
14
|
-
const timer = setTimeout(() => {
|
|
15
|
-
console.error('Shutdown timed out, forcing exit. (some connections might be lost)');
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}, timeoutMs);
|
|
18
|
-
try {
|
|
19
|
-
await app.shutdown();
|
|
20
|
-
clearTimeout(timer);
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
console.error('Error during shutdown:', err);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
for (const signal of signals) {
|
|
29
|
-
process.on(signal, handler);
|
|
30
|
-
}
|
|
31
|
-
}
|
package/dist/src/static.js
DELETED
|
@@ -1,107 +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.createStaticMiddleware = createStaticMiddleware;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
function guessContentType(filePath) {
|
|
10
|
-
const ext = path_1.default.extname(filePath).toLowerCase();
|
|
11
|
-
if (ext === '.html' || ext === '.htm') {
|
|
12
|
-
return 'text/html; charset=utf-8';
|
|
13
|
-
}
|
|
14
|
-
if (ext === '.js') {
|
|
15
|
-
return 'application/javascript; charset=utf-8';
|
|
16
|
-
}
|
|
17
|
-
if (ext === '.css') {
|
|
18
|
-
return 'text/css; charset=utf-8';
|
|
19
|
-
}
|
|
20
|
-
if (ext === '.json') {
|
|
21
|
-
return 'application/json; charset=utf-8';
|
|
22
|
-
}
|
|
23
|
-
if (ext === '.png') {
|
|
24
|
-
return 'image/png';
|
|
25
|
-
}
|
|
26
|
-
if (ext === '.jpg' || ext === '.jpeg') {
|
|
27
|
-
return 'image/jpeg';
|
|
28
|
-
}
|
|
29
|
-
if (ext === '.gif') {
|
|
30
|
-
return 'image/gif';
|
|
31
|
-
}
|
|
32
|
-
if (ext === '.svg') {
|
|
33
|
-
return 'image/svg+xml';
|
|
34
|
-
}
|
|
35
|
-
return 'application/octet-stream';
|
|
36
|
-
}
|
|
37
|
-
function createStaticMiddleware(options) {
|
|
38
|
-
const root = path_1.default.resolve(options.root);
|
|
39
|
-
const indexFile = options.index ?? 'index.html';
|
|
40
|
-
const fallthrough = options.fallthrough ?? false;
|
|
41
|
-
return async (ctx, next) => {
|
|
42
|
-
const method = ctx.req.method || 'GET';
|
|
43
|
-
if (method !== 'GET' && method !== 'HEAD') {
|
|
44
|
-
if (fallthrough) {
|
|
45
|
-
await next();
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
ctx.res.statusCode = 405;
|
|
49
|
-
ctx.res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
50
|
-
ctx.res.end('Method Not Allowed');
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
let requestPath = ctx.url.pathname || '/';
|
|
54
|
-
requestPath = requestPath.replace(/^[/\\]+/, '');
|
|
55
|
-
if (requestPath === '') {
|
|
56
|
-
requestPath = indexFile;
|
|
57
|
-
}
|
|
58
|
-
else if (requestPath.endsWith('/')) {
|
|
59
|
-
requestPath = requestPath + indexFile;
|
|
60
|
-
}
|
|
61
|
-
let safePath = path_1.default.normalize(requestPath);
|
|
62
|
-
safePath = safePath.replace(/^(\.\.(\/|\\|$))+/, '');
|
|
63
|
-
const filePath = path_1.default.join(root, safePath);
|
|
64
|
-
let stat;
|
|
65
|
-
try {
|
|
66
|
-
stat = await fs_1.default.promises.stat(filePath);
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
if (fallthrough) {
|
|
70
|
-
await next();
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
ctx.res.statusCode = 404;
|
|
74
|
-
ctx.res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
75
|
-
ctx.res.end('Not Found');
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (!stat.isFile()) {
|
|
79
|
-
if (fallthrough) {
|
|
80
|
-
await next();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
ctx.res.statusCode = 404;
|
|
84
|
-
ctx.res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
85
|
-
ctx.res.end('Not Found');
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const contentType = guessContentType(filePath);
|
|
89
|
-
ctx.res.setHeader('content-type', contentType);
|
|
90
|
-
ctx.res.statusCode = 200;
|
|
91
|
-
if (method === 'HEAD') {
|
|
92
|
-
ctx.res.end();
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
const data = await fs_1.default.promises.readFile(filePath);
|
|
97
|
-
ctx.res.end(data);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
if (!ctx.res.headersSent) {
|
|
101
|
-
ctx.res.statusCode = 500;
|
|
102
|
-
ctx.res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
103
|
-
}
|
|
104
|
-
ctx.res.end('Internal Server Error');
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
}
|
package/dist/src/stream.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createSseStream = createSseStream;
|
|
4
|
-
exports.sendStream = sendStream;
|
|
5
|
-
function createSseStream(ctx, options = {}) {
|
|
6
|
-
const res = ctx.res;
|
|
7
|
-
if (!res.headersSent) {
|
|
8
|
-
res.statusCode = 200;
|
|
9
|
-
res.setHeader('content-type', 'text/event-stream; charset=utf-8');
|
|
10
|
-
res.setHeader('cache-control', 'no-cache');
|
|
11
|
-
res.setHeader('connection', 'keep-alive');
|
|
12
|
-
}
|
|
13
|
-
const anyRes = res;
|
|
14
|
-
if (anyRes.flushHeaders) {
|
|
15
|
-
anyRes.flushHeaders();
|
|
16
|
-
}
|
|
17
|
-
if (typeof options.retryMs === 'number') {
|
|
18
|
-
res.write(`retry: ${options.retryMs}\n\n`);
|
|
19
|
-
}
|
|
20
|
-
const send = (data, event) => {
|
|
21
|
-
if (res.writableEnded) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
const payload = typeof data === 'string' ? data : JSON.stringify(data);
|
|
25
|
-
let chunk = '';
|
|
26
|
-
if (event) {
|
|
27
|
-
chunk += `event: ${event}\n`;
|
|
28
|
-
}
|
|
29
|
-
chunk += `data: ${payload}\n\n`;
|
|
30
|
-
res.write(chunk);
|
|
31
|
-
if (anyRes.flush) {
|
|
32
|
-
anyRes.flush();
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const close = () => {
|
|
36
|
-
if (!res.writableEnded) {
|
|
37
|
-
res.end();
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
return { send, close };
|
|
41
|
-
}
|
|
42
|
-
function sendStream(ctx, stream, options = {}) {
|
|
43
|
-
const res = ctx.res;
|
|
44
|
-
if (!res.headersSent) {
|
|
45
|
-
if (options.status !== undefined) {
|
|
46
|
-
res.statusCode = options.status;
|
|
47
|
-
}
|
|
48
|
-
if (options.contentType) {
|
|
49
|
-
res.setHeader('content-type', options.contentType);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
stream.on('error', (err) => {
|
|
54
|
-
if (!res.headersSent) {
|
|
55
|
-
res.statusCode = 500;
|
|
56
|
-
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
57
|
-
}
|
|
58
|
-
if (!res.writableEnded) {
|
|
59
|
-
res.end('Internal Server Error');
|
|
60
|
-
}
|
|
61
|
-
reject(err);
|
|
62
|
-
});
|
|
63
|
-
stream.on('end', () => {
|
|
64
|
-
if (!res.writableEnded) {
|
|
65
|
-
res.end();
|
|
66
|
-
}
|
|
67
|
-
resolve();
|
|
68
|
-
});
|
|
69
|
-
stream.pipe(res, { end: false });
|
|
70
|
-
});
|
|
71
|
-
}
|
package/dist/src/tasks.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TaskEngine = void 0;
|
|
4
|
-
class TaskEngine {
|
|
5
|
-
constructor(scheduler) {
|
|
6
|
-
this.tasks = new Map();
|
|
7
|
-
this.registeredTasksCount = 0;
|
|
8
|
-
this.totalEnqueued = 0;
|
|
9
|
-
this.totalCompleted = 0;
|
|
10
|
-
this.totalFailed = 0;
|
|
11
|
-
this.totalOverloaded = 0;
|
|
12
|
-
this.totalRetried = 0;
|
|
13
|
-
this.scheduler = scheduler;
|
|
14
|
-
}
|
|
15
|
-
register(name, handler, options = {}) {
|
|
16
|
-
if (!this.tasks.has(name)) {
|
|
17
|
-
this.registeredTasksCount += 1;
|
|
18
|
-
}
|
|
19
|
-
this.tasks.set(name, {
|
|
20
|
-
name,
|
|
21
|
-
handler,
|
|
22
|
-
options,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
async enqueue(name, payload) {
|
|
26
|
-
const def = this.tasks.get(name);
|
|
27
|
-
if (!def) {
|
|
28
|
-
throw new Error(`Task "${name}" is not registered`);
|
|
29
|
-
}
|
|
30
|
-
this.totalEnqueued += 1;
|
|
31
|
-
await this.executeWithRetry(def, payload);
|
|
32
|
-
}
|
|
33
|
-
getMetrics() {
|
|
34
|
-
return {
|
|
35
|
-
registeredTasks: this.registeredTasksCount,
|
|
36
|
-
totalEnqueued: this.totalEnqueued,
|
|
37
|
-
totalCompleted: this.totalCompleted,
|
|
38
|
-
totalFailed: this.totalFailed,
|
|
39
|
-
totalOverloaded: this.totalOverloaded,
|
|
40
|
-
totalRetried: this.totalRetried,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
async executeWithRetry(def, payload) {
|
|
44
|
-
const maxRetries = def.options.maxRetries ?? 0;
|
|
45
|
-
const backoffMs = def.options.backoffMs ?? 0;
|
|
46
|
-
let attempt = 0;
|
|
47
|
-
for (;;) {
|
|
48
|
-
let overloaded = false;
|
|
49
|
-
let error;
|
|
50
|
-
await this.scheduler.run(async () => {
|
|
51
|
-
try {
|
|
52
|
-
await def.handler(payload);
|
|
53
|
-
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
error = err;
|
|
56
|
-
}
|
|
57
|
-
}, {
|
|
58
|
-
onOverloaded: () => {
|
|
59
|
-
overloaded = true;
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
if (!overloaded && !error) {
|
|
63
|
-
this.totalCompleted += 1;
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (attempt >= maxRetries) {
|
|
67
|
-
if (error) {
|
|
68
|
-
this.totalFailed += 1;
|
|
69
|
-
throw error;
|
|
70
|
-
}
|
|
71
|
-
if (overloaded) {
|
|
72
|
-
this.totalOverloaded += 1;
|
|
73
|
-
throw new Error(`Task "${def.name}" overloaded`);
|
|
74
|
-
}
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
attempt += 1;
|
|
78
|
-
this.totalRetried += 1;
|
|
79
|
-
if (backoffMs > 0) {
|
|
80
|
-
await new Promise((resolve) => {
|
|
81
|
-
setTimeout(resolve, backoffMs);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
exports.TaskEngine = TaskEngine;
|
package/dist/src/testing.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createTestClient = createTestClient;
|
|
4
|
-
async function createTestClient(app) {
|
|
5
|
-
const { port } = await app.listen(0, '127.0.0.1');
|
|
6
|
-
const baseUrl = `http://127.0.0.1:${port}`;
|
|
7
|
-
const request = async (method, path, body, extraHeaders = {}) => {
|
|
8
|
-
const headers = { ...extraHeaders };
|
|
9
|
-
const init = {
|
|
10
|
-
method,
|
|
11
|
-
headers,
|
|
12
|
-
};
|
|
13
|
-
if (body !== undefined) {
|
|
14
|
-
if (typeof body === 'string') {
|
|
15
|
-
init.body = body;
|
|
16
|
-
}
|
|
17
|
-
else if (body instanceof Uint8Array || Buffer.isBuffer(body)) {
|
|
18
|
-
init.body = body;
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
init.body = JSON.stringify(body);
|
|
22
|
-
if (!('content-type' in headers)) {
|
|
23
|
-
headers['content-type'] = 'application/json';
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return fetch(`${baseUrl}${path}`, init);
|
|
28
|
-
};
|
|
29
|
-
return {
|
|
30
|
-
get: (path, headers) => request('GET', path, undefined, headers),
|
|
31
|
-
post: (path, body, headers) => request('POST', path, body, headers),
|
|
32
|
-
put: (path, body, headers) => request('PUT', path, body, headers),
|
|
33
|
-
delete: (path, headers) => request('DELETE', path, undefined, headers),
|
|
34
|
-
close: async () => {
|
|
35
|
-
await app.close();
|
|
36
|
-
},
|
|
37
|
-
app,
|
|
38
|
-
url: baseUrl,
|
|
39
|
-
};
|
|
40
|
-
}
|
package/dist/src/types.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HttpError = exports.RoutePriority = void 0;
|
|
4
|
-
var RoutePriority;
|
|
5
|
-
(function (RoutePriority) {
|
|
6
|
-
RoutePriority["CRITICAL"] = "critical";
|
|
7
|
-
RoutePriority["STANDARD"] = "standard";
|
|
8
|
-
RoutePriority["BEST_EFFORT"] = "best-effort";
|
|
9
|
-
})(RoutePriority || (exports.RoutePriority = RoutePriority = {}));
|
|
10
|
-
class HttpError extends Error {
|
|
11
|
-
constructor(status, message, options = {}) {
|
|
12
|
-
super(message ?? 'HTTP Error');
|
|
13
|
-
this.status = status;
|
|
14
|
-
this.code = options.code;
|
|
15
|
-
this.details = options.details;
|
|
16
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
exports.HttpError = HttpError;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createTestClient = createTestClient;
|
|
4
|
-
async function createTestClient(app) {
|
|
5
|
-
const { port } = await app.listen(0, '127.0.0.1');
|
|
6
|
-
const baseUrl = `http://127.0.0.1:${port}`;
|
|
7
|
-
const request = async (method, path, body, extraHeaders = {}) => {
|
|
8
|
-
const headers = { ...extraHeaders };
|
|
9
|
-
const init = {
|
|
10
|
-
method,
|
|
11
|
-
headers,
|
|
12
|
-
};
|
|
13
|
-
if (body !== undefined) {
|
|
14
|
-
if (typeof body === 'string') {
|
|
15
|
-
init.body = body;
|
|
16
|
-
}
|
|
17
|
-
else if (body instanceof Uint8Array || Buffer.isBuffer(body)) {
|
|
18
|
-
init.body = body;
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
init.body = JSON.stringify(body);
|
|
22
|
-
if (!('content-type' in headers)) {
|
|
23
|
-
headers['content-type'] = 'application/json';
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return fetch(`${baseUrl}${path}`, init);
|
|
28
|
-
};
|
|
29
|
-
return {
|
|
30
|
-
get: (path, headers) => request('GET', path, undefined, headers),
|
|
31
|
-
post: (path, body, headers) => request('POST', path, body, headers),
|
|
32
|
-
put: (path, body, headers) => request('PUT', path, body, headers),
|
|
33
|
-
delete: (path, headers) => request('DELETE', path, undefined, headers),
|
|
34
|
-
close: async () => {
|
|
35
|
-
await app.close();
|
|
36
|
-
},
|
|
37
|
-
app,
|
|
38
|
-
url: baseUrl,
|
|
39
|
-
};
|
|
40
|
-
}
|
package/dist/src/worker-queue.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Lock-free (or lock-minimal) work queue for per-worker task distribution.
|
|
4
|
-
* Uses a simple ring buffer for high throughput.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.WorkerQueue = void 0;
|
|
8
|
-
class WorkerQueue {
|
|
9
|
-
constructor(capacity = 1024) {
|
|
10
|
-
this.writeIndex = 0;
|
|
11
|
-
this.readIndex = 0;
|
|
12
|
-
this.size = 0;
|
|
13
|
-
if (capacity <= 0 || !Number.isInteger(capacity)) {
|
|
14
|
-
throw new Error('Capacity must be a positive integer');
|
|
15
|
-
}
|
|
16
|
-
// Ensure capacity is a power of 2 for efficient modulo with bitmask
|
|
17
|
-
this.capacity = Math.pow(2, Math.ceil(Math.log2(capacity)));
|
|
18
|
-
this.buffer = new Array(this.capacity);
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Enqueue a work item. Returns true if successful, false if queue is full.
|
|
22
|
-
*/
|
|
23
|
-
enqueue(item) {
|
|
24
|
-
if (this.size >= this.capacity) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
this.buffer[this.writeIndex] = item;
|
|
28
|
-
this.writeIndex = (this.writeIndex + 1) & (this.capacity - 1);
|
|
29
|
-
this.size += 1;
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Dequeue a work item. Returns undefined if queue is empty.
|
|
34
|
-
*/
|
|
35
|
-
dequeue() {
|
|
36
|
-
if (this.size <= 0) {
|
|
37
|
-
return undefined;
|
|
38
|
-
}
|
|
39
|
-
const item = this.buffer[this.readIndex];
|
|
40
|
-
this.buffer[this.readIndex] = undefined;
|
|
41
|
-
this.readIndex = (this.readIndex + 1) & (this.capacity - 1);
|
|
42
|
-
this.size -= 1;
|
|
43
|
-
return item;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Peek at the next item without removing it.
|
|
47
|
-
*/
|
|
48
|
-
peek() {
|
|
49
|
-
if (this.size <= 0) {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
return this.buffer[this.readIndex];
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Check if the queue is empty.
|
|
56
|
-
*/
|
|
57
|
-
isEmpty() {
|
|
58
|
-
return this.size === 0;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Get the current size of the queue.
|
|
62
|
-
*/
|
|
63
|
-
getSize() {
|
|
64
|
-
return this.size;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Get the capacity of the queue.
|
|
68
|
-
*/
|
|
69
|
-
getCapacity() {
|
|
70
|
-
return this.capacity;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
exports.WorkerQueue = WorkerQueue;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,71 +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
|
-
const vitest_1 = require("vitest");
|
|
7
|
-
const native_adapter_1 = require("../src/core/native-adapter");
|
|
8
|
-
const index_1 = require("../src/index");
|
|
9
|
-
const net_1 = __importDefault(require("net"));
|
|
10
|
-
// Mock the NativeServer module
|
|
11
|
-
vitest_1.vi.mock('../src/native', () => {
|
|
12
|
-
return {
|
|
13
|
-
NativeServer: class MockNativeServer {
|
|
14
|
-
constructor() {
|
|
15
|
-
this.isAvailable = true;
|
|
16
|
-
}
|
|
17
|
-
parse(buffer) {
|
|
18
|
-
const str = buffer.toString();
|
|
19
|
-
if (str.includes('GET / HTTP/1.1')) {
|
|
20
|
-
return {
|
|
21
|
-
method: 'GET',
|
|
22
|
-
path: '/',
|
|
23
|
-
version: 1,
|
|
24
|
-
headers: { host: 'localhost' },
|
|
25
|
-
bodyOffset: str.indexOf('\r\n\r\n') + 4
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
-
createResponse(statusCode, headers, body) {
|
|
32
|
-
return Buffer.from(`HTTP/1.1 ${statusCode} OK\r\n\r\n${body || ''}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
});
|
|
37
|
-
(0, vitest_1.describe)('NativeAdapter', () => {
|
|
38
|
-
let app;
|
|
39
|
-
let adapter;
|
|
40
|
-
let server;
|
|
41
|
-
let port = 0;
|
|
42
|
-
(0, vitest_1.beforeEach)(() => {
|
|
43
|
-
app = (0, index_1.createHttpApp)();
|
|
44
|
-
adapter = new native_adapter_1.NativeAdapter(app);
|
|
45
|
-
});
|
|
46
|
-
(0, vitest_1.afterEach)(async () => {
|
|
47
|
-
if (server) {
|
|
48
|
-
await new Promise(resolve => server.close(() => resolve()));
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
(0, vitest_1.it)('should handle request using native parser mock', async () => {
|
|
52
|
-
app.get('/', ({ res }) => { res.end('Native Works'); });
|
|
53
|
-
await new Promise((resolve) => {
|
|
54
|
-
server = adapter.listen(0, () => {
|
|
55
|
-
port = server.address().port;
|
|
56
|
-
resolve();
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
const socket = net_1.default.createConnection(port);
|
|
60
|
-
const responsePromise = new Promise((resolve) => {
|
|
61
|
-
socket.on('data', (data) => {
|
|
62
|
-
resolve(data.toString());
|
|
63
|
-
socket.end();
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
socket.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
|
67
|
-
const response = await responsePromise;
|
|
68
|
-
(0, vitest_1.expect)(response).toContain('HTTP/1.1 200 OK');
|
|
69
|
-
(0, vitest_1.expect)(response).toContain('Native Works');
|
|
70
|
-
});
|
|
71
|
-
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const { execSync } = require('child_process');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
// Graceful Native Build Strategy
|
|
6
|
-
// This script attempts to build the native addon but swallows errors
|
|
7
|
-
// to ensure installation succeeds even on environments without C++ tools.
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
// Check if prebuilds exist (optional optimization)
|
|
11
|
-
// But usually we want to try building if we are installing from source/npm
|
|
12
|
-
|
|
13
|
-
console.log('QHTTPX: Attempting native compilation...');
|
|
14
|
-
|
|
15
|
-
// Use node-gyp directly. It should be available in npm environment.
|
|
16
|
-
// We inherit stdio so user can see progress/errors if they care,
|
|
17
|
-
// but we catch the error to prevent install failure.
|
|
18
|
-
execSync('node-gyp rebuild', { stdio: 'inherit' });
|
|
19
|
-
|
|
20
|
-
console.log('QHTTPX: Native compilation successful.');
|
|
21
|
-
} catch (error) {
|
|
22
|
-
console.log('QHTTPX: Native compilation failed.');
|
|
23
|
-
console.log('QHTTPX: Falling back to pure JavaScript/WASM adapter.');
|
|
24
|
-
// Exit with 0 to allow installation to proceed
|
|
25
|
-
process.exit(0);
|
|
26
|
-
}
|
package/src/native/README.md
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# QHTTPX Native Addon
|
|
2
|
-
|
|
3
|
-
This directory contains the C++ native addon for QHTTPX to accelerate HTTP parsing and response generation.
|
|
4
|
-
|
|
5
|
-
## Prerequisites
|
|
6
|
-
|
|
7
|
-
To build this addon, you need:
|
|
8
|
-
- **Windows**: Visual Studio Build Tools (Desktop development with C++).
|
|
9
|
-
- **Linux/macOS**: Python 3, make, and a C++ compiler (GCC/Clang).
|
|
10
|
-
|
|
11
|
-
## Building
|
|
12
|
-
|
|
13
|
-
The build is handled automatically by `npm install` if `node-gyp` and build tools are present.
|
|
14
|
-
|
|
15
|
-
To manually rebuild:
|
|
16
|
-
```bash
|
|
17
|
-
npx node-gyp rebuild
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Structure
|
|
21
|
-
|
|
22
|
-
- `addon.cc`: Entry point, registers the module.
|
|
23
|
-
- `server.cc`: `NativeServer` class implementation.
|
|
24
|
-
- `picohttpparser.c/h`: The HTTP parser library (vendored).
|
|
25
|
-
- `index.ts`: TypeScript wrapper and fallback logic.
|
|
26
|
-
|
|
27
|
-
## Usage
|
|
28
|
-
|
|
29
|
-
The `NativeAdapter` in `src/core/native-adapter.ts` automatically detects if the native addon is available.
|
|
30
|
-
If available, it uses `net.Server` + `NativeServer` for handling requests.
|
|
31
|
-
If not, it falls back to the standard `http.Server`.
|