@barqel/barq 1.0.1 → 1.0.2
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 +7 -3
- package/dist/app.d.ts +0 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/context.d.ts +0 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/cookies.d.ts.map +1 -1
- package/dist/cors.d.ts.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +755 -189
- package/dist/index.js.map +18 -18
- package/dist/internal/ip.d.ts.map +1 -1
- package/dist/internal/pathname.d.ts.map +1 -1
- package/dist/internal/query.d.ts.map +1 -1
- package/dist/internal/scan.d.ts.map +1 -1
- package/dist/internal/shutdown.d.ts.map +1 -1
- package/dist/internal/trie.d.ts.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/node-patch.js +755 -189
- package/dist/node-patch.js.map +18 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/package.json +72 -67
package/dist/node-patch.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// src/internal/node-compat.ts
|
|
2
|
+
import cluster from "node:cluster";
|
|
2
3
|
import crypto from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
3
5
|
import http from "node:http";
|
|
6
|
+
import https from "node:https";
|
|
7
|
+
import { cpus as cpus2 } from "node:os";
|
|
4
8
|
import { Readable } from "node:stream";
|
|
5
9
|
|
|
6
10
|
// src/app.ts
|
|
@@ -495,6 +499,86 @@ function parseIpAddress(value) {
|
|
|
495
499
|
return bytes;
|
|
496
500
|
}
|
|
497
501
|
|
|
502
|
+
// src/errors.ts
|
|
503
|
+
class HttpError extends Error {
|
|
504
|
+
status;
|
|
505
|
+
body;
|
|
506
|
+
headers;
|
|
507
|
+
constructor(status, body, headers) {
|
|
508
|
+
if (!Number.isInteger(status) || status < 100 || status > 599) {
|
|
509
|
+
throw new Error(`Invalid HTTP status code: ${status}. Must be an integer between 100 and 599.`);
|
|
510
|
+
}
|
|
511
|
+
super(typeof body === "string" ? body : `HTTP ${status}`);
|
|
512
|
+
this.name = "HttpError";
|
|
513
|
+
this.status = status;
|
|
514
|
+
this.body = body ?? { error: defaultStatusText(status) };
|
|
515
|
+
if (headers !== undefined)
|
|
516
|
+
this.headers = headers;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function defaultStatusText(status) {
|
|
520
|
+
switch (status) {
|
|
521
|
+
case 400:
|
|
522
|
+
return "Bad Request";
|
|
523
|
+
case 401:
|
|
524
|
+
return "Unauthorized";
|
|
525
|
+
case 403:
|
|
526
|
+
return "Forbidden";
|
|
527
|
+
case 404:
|
|
528
|
+
return "Not Found";
|
|
529
|
+
case 405:
|
|
530
|
+
return "Method Not Allowed";
|
|
531
|
+
case 408:
|
|
532
|
+
return "Request Timeout";
|
|
533
|
+
case 409:
|
|
534
|
+
return "Conflict";
|
|
535
|
+
case 413:
|
|
536
|
+
return "Payload Too Large";
|
|
537
|
+
case 418:
|
|
538
|
+
return "I'm a teapot";
|
|
539
|
+
case 422:
|
|
540
|
+
return "Unprocessable Entity";
|
|
541
|
+
case 429:
|
|
542
|
+
return "Too Many Requests";
|
|
543
|
+
case 500:
|
|
544
|
+
return "Internal Server Error";
|
|
545
|
+
case 502:
|
|
546
|
+
return "Bad Gateway";
|
|
547
|
+
case 503:
|
|
548
|
+
return "Service Unavailable";
|
|
549
|
+
case 504:
|
|
550
|
+
return "Gateway Timeout";
|
|
551
|
+
default:
|
|
552
|
+
return status >= 500 ? "Internal Server Error" : "Error";
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function safeJson(status, body, headers) {
|
|
556
|
+
try {
|
|
557
|
+
const init = headers !== undefined ? { status, headers } : { status };
|
|
558
|
+
return json(body, init);
|
|
559
|
+
} catch {
|
|
560
|
+
return new Response("Internal Server Error", {
|
|
561
|
+
status: 500,
|
|
562
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function errorToResponse(err, development) {
|
|
567
|
+
if (err instanceof Response) {
|
|
568
|
+
return err;
|
|
569
|
+
}
|
|
570
|
+
if (err instanceof HttpError) {
|
|
571
|
+
return safeJson(err.status, err.body, err.headers);
|
|
572
|
+
}
|
|
573
|
+
if (development) {
|
|
574
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
575
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
576
|
+
return safeJson(500, { error: "Internal Server Error", message, stack });
|
|
577
|
+
}
|
|
578
|
+
return safeJson(500, { error: "Internal Server Error" });
|
|
579
|
+
}
|
|
580
|
+
var createErrorResponse = errorToResponse;
|
|
581
|
+
|
|
498
582
|
// src/internal/pathname.ts
|
|
499
583
|
function decodeSegment(seg) {
|
|
500
584
|
if (seg.indexOf("%") === -1)
|
|
@@ -541,9 +625,6 @@ function hasDotSegment(path) {
|
|
|
541
625
|
}
|
|
542
626
|
return false;
|
|
543
627
|
}
|
|
544
|
-
function hasEncodedSlash(path) {
|
|
545
|
-
return path.includes("%2f") || path.includes("%2F");
|
|
546
|
-
}
|
|
547
628
|
function targetStart(url) {
|
|
548
629
|
if (url.charCodeAt(0) === 47)
|
|
549
630
|
return 0;
|
|
@@ -596,11 +677,11 @@ function scanPathname(url) {
|
|
|
596
677
|
end = hash;
|
|
597
678
|
}
|
|
598
679
|
const raw = url.slice(slash, end);
|
|
680
|
+
if (raw.indexOf("%00") !== -1 || raw.indexOf("\x00") !== -1) {
|
|
681
|
+
throw new HttpError(400, { error: "Bad Request" });
|
|
682
|
+
}
|
|
599
683
|
if (raw.indexOf(".") === -1 && raw.indexOf("%") === -1)
|
|
600
684
|
return raw;
|
|
601
|
-
if (hasEncodedSlash(raw)) {
|
|
602
|
-
return raw;
|
|
603
|
-
}
|
|
604
685
|
return hasDotSegment(raw) ? normalizeDotSegments(raw) : raw;
|
|
605
686
|
}
|
|
606
687
|
function parseRequestTarget(url) {
|
|
@@ -626,11 +707,10 @@ function parseRequestTarget(url) {
|
|
|
626
707
|
query = hash !== -1 && hash > end ? url.slice(end + 1, hash) : url.slice(end + 1);
|
|
627
708
|
}
|
|
628
709
|
const raw = url.slice(slash, end);
|
|
629
|
-
if (
|
|
630
|
-
|
|
631
|
-
} else {
|
|
632
|
-
pathname = hasDotSegment(raw) ? normalizeDotSegments(raw) : raw;
|
|
710
|
+
if (raw.indexOf("%00") !== -1 || raw.indexOf("\x00") !== -1) {
|
|
711
|
+
throw new HttpError(400, { error: "Bad Request" });
|
|
633
712
|
}
|
|
713
|
+
pathname = hasDotSegment(raw) ? normalizeDotSegments(raw) : raw;
|
|
634
714
|
}
|
|
635
715
|
return { pathname, query };
|
|
636
716
|
}
|
|
@@ -654,17 +734,20 @@ function findQueryParam(url, name, startPos) {
|
|
|
654
734
|
const queryStart = startPos !== undefined && startPos >= 0 ? startPos : url.indexOf("?");
|
|
655
735
|
if (queryStart === -1)
|
|
656
736
|
return null;
|
|
737
|
+
const hashPos = url.indexOf("#", queryStart);
|
|
738
|
+
const len = hashPos === -1 ? url.length : hashPos;
|
|
657
739
|
let s = queryStart + 1;
|
|
658
|
-
const len = url.length;
|
|
659
740
|
const nameLen = name.length;
|
|
660
741
|
while (s < len) {
|
|
661
742
|
if (url.startsWith(name, s)) {
|
|
662
743
|
const eqPos = s + nameLen;
|
|
744
|
+
if (eqPos >= len)
|
|
745
|
+
return "";
|
|
663
746
|
const nextChar = url.charCodeAt(eqPos);
|
|
664
747
|
if (nextChar === 61) {
|
|
665
748
|
const valStart = eqPos + 1;
|
|
666
749
|
let amp = url.indexOf("&", valStart);
|
|
667
|
-
if (amp === -1)
|
|
750
|
+
if (amp === -1 || amp > len)
|
|
668
751
|
amp = len;
|
|
669
752
|
const val = url.slice(valStart, amp);
|
|
670
753
|
if (val.indexOf("+") === -1 && val.indexOf("%") === -1) {
|
|
@@ -672,12 +755,12 @@ function findQueryParam(url, name, startPos) {
|
|
|
672
755
|
}
|
|
673
756
|
return decodeQueryComponent(val);
|
|
674
757
|
}
|
|
675
|
-
if (nextChar === 38
|
|
758
|
+
if (nextChar === 38) {
|
|
676
759
|
return "";
|
|
677
760
|
}
|
|
678
761
|
}
|
|
679
762
|
const amp = url.indexOf("&", s);
|
|
680
|
-
if (amp === -1)
|
|
763
|
+
if (amp === -1 || amp >= len)
|
|
681
764
|
break;
|
|
682
765
|
s = amp + 1;
|
|
683
766
|
}
|
|
@@ -688,12 +771,13 @@ function findQueryParamAll(url, name) {
|
|
|
688
771
|
const queryStart = url.indexOf("?");
|
|
689
772
|
if (queryStart === -1)
|
|
690
773
|
return res;
|
|
691
|
-
const
|
|
774
|
+
const hashPos = url.indexOf("#", queryStart);
|
|
775
|
+
const len = hashPos === -1 ? url.length : hashPos;
|
|
692
776
|
let s = queryStart + 1;
|
|
693
777
|
const nLen = name.length;
|
|
694
778
|
while (s < len) {
|
|
695
779
|
let amp = url.indexOf("&", s);
|
|
696
|
-
if (amp === -1)
|
|
780
|
+
if (amp === -1 || amp > len)
|
|
697
781
|
amp = len;
|
|
698
782
|
let eq = url.indexOf("=", s);
|
|
699
783
|
if (eq === -1 || eq > amp)
|
|
@@ -718,12 +802,13 @@ function parseAllQueryParams(url) {
|
|
|
718
802
|
const qIdx = url.indexOf("?");
|
|
719
803
|
if (qIdx === -1)
|
|
720
804
|
return Object.create(null);
|
|
721
|
-
const
|
|
805
|
+
const hashPos = url.indexOf("#", qIdx);
|
|
806
|
+
const len = hashPos === -1 ? url.length : hashPos;
|
|
722
807
|
const res = Object.create(null);
|
|
723
808
|
let s = qIdx + 1;
|
|
724
809
|
while (s < len) {
|
|
725
810
|
let amp = url.indexOf("&", s);
|
|
726
|
-
if (amp === -1)
|
|
811
|
+
if (amp === -1 || amp > len)
|
|
727
812
|
amp = len;
|
|
728
813
|
let eq = url.indexOf("=", s);
|
|
729
814
|
if (eq === -1 || eq > amp)
|
|
@@ -866,12 +951,10 @@ class Context {
|
|
|
866
951
|
this._getLazy().ip = directIp;
|
|
867
952
|
return directIp;
|
|
868
953
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
return candidate;
|
|
874
|
-
}
|
|
954
|
+
const realIp = this.req.headers.get("x-real-ip")?.trim();
|
|
955
|
+
if (realIp && isValidIpAddress(realIp)) {
|
|
956
|
+
this._getLazy().ip = realIp;
|
|
957
|
+
return realIp;
|
|
875
958
|
}
|
|
876
959
|
this._getLazy().ip = directIp;
|
|
877
960
|
return directIp;
|
|
@@ -985,86 +1068,6 @@ class Context {
|
|
|
985
1068
|
}
|
|
986
1069
|
}
|
|
987
1070
|
|
|
988
|
-
// src/errors.ts
|
|
989
|
-
class HttpError extends Error {
|
|
990
|
-
status;
|
|
991
|
-
body;
|
|
992
|
-
headers;
|
|
993
|
-
constructor(status, body, headers) {
|
|
994
|
-
if (!Number.isInteger(status) || status < 100 || status > 599) {
|
|
995
|
-
throw new Error(`Invalid HTTP status code: ${status}. Must be an integer between 100 and 599.`);
|
|
996
|
-
}
|
|
997
|
-
super(typeof body === "string" ? body : `HTTP ${status}`);
|
|
998
|
-
this.name = "HttpError";
|
|
999
|
-
this.status = status;
|
|
1000
|
-
this.body = body ?? { error: defaultStatusText(status) };
|
|
1001
|
-
if (headers !== undefined)
|
|
1002
|
-
this.headers = headers;
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
function defaultStatusText(status) {
|
|
1006
|
-
switch (status) {
|
|
1007
|
-
case 400:
|
|
1008
|
-
return "Bad Request";
|
|
1009
|
-
case 401:
|
|
1010
|
-
return "Unauthorized";
|
|
1011
|
-
case 403:
|
|
1012
|
-
return "Forbidden";
|
|
1013
|
-
case 404:
|
|
1014
|
-
return "Not Found";
|
|
1015
|
-
case 405:
|
|
1016
|
-
return "Method Not Allowed";
|
|
1017
|
-
case 408:
|
|
1018
|
-
return "Request Timeout";
|
|
1019
|
-
case 409:
|
|
1020
|
-
return "Conflict";
|
|
1021
|
-
case 413:
|
|
1022
|
-
return "Payload Too Large";
|
|
1023
|
-
case 418:
|
|
1024
|
-
return "I'm a teapot";
|
|
1025
|
-
case 422:
|
|
1026
|
-
return "Unprocessable Entity";
|
|
1027
|
-
case 429:
|
|
1028
|
-
return "Too Many Requests";
|
|
1029
|
-
case 500:
|
|
1030
|
-
return "Internal Server Error";
|
|
1031
|
-
case 502:
|
|
1032
|
-
return "Bad Gateway";
|
|
1033
|
-
case 503:
|
|
1034
|
-
return "Service Unavailable";
|
|
1035
|
-
case 504:
|
|
1036
|
-
return "Gateway Timeout";
|
|
1037
|
-
default:
|
|
1038
|
-
return status >= 500 ? "Internal Server Error" : "Error";
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
function safeJson(status, body, headers) {
|
|
1042
|
-
try {
|
|
1043
|
-
const init = headers !== undefined ? { status, headers } : { status };
|
|
1044
|
-
return json(body, init);
|
|
1045
|
-
} catch {
|
|
1046
|
-
return new Response("Internal Server Error", {
|
|
1047
|
-
status: 500,
|
|
1048
|
-
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
function errorToResponse(err, development) {
|
|
1053
|
-
if (err instanceof Response) {
|
|
1054
|
-
return err;
|
|
1055
|
-
}
|
|
1056
|
-
if (err instanceof HttpError) {
|
|
1057
|
-
return safeJson(err.status, err.body, err.headers);
|
|
1058
|
-
}
|
|
1059
|
-
if (development) {
|
|
1060
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1061
|
-
const stack = err instanceof Error ? err.stack : undefined;
|
|
1062
|
-
return safeJson(500, { error: "Internal Server Error", message, stack });
|
|
1063
|
-
}
|
|
1064
|
-
return safeJson(500, { error: "Internal Server Error" });
|
|
1065
|
-
}
|
|
1066
|
-
var createErrorResponse = errorToResponse;
|
|
1067
|
-
|
|
1068
1071
|
// src/internal/scan.ts
|
|
1069
1072
|
var ESCAPE_JSON_CHARS_RE = /[\u0000-\u001f"\\]/;
|
|
1070
1073
|
function escapeJsonString(value) {
|
|
@@ -2071,13 +2074,13 @@ async function stopServer(server, hardTimeoutMs) {
|
|
|
2071
2074
|
let forced = false;
|
|
2072
2075
|
let stopPromise = null;
|
|
2073
2076
|
try {
|
|
2074
|
-
stopPromise = server.stop().then(() => {});
|
|
2077
|
+
stopPromise = Promise.resolve(server.stop()).then(() => {});
|
|
2075
2078
|
await Promise.race([
|
|
2076
2079
|
stopPromise,
|
|
2077
2080
|
new Promise((resolve) => {
|
|
2078
2081
|
timer = setTimeout(() => {
|
|
2079
2082
|
forced = true;
|
|
2080
|
-
stopPromise = server.stop(true).then(() => {});
|
|
2083
|
+
stopPromise = Promise.resolve(server.stop(true)).then(() => {});
|
|
2081
2084
|
resolve();
|
|
2082
2085
|
}, hardTimeoutMs);
|
|
2083
2086
|
timer?.unref?.();
|
|
@@ -2107,7 +2110,6 @@ var METHOD_BITS = {
|
|
|
2107
2110
|
HEAD: 1 << 5,
|
|
2108
2111
|
OPTIONS: 1 << 6
|
|
2109
2112
|
};
|
|
2110
|
-
var _ALL_METHODS_BITMASK = (1 << 7) - 1;
|
|
2111
2113
|
var STATIC_CHILD_SCAN_THRESHOLD = 4;
|
|
2112
2114
|
function methodBit(method) {
|
|
2113
2115
|
return METHOD_BITS[method] ?? 0;
|
|
@@ -2159,10 +2161,10 @@ function cloneNode(node) {
|
|
|
2159
2161
|
var MAX_OPTIONAL_PARAMS = 8;
|
|
2160
2162
|
function parsePattern(pattern) {
|
|
2161
2163
|
if (pattern.length === 0 || pattern.charCodeAt(0) !== 47) {
|
|
2162
|
-
throw new Error(`Invalid route pattern "${pattern}": must start with
|
|
2164
|
+
throw new Error(`Invalid route pattern "${pattern}": must start with /`);
|
|
2163
2165
|
}
|
|
2164
2166
|
if (pattern.indexOf("#") !== -1) {
|
|
2165
|
-
throw new Error(`Invalid route pattern "${pattern}": must not contain
|
|
2167
|
+
throw new Error(`Invalid route pattern "${pattern}": must not contain #`);
|
|
2166
2168
|
}
|
|
2167
2169
|
let p = pattern;
|
|
2168
2170
|
if (p.length > 1 && p.charCodeAt(p.length - 1) === 47) {
|
|
@@ -2176,7 +2178,7 @@ function parsePattern(pattern) {
|
|
|
2176
2178
|
const end = slash === -1 ? p.length : slash;
|
|
2177
2179
|
const seg = p.slice(i, end);
|
|
2178
2180
|
if (seg.length === 0) {
|
|
2179
|
-
throw new Error(`Invalid route pattern "${pattern}": empty segment (
|
|
2181
|
+
throw new Error(`Invalid route pattern "${pattern}": empty segment (//)`);
|
|
2180
2182
|
}
|
|
2181
2183
|
const c0 = seg.charCodeAt(0);
|
|
2182
2184
|
if (seg.includes("?") && !(c0 === 58 && seg.endsWith("?") && seg.indexOf("?") === seg.length - 1)) {
|
|
@@ -2198,7 +2200,7 @@ function parsePattern(pattern) {
|
|
|
2198
2200
|
segments.push(optional ? { kind: "param", name, optional: true } : { kind: "param", name });
|
|
2199
2201
|
} else if (c0 === 42) {
|
|
2200
2202
|
if (end !== p.length) {
|
|
2201
|
-
throw new Error(`Invalid route pattern "${pattern}": wildcard
|
|
2203
|
+
throw new Error(`Invalid route pattern "${pattern}": wildcard * must be the final segment`);
|
|
2202
2204
|
}
|
|
2203
2205
|
const name = seg.length > 1 ? seg.slice(1) : null;
|
|
2204
2206
|
if (name !== null) {
|
|
@@ -2290,7 +2292,7 @@ function matchNode(rootNode, pathname, startI, method, paramNames, paramValues,
|
|
|
2290
2292
|
child = staticChildren.get(pathname.slice(i, segEnd));
|
|
2291
2293
|
}
|
|
2292
2294
|
if (child !== undefined) {
|
|
2293
|
-
const hasParamChildren = node.paramChildren !== null && node.paramChildren.size > 0;
|
|
2295
|
+
const hasParamChildren = node.paramChild !== null || node.paramChildren !== null && node.paramChildren.size > 0;
|
|
2294
2296
|
if (!hasParamChildren && node.wildcardChild === null) {
|
|
2295
2297
|
node = child;
|
|
2296
2298
|
i = nextI;
|
|
@@ -2306,9 +2308,39 @@ function matchNode(rootNode, pathname, startI, method, paramNames, paramValues,
|
|
|
2306
2308
|
}
|
|
2307
2309
|
}
|
|
2308
2310
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2311
|
+
if (node.paramChild !== null && node.paramName !== null) {
|
|
2312
|
+
const child = node.paramChild;
|
|
2313
|
+
const paramName = node.paramName;
|
|
2314
|
+
if (nextI >= len && child.handlers !== null) {
|
|
2315
|
+
let leafH = child.handlers[method];
|
|
2316
|
+
if (leafH === undefined && method === "HEAD")
|
|
2317
|
+
leafH = child.handlers.GET;
|
|
2318
|
+
if (leafH !== undefined) {
|
|
2319
|
+
const paramVal = pathname.indexOf("%", i) !== -1 ? decodeParam(pathname.slice(i, segEnd)) : pathname.slice(i, segEnd);
|
|
2320
|
+
const p = {
|
|
2321
|
+
[paramName]: paramVal,
|
|
2322
|
+
[MATCH_HANDLER]: leafH
|
|
2323
|
+
};
|
|
2324
|
+
if (depth === 0)
|
|
2325
|
+
return p;
|
|
2326
|
+
for (let k = 0;k < depth; k++) {
|
|
2327
|
+
p[paramNames[k]] = paramValues[k];
|
|
2328
|
+
}
|
|
2329
|
+
return p;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
paramNames[depth] = paramName;
|
|
2333
|
+
paramValues[depth] = decodeParam(pathname.slice(i, segEnd));
|
|
2334
|
+
const res = matchNode(child, pathname, nextI, method, paramNames, paramValues, depth + 1);
|
|
2335
|
+
if (res !== null) {
|
|
2336
|
+
if (MATCH_NOT_ALLOWED in res) {
|
|
2337
|
+
notAllowedCandidate ??= res;
|
|
2338
|
+
} else {
|
|
2339
|
+
return res;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
} else if (node.paramChildren !== null && node.paramChildren.size > 0) {
|
|
2343
|
+
for (const [paramName, child] of node.paramChildren) {
|
|
2312
2344
|
if (nextI >= len && child.handlers !== null) {
|
|
2313
2345
|
let leafH = child.handlers[method];
|
|
2314
2346
|
if (leafH === undefined && method === "HEAD")
|
|
@@ -2475,17 +2507,6 @@ class SegmentTrie {
|
|
|
2475
2507
|
this.maxStaticLength = cleanPattern.length;
|
|
2476
2508
|
}
|
|
2477
2509
|
}
|
|
2478
|
-
for (const seg of segments) {
|
|
2479
|
-
if (seg.kind === "static") {} else if (seg.kind === "param") {
|
|
2480
|
-
if (isPureStatic) {
|
|
2481
|
-
throw new Error(`Internal error: param segment in pure static route`);
|
|
2482
|
-
}
|
|
2483
|
-
} else {
|
|
2484
|
-
if (isPureStatic) {
|
|
2485
|
-
throw new Error(`Internal error: wildcard segment in pure static route`);
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
2510
|
let node = this.root;
|
|
2490
2511
|
for (const seg of segments) {
|
|
2491
2512
|
if (seg.kind === "static") {
|
|
@@ -2868,12 +2889,6 @@ var FAST_JSON_HEADERS = new Headers({
|
|
|
2868
2889
|
var FAST_TEXT_HEADERS = new Headers({
|
|
2869
2890
|
"content-type": "text/plain; charset=utf-8"
|
|
2870
2891
|
});
|
|
2871
|
-
var _FAST_JSON_INIT = Object.freeze({
|
|
2872
|
-
headers: FAST_JSON_HEADERS
|
|
2873
|
-
});
|
|
2874
|
-
var _FAST_TEXT_INIT = Object.freeze({
|
|
2875
|
-
headers: FAST_TEXT_HEADERS
|
|
2876
|
-
});
|
|
2877
2892
|
|
|
2878
2893
|
class ReadonlySetView {
|
|
2879
2894
|
source;
|
|
@@ -3054,12 +3069,16 @@ class FastLightContext {
|
|
|
3054
3069
|
return this;
|
|
3055
3070
|
}
|
|
3056
3071
|
get url() {
|
|
3057
|
-
|
|
3072
|
+
if (this._url === undefined) {
|
|
3073
|
+
this._url = this.req.url;
|
|
3074
|
+
}
|
|
3075
|
+
return this._url;
|
|
3058
3076
|
}
|
|
3059
3077
|
get pathname() {
|
|
3060
|
-
if (this._pathname
|
|
3061
|
-
|
|
3062
|
-
|
|
3078
|
+
if (this._pathname === undefined) {
|
|
3079
|
+
this._pathname = scanPathname(this.url);
|
|
3080
|
+
}
|
|
3081
|
+
return this._pathname;
|
|
3063
3082
|
}
|
|
3064
3083
|
queryParam(name) {
|
|
3065
3084
|
let u = this._url;
|
|
@@ -3093,6 +3112,19 @@ class FastLightContext {
|
|
|
3093
3112
|
return this.req.body;
|
|
3094
3113
|
}
|
|
3095
3114
|
}
|
|
3115
|
+
var lightContextPool = [];
|
|
3116
|
+
function acquireLightContext(req, params) {
|
|
3117
|
+
const popped = lightContextPool.pop();
|
|
3118
|
+
if (popped !== undefined) {
|
|
3119
|
+
return popped.init(req, params);
|
|
3120
|
+
}
|
|
3121
|
+
return new FastLightContext(req, params);
|
|
3122
|
+
}
|
|
3123
|
+
function releaseLightContext(ctx) {
|
|
3124
|
+
if (lightContextPool.length < 128) {
|
|
3125
|
+
lightContextPool.push(ctx);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3096
3128
|
function reportWebSocketError(handler, ws, error) {
|
|
3097
3129
|
if (!handler) {
|
|
3098
3130
|
console.error("Unhandled WebSocket callback error:", error);
|
|
@@ -3193,12 +3225,12 @@ function canonicalizeRoutePattern(pattern) {
|
|
|
3193
3225
|
}
|
|
3194
3226
|
function validateRoutePattern(pattern) {
|
|
3195
3227
|
if (pattern.length === 0 || pattern.charCodeAt(0) !== 47) {
|
|
3196
|
-
throw new Error(`Invalid route pattern "${pattern}": must start with
|
|
3228
|
+
throw new Error(`Invalid route pattern "${pattern}": must start with /`);
|
|
3197
3229
|
}
|
|
3198
3230
|
if (pattern === "/")
|
|
3199
3231
|
return;
|
|
3200
3232
|
if (pattern.indexOf("#") !== -1) {
|
|
3201
|
-
throw new Error(`Invalid route pattern "${pattern}": must not contain
|
|
3233
|
+
throw new Error(`Invalid route pattern "${pattern}": must not contain #`);
|
|
3202
3234
|
}
|
|
3203
3235
|
const segments = pattern.split("/");
|
|
3204
3236
|
if (segments.length - 1 > MAX_ROUTE_SEGMENTS) {
|
|
@@ -3207,11 +3239,11 @@ function validateRoutePattern(pattern) {
|
|
|
3207
3239
|
for (let i = 1;i < segments.length; i++) {
|
|
3208
3240
|
const seg = segments[i];
|
|
3209
3241
|
if (seg.length === 0) {
|
|
3210
|
-
throw new Error(`Invalid route pattern "${pattern}": empty segment (
|
|
3242
|
+
throw new Error(`Invalid route pattern "${pattern}": empty segment (//)`);
|
|
3211
3243
|
}
|
|
3212
3244
|
const decoded = decodeSegment2(seg);
|
|
3213
3245
|
if (decoded === "." || decoded === "..") {
|
|
3214
|
-
throw new Error(`Invalid route pattern "${pattern}": dot segments
|
|
3246
|
+
throw new Error(`Invalid route pattern "${pattern}": dot segments ., .. not allowed in route patterns`);
|
|
3215
3247
|
}
|
|
3216
3248
|
}
|
|
3217
3249
|
}
|
|
@@ -3685,11 +3717,15 @@ class App {
|
|
|
3685
3717
|
}
|
|
3686
3718
|
} else if (nr.isParamsOnly && raw) {
|
|
3687
3719
|
const analysis = analyzeHandler(raw);
|
|
3688
|
-
if (analysis.paramSerializer
|
|
3720
|
+
if (analysis.paramSerializer) {
|
|
3689
3721
|
const serializer = analysis.paramSerializer;
|
|
3690
3722
|
const responseInit = { headers: FAST_JSON_HEADERS };
|
|
3691
3723
|
methodMap[nr.method] = (req) => {
|
|
3692
3724
|
const params = req.params ?? EMPTY_PARAMS;
|
|
3725
|
+
const syncRes = raw({ params });
|
|
3726
|
+
if (isThenable(syncRes)) {
|
|
3727
|
+
return Promise.resolve(syncRes).then((val) => val instanceof Response ? val : typeof val === "object" && val !== null ? Response.json(val) : new Response(String(val)));
|
|
3728
|
+
}
|
|
3693
3729
|
return new Response(serializer(params), responseInit);
|
|
3694
3730
|
};
|
|
3695
3731
|
} else {
|
|
@@ -3724,48 +3760,36 @@ class App {
|
|
|
3724
3760
|
methodMap.HEAD = () => new Response(null, { headers: FAST_JSON_HEADERS });
|
|
3725
3761
|
}
|
|
3726
3762
|
} else if (nr.isLightweight && raw) {
|
|
3727
|
-
const isAsync = raw.constructor.name === "AsyncFunction";
|
|
3728
|
-
const reusedLightCtx = new FastLightContext(null, EMPTY_PARAMS);
|
|
3729
|
-
let lightCtxInUse = false;
|
|
3730
3763
|
methodMap[nr.method] = (req) => {
|
|
3731
3764
|
const params = isDyn ? req.params ?? EMPTY_PARAMS : EMPTY_PARAMS;
|
|
3732
|
-
const
|
|
3733
|
-
const ctx = useFresh ? new FastLightContext(req, params) : reusedLightCtx.init(req, params);
|
|
3734
|
-
if (!useFresh)
|
|
3735
|
-
lightCtxInUse = true;
|
|
3765
|
+
const ctx = acquireLightContext(req, params);
|
|
3736
3766
|
let res;
|
|
3737
3767
|
try {
|
|
3738
3768
|
res = raw(ctx);
|
|
3739
3769
|
} catch (err) {
|
|
3740
|
-
|
|
3741
|
-
lightCtxInUse = false;
|
|
3770
|
+
releaseLightContext(ctx);
|
|
3742
3771
|
return errorToResponse(err, this.development);
|
|
3743
3772
|
}
|
|
3744
3773
|
if (typeof res === "object" && res !== null) {
|
|
3745
3774
|
if (res instanceof Response) {
|
|
3746
|
-
|
|
3747
|
-
lightCtxInUse = false;
|
|
3775
|
+
releaseLightContext(ctx);
|
|
3748
3776
|
return res;
|
|
3749
3777
|
}
|
|
3750
3778
|
if (!isThenable(res)) {
|
|
3751
|
-
|
|
3752
|
-
lightCtxInUse = false;
|
|
3779
|
+
releaseLightContext(ctx);
|
|
3753
3780
|
return Response.json(res);
|
|
3754
3781
|
}
|
|
3755
3782
|
} else if (typeof res === "string") {
|
|
3756
|
-
|
|
3757
|
-
lightCtxInUse = false;
|
|
3783
|
+
releaseLightContext(ctx);
|
|
3758
3784
|
return new Response(res, { headers: FAST_TEXT_HEADERS });
|
|
3759
3785
|
} else if (!isThenable(res)) {
|
|
3760
|
-
|
|
3761
|
-
lightCtxInUse = false;
|
|
3786
|
+
releaseLightContext(ctx);
|
|
3762
3787
|
return new Response(JSON.stringify(res), {
|
|
3763
3788
|
headers: FAST_JSON_HEADERS
|
|
3764
3789
|
});
|
|
3765
3790
|
}
|
|
3766
3791
|
return Promise.resolve(res).then((r) => {
|
|
3767
|
-
|
|
3768
|
-
lightCtxInUse = false;
|
|
3792
|
+
releaseLightContext(ctx);
|
|
3769
3793
|
if (r instanceof Response)
|
|
3770
3794
|
return r;
|
|
3771
3795
|
if (typeof r === "object" && r !== null)
|
|
@@ -3776,8 +3800,7 @@ class App {
|
|
|
3776
3800
|
return new Response(r, { headers: FAST_TEXT_HEADERS });
|
|
3777
3801
|
return finalizeValue(r);
|
|
3778
3802
|
}).catch((err) => {
|
|
3779
|
-
|
|
3780
|
-
lightCtxInUse = false;
|
|
3803
|
+
releaseLightContext(ctx);
|
|
3781
3804
|
return errorToResponse(err, this.development);
|
|
3782
3805
|
});
|
|
3783
3806
|
};
|
|
@@ -3824,7 +3847,7 @@ class App {
|
|
|
3824
3847
|
return isThenable(res) ? res.then(stripBody) : stripBody(res);
|
|
3825
3848
|
}
|
|
3826
3849
|
}
|
|
3827
|
-
return new Response(
|
|
3850
|
+
return new Response('{"error":"Method Not Allowed"}', {
|
|
3828
3851
|
status: 405,
|
|
3829
3852
|
headers: {
|
|
3830
3853
|
"content-type": "application/json; charset=utf-8",
|
|
@@ -3973,6 +3996,8 @@ class App {
|
|
|
3973
3996
|
throw new Error("Cannot start cluster without an application entry file");
|
|
3974
3997
|
}
|
|
3975
3998
|
const processes = [];
|
|
3999
|
+
let crashCount = 0;
|
|
4000
|
+
let lastReset = Date.now();
|
|
3976
4001
|
const spawnWorker = () => {
|
|
3977
4002
|
const proc = Bun.spawn([process.execPath, "run", entry], {
|
|
3978
4003
|
env: {
|
|
@@ -3984,10 +4009,25 @@ class App {
|
|
|
3984
4009
|
});
|
|
3985
4010
|
proc.exited.then((_code) => {
|
|
3986
4011
|
if (this.stopPromise === null && this.clusterProcesses !== null) {
|
|
4012
|
+
const now = Date.now();
|
|
4013
|
+
if (now - lastReset > 5000) {
|
|
4014
|
+
crashCount = 0;
|
|
4015
|
+
lastReset = now;
|
|
4016
|
+
}
|
|
4017
|
+
crashCount++;
|
|
3987
4018
|
const idx = processes.indexOf(proc);
|
|
3988
4019
|
if (idx !== -1) {
|
|
3989
4020
|
processes.splice(idx, 1);
|
|
3990
4021
|
}
|
|
4022
|
+
if (crashCount > 15) {
|
|
4023
|
+
console.error("[Barq Cluster] Workers restarting too fast. Throttling respawn.");
|
|
4024
|
+
setTimeout(() => {
|
|
4025
|
+
if (this.stopPromise === null && this.clusterProcesses !== null) {
|
|
4026
|
+
processes.push(spawnWorker());
|
|
4027
|
+
}
|
|
4028
|
+
}, 2000);
|
|
4029
|
+
return;
|
|
4030
|
+
}
|
|
3991
4031
|
processes.push(spawnWorker());
|
|
3992
4032
|
}
|
|
3993
4033
|
});
|
|
@@ -4096,6 +4136,300 @@ function stripBody(res) {
|
|
|
4096
4136
|
|
|
4097
4137
|
// src/internal/node-compat.ts
|
|
4098
4138
|
var isNode = typeof Bun === "undefined";
|
|
4139
|
+
var WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
4140
|
+
var nodeTopics = new Map;
|
|
4141
|
+
|
|
4142
|
+
class NodeServerWebSocket {
|
|
4143
|
+
socket;
|
|
4144
|
+
data;
|
|
4145
|
+
readyState = 1;
|
|
4146
|
+
remoteAddress;
|
|
4147
|
+
topics = new Set;
|
|
4148
|
+
buffer = Buffer.alloc(0);
|
|
4149
|
+
fragments = [];
|
|
4150
|
+
fragmentOpcode = 0;
|
|
4151
|
+
maxPayloadLength;
|
|
4152
|
+
handlers;
|
|
4153
|
+
appHandlers;
|
|
4154
|
+
constructor(socket, data, wsOptions) {
|
|
4155
|
+
this.socket = socket;
|
|
4156
|
+
this.data = data;
|
|
4157
|
+
this.remoteAddress = ("remoteAddress" in socket ? socket.remoteAddress : "") || "";
|
|
4158
|
+
this.maxPayloadLength = wsOptions?.maxPayloadLength ?? 16 * 1024 * 1024;
|
|
4159
|
+
this.appHandlers = wsOptions;
|
|
4160
|
+
const internal = data?._barqInternal ? data.handlers : undefined;
|
|
4161
|
+
this.handlers = internal;
|
|
4162
|
+
socket.on("data", (chunk) => this.onData(chunk));
|
|
4163
|
+
socket.on("close", () => this.onClose(1006, ""));
|
|
4164
|
+
socket.on("error", (err) => this.onError(err));
|
|
4165
|
+
socket.on("drain", () => this.onDrain());
|
|
4166
|
+
}
|
|
4167
|
+
send(data) {
|
|
4168
|
+
if (this.readyState !== 1)
|
|
4169
|
+
return -1;
|
|
4170
|
+
let payload;
|
|
4171
|
+
let isText = true;
|
|
4172
|
+
if (typeof data === "string") {
|
|
4173
|
+
payload = Buffer.from(data, "utf8");
|
|
4174
|
+
isText = true;
|
|
4175
|
+
} else if (Buffer.isBuffer(data)) {
|
|
4176
|
+
payload = data;
|
|
4177
|
+
isText = false;
|
|
4178
|
+
} else if (ArrayBuffer.isView(data)) {
|
|
4179
|
+
payload = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
4180
|
+
isText = false;
|
|
4181
|
+
} else {
|
|
4182
|
+
payload = Buffer.from(String(data), "utf8");
|
|
4183
|
+
isText = true;
|
|
4184
|
+
}
|
|
4185
|
+
this.sendFrame(isText ? 1 : 2, payload);
|
|
4186
|
+
return payload.length;
|
|
4187
|
+
}
|
|
4188
|
+
subscribe(room) {
|
|
4189
|
+
let set = nodeTopics.get(room);
|
|
4190
|
+
if (!set) {
|
|
4191
|
+
set = new Set;
|
|
4192
|
+
nodeTopics.set(room, set);
|
|
4193
|
+
}
|
|
4194
|
+
set.add(this);
|
|
4195
|
+
this.topics.add(room);
|
|
4196
|
+
}
|
|
4197
|
+
unsubscribe(room) {
|
|
4198
|
+
const set = nodeTopics.get(room);
|
|
4199
|
+
if (set) {
|
|
4200
|
+
set.delete(this);
|
|
4201
|
+
if (set.size === 0)
|
|
4202
|
+
nodeTopics.delete(room);
|
|
4203
|
+
}
|
|
4204
|
+
this.topics.delete(room);
|
|
4205
|
+
}
|
|
4206
|
+
isSubscribed(room) {
|
|
4207
|
+
return this.topics.has(room);
|
|
4208
|
+
}
|
|
4209
|
+
publish(room, message, _compress) {
|
|
4210
|
+
const set = nodeTopics.get(room);
|
|
4211
|
+
if (!set || set.size === 0)
|
|
4212
|
+
return 0;
|
|
4213
|
+
let sent = 0;
|
|
4214
|
+
for (const client of set) {
|
|
4215
|
+
if (client !== this && client.readyState === 1) {
|
|
4216
|
+
client.send(message);
|
|
4217
|
+
sent++;
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
return sent;
|
|
4221
|
+
}
|
|
4222
|
+
ping(data = "") {
|
|
4223
|
+
if (this.readyState !== 1)
|
|
4224
|
+
return;
|
|
4225
|
+
const buf = typeof data === "string" ? Buffer.from(data, "utf8") : data;
|
|
4226
|
+
this.sendFrame(9, buf);
|
|
4227
|
+
}
|
|
4228
|
+
pong(data = "") {
|
|
4229
|
+
if (this.readyState !== 1)
|
|
4230
|
+
return;
|
|
4231
|
+
const buf = typeof data === "string" ? Buffer.from(data, "utf8") : data;
|
|
4232
|
+
this.sendFrame(10, buf);
|
|
4233
|
+
}
|
|
4234
|
+
cork(callback) {
|
|
4235
|
+
return callback(this);
|
|
4236
|
+
}
|
|
4237
|
+
close(code = 1000, reason = "") {
|
|
4238
|
+
if (this.readyState >= 2)
|
|
4239
|
+
return;
|
|
4240
|
+
this.readyState = 2;
|
|
4241
|
+
const reasonBuf = Buffer.from(reason, "utf8");
|
|
4242
|
+
const payload = Buffer.allocUnsafe(2 + reasonBuf.length);
|
|
4243
|
+
payload.writeUInt16BE(code, 0);
|
|
4244
|
+
reasonBuf.copy(payload, 2);
|
|
4245
|
+
this.sendFrame(8, payload);
|
|
4246
|
+
this.socket.end();
|
|
4247
|
+
}
|
|
4248
|
+
terminate() {
|
|
4249
|
+
this.readyState = 3;
|
|
4250
|
+
this.socket.destroy();
|
|
4251
|
+
}
|
|
4252
|
+
sendFrame(opcode, payload) {
|
|
4253
|
+
if (this.socket.destroyed)
|
|
4254
|
+
return;
|
|
4255
|
+
const len = payload.length;
|
|
4256
|
+
let header;
|
|
4257
|
+
if (len <= 125) {
|
|
4258
|
+
header = Buffer.allocUnsafe(2);
|
|
4259
|
+
header[0] = 128 | opcode & 15;
|
|
4260
|
+
header[1] = len;
|
|
4261
|
+
} else if (len <= 65535) {
|
|
4262
|
+
header = Buffer.allocUnsafe(4);
|
|
4263
|
+
header[0] = 128 | opcode & 15;
|
|
4264
|
+
header[1] = 126;
|
|
4265
|
+
header.writeUInt16BE(len, 2);
|
|
4266
|
+
} else {
|
|
4267
|
+
header = Buffer.allocUnsafe(10);
|
|
4268
|
+
header[0] = 128 | opcode & 15;
|
|
4269
|
+
header[1] = 127;
|
|
4270
|
+
header.writeBigUInt64BE(BigInt(len), 2);
|
|
4271
|
+
}
|
|
4272
|
+
this.socket.write(Buffer.concat([header, payload]));
|
|
4273
|
+
}
|
|
4274
|
+
onData(chunk) {
|
|
4275
|
+
this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
|
|
4276
|
+
while (this.buffer.length >= 2) {
|
|
4277
|
+
const byte0 = this.buffer[0];
|
|
4278
|
+
const byte1 = this.buffer[1];
|
|
4279
|
+
const fin = (byte0 & 128) !== 0;
|
|
4280
|
+
const opcode = byte0 & 15;
|
|
4281
|
+
const masked = (byte1 & 128) !== 0;
|
|
4282
|
+
if (!masked) {
|
|
4283
|
+
this.close(1002, "Protocol error: unmasked client frame");
|
|
4284
|
+
this.onClose(1002, "Protocol error: unmasked client frame");
|
|
4285
|
+
return;
|
|
4286
|
+
}
|
|
4287
|
+
let payloadLen = byte1 & 127;
|
|
4288
|
+
let offset = 2;
|
|
4289
|
+
if (payloadLen === 126) {
|
|
4290
|
+
if (this.buffer.length < 4)
|
|
4291
|
+
return;
|
|
4292
|
+
payloadLen = this.buffer.readUInt16BE(2);
|
|
4293
|
+
offset = 4;
|
|
4294
|
+
} else if (payloadLen === 127) {
|
|
4295
|
+
if (this.buffer.length < 10)
|
|
4296
|
+
return;
|
|
4297
|
+
const high = this.buffer.readUInt32BE(2);
|
|
4298
|
+
const low = this.buffer.readUInt32BE(6);
|
|
4299
|
+
payloadLen = high * 4294967296 + low;
|
|
4300
|
+
offset = 10;
|
|
4301
|
+
}
|
|
4302
|
+
if (payloadLen > this.maxPayloadLength) {
|
|
4303
|
+
this.close(1009, "Message Too Big");
|
|
4304
|
+
return;
|
|
4305
|
+
}
|
|
4306
|
+
const maskLen = 4;
|
|
4307
|
+
const totalLen = offset + maskLen + payloadLen;
|
|
4308
|
+
if (this.buffer.length < totalLen)
|
|
4309
|
+
return;
|
|
4310
|
+
const maskKey = this.buffer.subarray(offset, offset + 4);
|
|
4311
|
+
const rawPayload = this.buffer.subarray(offset + maskLen, totalLen);
|
|
4312
|
+
const payload = Buffer.allocUnsafe(payloadLen);
|
|
4313
|
+
for (let i = 0;i < payloadLen; i++) {
|
|
4314
|
+
payload[i] = rawPayload[i] ^ maskKey[i & 3];
|
|
4315
|
+
}
|
|
4316
|
+
this.buffer = this.buffer.subarray(totalLen);
|
|
4317
|
+
if (opcode === 8) {
|
|
4318
|
+
let code = 1000;
|
|
4319
|
+
let reason = "";
|
|
4320
|
+
if (payload.length >= 2) {
|
|
4321
|
+
code = payload.readUInt16BE(0);
|
|
4322
|
+
if (payload.length > 2) {
|
|
4323
|
+
reason = payload.subarray(2).toString("utf8");
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
if (this.readyState === 1) {
|
|
4327
|
+
this.close(code, reason);
|
|
4328
|
+
}
|
|
4329
|
+
this.onClose(code, reason);
|
|
4330
|
+
return;
|
|
4331
|
+
}
|
|
4332
|
+
if (opcode === 9) {
|
|
4333
|
+
this.sendFrame(10, payload);
|
|
4334
|
+
continue;
|
|
4335
|
+
}
|
|
4336
|
+
if (opcode === 10) {
|
|
4337
|
+
continue;
|
|
4338
|
+
}
|
|
4339
|
+
if (!fin) {
|
|
4340
|
+
if (opcode !== 0) {
|
|
4341
|
+
this.fragmentOpcode = opcode;
|
|
4342
|
+
this.fragments = [payload];
|
|
4343
|
+
} else {
|
|
4344
|
+
this.fragments.push(payload);
|
|
4345
|
+
}
|
|
4346
|
+
continue;
|
|
4347
|
+
}
|
|
4348
|
+
let fullPayload = payload;
|
|
4349
|
+
let effectiveOpcode = opcode;
|
|
4350
|
+
if (opcode === 0) {
|
|
4351
|
+
this.fragments.push(payload);
|
|
4352
|
+
fullPayload = Buffer.concat(this.fragments);
|
|
4353
|
+
effectiveOpcode = this.fragmentOpcode;
|
|
4354
|
+
this.fragments = [];
|
|
4355
|
+
this.fragmentOpcode = 0;
|
|
4356
|
+
}
|
|
4357
|
+
const message = effectiveOpcode === 1 ? fullPayload.toString("utf8") : fullPayload;
|
|
4358
|
+
this.onMessage(message);
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4361
|
+
dispatchOpen() {
|
|
4362
|
+
try {
|
|
4363
|
+
this.handlers?.open?.(this);
|
|
4364
|
+
this.appHandlers?.open?.(this);
|
|
4365
|
+
} catch (err) {
|
|
4366
|
+
this.onError(err);
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
onMessage(msg) {
|
|
4370
|
+
try {
|
|
4371
|
+
this.handlers?.message?.(this, msg);
|
|
4372
|
+
this.appHandlers?.message?.(this, msg);
|
|
4373
|
+
} catch (err) {
|
|
4374
|
+
this.onError(err);
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
onClose(code, reason) {
|
|
4378
|
+
if (this.readyState === 3)
|
|
4379
|
+
return;
|
|
4380
|
+
this.readyState = 3;
|
|
4381
|
+
for (const room of this.topics) {
|
|
4382
|
+
const set = nodeTopics.get(room);
|
|
4383
|
+
if (set) {
|
|
4384
|
+
set.delete(this);
|
|
4385
|
+
if (set.size === 0)
|
|
4386
|
+
nodeTopics.delete(room);
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
this.topics.clear();
|
|
4390
|
+
try {
|
|
4391
|
+
this.handlers?.close?.(this, code, reason);
|
|
4392
|
+
this.appHandlers?.close?.(this, code, reason);
|
|
4393
|
+
} catch (err) {
|
|
4394
|
+
this.onError(err);
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
onError(err) {
|
|
4398
|
+
try {
|
|
4399
|
+
if (this.handlers?.error) {
|
|
4400
|
+
this.handlers.error(this, err);
|
|
4401
|
+
} else if (this.appHandlers?.error) {
|
|
4402
|
+
this.appHandlers.error(this, err);
|
|
4403
|
+
}
|
|
4404
|
+
} catch (e) {
|
|
4405
|
+
console.error("Unhandled WebSocket error:", e);
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
onDrain() {
|
|
4409
|
+
try {
|
|
4410
|
+
this.handlers?.drain?.(this);
|
|
4411
|
+
this.appHandlers?.drain?.(this);
|
|
4412
|
+
} catch (err) {
|
|
4413
|
+
this.onError(err);
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
function resolveTlsData(input) {
|
|
4418
|
+
if (!input)
|
|
4419
|
+
return;
|
|
4420
|
+
if (Buffer.isBuffer(input) || typeof input === "string") {
|
|
4421
|
+
if (typeof input === "string" && !input.includes(`
|
|
4422
|
+
`) && (input.startsWith("./") || input.startsWith("/") || input.includes("."))) {
|
|
4423
|
+
try {
|
|
4424
|
+
if (fs.existsSync(input)) {
|
|
4425
|
+
return fs.readFileSync(input);
|
|
4426
|
+
}
|
|
4427
|
+
} catch {}
|
|
4428
|
+
}
|
|
4429
|
+
return input;
|
|
4430
|
+
}
|
|
4431
|
+
return;
|
|
4432
|
+
}
|
|
4099
4433
|
if (isNode) {
|
|
4100
4434
|
if (typeof globalThis.Bun === "undefined") {
|
|
4101
4435
|
Object.defineProperty(globalThis, "Bun", {
|
|
@@ -4119,15 +4453,59 @@ if (isNode) {
|
|
|
4119
4453
|
});
|
|
4120
4454
|
}
|
|
4121
4455
|
App.prototype.listen = function(port) {
|
|
4122
|
-
const
|
|
4123
|
-
const
|
|
4124
|
-
const
|
|
4125
|
-
const
|
|
4456
|
+
const app = this;
|
|
4457
|
+
const rawPort = port ?? app._options?.port ?? 3000;
|
|
4458
|
+
const hostname = app._options?.hostname ?? "0.0.0.0";
|
|
4459
|
+
const activeSockets = new Set;
|
|
4460
|
+
let inFlightRequests = 0;
|
|
4461
|
+
let isDraining = false;
|
|
4462
|
+
const fastRoutes = new Map;
|
|
4463
|
+
if (app.nativeRoutes && !app.hasMiddlewares && app.errorResolver === undefined) {
|
|
4464
|
+
for (const nr of app.nativeRoutes) {
|
|
4465
|
+
if (!nr.isDynamic && nr.isContextFree && nr.rawHandler) {
|
|
4466
|
+
fastRoutes.set(`${nr.method}:${nr.pattern}`, nr.rawHandler);
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
const requestListener = async (req, res) => {
|
|
4471
|
+
inFlightRequests++;
|
|
4472
|
+
if (isDraining) {
|
|
4473
|
+
res.setHeader("Connection", "close");
|
|
4474
|
+
}
|
|
4475
|
+
const onFinished = () => {
|
|
4476
|
+
inFlightRequests--;
|
|
4477
|
+
res.removeListener("finish", onFinished);
|
|
4478
|
+
res.removeListener("close", onFinished);
|
|
4479
|
+
};
|
|
4480
|
+
res.on("finish", onFinished);
|
|
4481
|
+
res.on("close", onFinished);
|
|
4482
|
+
const method = req.method || "GET";
|
|
4483
|
+
const rawUrl = req.url || "/";
|
|
4484
|
+
const pathname = scanPathname(rawUrl);
|
|
4485
|
+
const fastHandler = fastRoutes.get(`${method}:${pathname}`);
|
|
4486
|
+
if (fastHandler) {
|
|
4487
|
+
try {
|
|
4488
|
+
const val = fastHandler();
|
|
4489
|
+
if (typeof val === "string") {
|
|
4490
|
+
res.writeHead(200, {
|
|
4491
|
+
"content-type": "text/plain; charset=utf-8"
|
|
4492
|
+
});
|
|
4493
|
+
res.end(val);
|
|
4494
|
+
return;
|
|
4495
|
+
}
|
|
4496
|
+
if (typeof val === "object" && val !== null && !(val instanceof Response)) {
|
|
4497
|
+
res.writeHead(200, {
|
|
4498
|
+
"content-type": "application/json; charset=utf-8"
|
|
4499
|
+
});
|
|
4500
|
+
res.end(JSON.stringify(val));
|
|
4501
|
+
return;
|
|
4502
|
+
}
|
|
4503
|
+
} catch {}
|
|
4504
|
+
}
|
|
4126
4505
|
try {
|
|
4127
4506
|
const protocol = req.socket?.encrypted ? "https" : "http";
|
|
4128
4507
|
const host = req.headers.host || "localhost";
|
|
4129
|
-
const url = `${protocol}://${host}${
|
|
4130
|
-
const method = req.method || "GET";
|
|
4508
|
+
const url = `${protocol}://${host}${rawUrl}`;
|
|
4131
4509
|
let reqBody;
|
|
4132
4510
|
if (method !== "GET" && method !== "HEAD") {
|
|
4133
4511
|
reqBody = Readable.toWeb(req);
|
|
@@ -4139,40 +4517,228 @@ if (isNode) {
|
|
|
4139
4517
|
duplex: "half"
|
|
4140
4518
|
});
|
|
4141
4519
|
const remoteIp = req.socket.remoteAddress || "";
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4520
|
+
let upgraded = false;
|
|
4521
|
+
const serverShim = {
|
|
4522
|
+
requestIP: () => remoteIp ? { address: remoteIp } : null,
|
|
4523
|
+
upgrade: (_request, options) => {
|
|
4524
|
+
const secKey = req.headers["sec-websocket-key"];
|
|
4525
|
+
if (!secKey || typeof secKey !== "string")
|
|
4526
|
+
return false;
|
|
4527
|
+
const acceptKey = crypto.createHash("sha1").update(secKey + WS_GUID).digest("base64");
|
|
4528
|
+
req.socket.write(`HTTP/1.1 101 Switching Protocols\r
|
|
4529
|
+
` + `Upgrade: websocket\r
|
|
4530
|
+
` + `Connection: Upgrade\r
|
|
4531
|
+
` + `Sec-WebSocket-Accept: ${acceptKey}\r
|
|
4532
|
+
\r
|
|
4533
|
+
`);
|
|
4534
|
+
const ws = new NodeServerWebSocket(req.socket, options?.data, app._options?.websocket);
|
|
4535
|
+
upgraded = true;
|
|
4536
|
+
ws.dispatchOpen();
|
|
4537
|
+
return true;
|
|
4538
|
+
}
|
|
4539
|
+
};
|
|
4540
|
+
const webRes = await app.handle(webReq, serverShim);
|
|
4541
|
+
if (upgraded)
|
|
4542
|
+
return;
|
|
4543
|
+
const resHeaders = {};
|
|
4147
4544
|
webRes.headers.forEach((val, key) => {
|
|
4148
|
-
|
|
4545
|
+
resHeaders[key] = val;
|
|
4149
4546
|
});
|
|
4150
|
-
|
|
4547
|
+
const getSetCookie = webRes.headers.getSetCookie;
|
|
4548
|
+
if (typeof getSetCookie === "function") {
|
|
4549
|
+
const cookies = getSetCookie.call(webRes.headers);
|
|
4550
|
+
if (cookies && cookies.length > 0) {
|
|
4551
|
+
resHeaders["set-cookie"] = cookies;
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
res.writeHead(webRes.status, webRes.statusText || undefined, resHeaders);
|
|
4555
|
+
if (webRes.body) {
|
|
4556
|
+
const nodeStream = Readable.fromWeb(webRes.body);
|
|
4557
|
+
nodeStream.pipe(res);
|
|
4558
|
+
} else {
|
|
4151
4559
|
res.end();
|
|
4152
|
-
return;
|
|
4153
4560
|
}
|
|
4154
|
-
const nodeStream = Readable.fromWeb(webRes.body);
|
|
4155
|
-
nodeStream.pipe(res);
|
|
4156
4561
|
} catch (err) {
|
|
4157
4562
|
console.error("Barq Node Adapter Error:", err);
|
|
4158
4563
|
if (!res.headersSent) {
|
|
4159
|
-
res.
|
|
4160
|
-
|
|
4564
|
+
res.writeHead(500, {
|
|
4565
|
+
"content-type": "application/json; charset=utf-8"
|
|
4566
|
+
});
|
|
4161
4567
|
res.end('{"error":"Internal Server Error"}');
|
|
4162
4568
|
}
|
|
4163
4569
|
}
|
|
4570
|
+
};
|
|
4571
|
+
const tlsConfig = app._options?.tls;
|
|
4572
|
+
const hasTls = Boolean(tlsConfig && (tlsConfig.key || tlsConfig.cert));
|
|
4573
|
+
const server = hasTls ? https.createServer({
|
|
4574
|
+
key: resolveTlsData(tlsConfig?.key),
|
|
4575
|
+
cert: resolveTlsData(tlsConfig?.cert),
|
|
4576
|
+
ca: resolveTlsData(tlsConfig?.ca),
|
|
4577
|
+
passphrase: tlsConfig?.passphrase
|
|
4578
|
+
}, requestListener) : http.createServer(requestListener);
|
|
4579
|
+
server.on("connection", (socket) => {
|
|
4580
|
+
activeSockets.add(socket);
|
|
4581
|
+
socket.on("close", () => activeSockets.delete(socket));
|
|
4164
4582
|
});
|
|
4165
|
-
server.
|
|
4166
|
-
|
|
4167
|
-
|
|
4583
|
+
server.on("upgrade", async (req, socket, head) => {
|
|
4584
|
+
activeSockets.add(socket);
|
|
4585
|
+
socket.on("close", () => activeSockets.delete(socket));
|
|
4586
|
+
const protocol = socket?.encrypted ? "https" : "http";
|
|
4587
|
+
const host = req.headers.host || "localhost";
|
|
4588
|
+
const fullUrl = `${protocol}://${host}${req.url || "/"}`;
|
|
4589
|
+
let upgradedSocket = null;
|
|
4590
|
+
const serverShim = {
|
|
4591
|
+
requestIP: () => ({
|
|
4592
|
+
address: ("remoteAddress" in socket ? socket.remoteAddress : "") || ""
|
|
4593
|
+
}),
|
|
4594
|
+
upgrade: (_request, options) => {
|
|
4595
|
+
const secKey = req.headers["sec-websocket-key"];
|
|
4596
|
+
if (!secKey || typeof secKey !== "string")
|
|
4597
|
+
return false;
|
|
4598
|
+
const acceptKey = crypto.createHash("sha1").update(secKey + WS_GUID).digest("base64");
|
|
4599
|
+
socket.write(`HTTP/1.1 101 Switching Protocols\r
|
|
4600
|
+
` + `Upgrade: websocket\r
|
|
4601
|
+
` + `Connection: Upgrade\r
|
|
4602
|
+
` + `Sec-WebSocket-Accept: ${acceptKey}\r
|
|
4603
|
+
\r
|
|
4604
|
+
`);
|
|
4605
|
+
const ws = new NodeServerWebSocket(socket, options?.data, app._options?.websocket);
|
|
4606
|
+
upgradedSocket = ws;
|
|
4607
|
+
if (head && head.length > 0) {
|
|
4608
|
+
socket.unshift(head);
|
|
4609
|
+
}
|
|
4610
|
+
ws.dispatchOpen();
|
|
4611
|
+
return true;
|
|
4612
|
+
}
|
|
4613
|
+
};
|
|
4614
|
+
const webReq = new Request(fullUrl, {
|
|
4615
|
+
method: "GET",
|
|
4616
|
+
headers: req.headers
|
|
4617
|
+
});
|
|
4618
|
+
try {
|
|
4619
|
+
const webRes = await app.handle(webReq, serverShim);
|
|
4620
|
+
if (!upgradedSocket) {
|
|
4621
|
+
const status = webRes.status || 400;
|
|
4622
|
+
const statusText = webRes.statusText || "Bad Request";
|
|
4623
|
+
socket.write(`HTTP/1.1 ${status} ${statusText}\r
|
|
4624
|
+
Connection: close\r
|
|
4625
|
+
\r
|
|
4626
|
+
`);
|
|
4627
|
+
socket.destroy();
|
|
4628
|
+
}
|
|
4629
|
+
} catch {
|
|
4630
|
+
if (!upgradedSocket) {
|
|
4631
|
+
socket.write(`HTTP/1.1 500 Internal Server Error\r
|
|
4632
|
+
Connection: close\r
|
|
4633
|
+
\r
|
|
4634
|
+
`);
|
|
4635
|
+
socket.destroy();
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
});
|
|
4639
|
+
server.listen(rawPort, hostname);
|
|
4640
|
+
const serverHandle = {
|
|
4641
|
+
get port() {
|
|
4642
|
+
const addr = server.address();
|
|
4643
|
+
return addr && typeof addr === "object" ? addr.port : rawPort;
|
|
4644
|
+
},
|
|
4168
4645
|
hostname,
|
|
4169
|
-
stop: () => new Promise((resolve) => {
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4646
|
+
stop: (hardTimeoutMs) => new Promise((resolve) => {
|
|
4647
|
+
if (hardTimeoutMs === true) {
|
|
4648
|
+
for (const socket of activeSockets) {
|
|
4649
|
+
socket.destroy();
|
|
4650
|
+
}
|
|
4651
|
+
activeSockets.clear();
|
|
4652
|
+
server.close(() => resolve({ forced: true, pending: 0 }));
|
|
4653
|
+
return;
|
|
4654
|
+
}
|
|
4655
|
+
isDraining = true;
|
|
4656
|
+
const timeout = typeof hardTimeoutMs === "number" ? hardTimeoutMs : 5000;
|
|
4657
|
+
let timer;
|
|
4658
|
+
let resolved = false;
|
|
4659
|
+
const done = (forced) => {
|
|
4660
|
+
if (resolved)
|
|
4661
|
+
return;
|
|
4662
|
+
resolved = true;
|
|
4663
|
+
if (timer !== undefined)
|
|
4664
|
+
clearTimeout(timer);
|
|
4665
|
+
const pending = activeSockets.size;
|
|
4666
|
+
if (forced) {
|
|
4667
|
+
for (const socket of activeSockets) {
|
|
4668
|
+
socket.destroy();
|
|
4669
|
+
}
|
|
4670
|
+
activeSockets.clear();
|
|
4671
|
+
}
|
|
4672
|
+
server.close(() => resolve({ forced, pending: forced ? pending : 0 }));
|
|
4673
|
+
};
|
|
4674
|
+
timer = setTimeout(() => done(true), timeout);
|
|
4675
|
+
if (typeof timer.unref === "function")
|
|
4676
|
+
timer.unref();
|
|
4677
|
+
server.close(() => {
|
|
4678
|
+
if (!resolved) {
|
|
4679
|
+
resolved = true;
|
|
4680
|
+
if (timer !== undefined)
|
|
4681
|
+
clearTimeout(timer);
|
|
4682
|
+
resolve({ forced: false, pending: 0 });
|
|
4683
|
+
}
|
|
4684
|
+
});
|
|
4685
|
+
if (inFlightRequests === 0) {
|
|
4686
|
+
done(false);
|
|
4173
4687
|
}
|
|
4174
4688
|
})
|
|
4175
4689
|
};
|
|
4690
|
+
return serverHandle;
|
|
4691
|
+
};
|
|
4692
|
+
App.prototype.cluster = function(port) {
|
|
4693
|
+
const app = this;
|
|
4694
|
+
const rawPort = port ?? app._options?.port ?? 3000;
|
|
4695
|
+
const hostname = app._options?.hostname ?? "0.0.0.0";
|
|
4696
|
+
const listenPort = rawPort === 0 ? 3000 : rawPort;
|
|
4697
|
+
if (cluster.isPrimary) {
|
|
4698
|
+
const numCPUs = cpus2().length;
|
|
4699
|
+
const workers = [];
|
|
4700
|
+
let crashCount = 0;
|
|
4701
|
+
let lastReset = Date.now();
|
|
4702
|
+
const spawnWorker = () => {
|
|
4703
|
+
const worker = cluster.fork({
|
|
4704
|
+
...process.env,
|
|
4705
|
+
PORT: String(listenPort)
|
|
4706
|
+
});
|
|
4707
|
+
worker.on("exit", () => {
|
|
4708
|
+
const now = Date.now();
|
|
4709
|
+
if (now - lastReset > 5000) {
|
|
4710
|
+
crashCount = 0;
|
|
4711
|
+
lastReset = now;
|
|
4712
|
+
}
|
|
4713
|
+
crashCount++;
|
|
4714
|
+
const idx = workers.indexOf(worker);
|
|
4715
|
+
if (idx !== -1)
|
|
4716
|
+
workers.splice(idx, 1);
|
|
4717
|
+
if (crashCount > 15) {
|
|
4718
|
+
console.error("[Barq Cluster] Node workers restarting too fast. Throttling respawn.");
|
|
4719
|
+
setTimeout(() => {
|
|
4720
|
+
workers.push(spawnWorker());
|
|
4721
|
+
}, 2000);
|
|
4722
|
+
return;
|
|
4723
|
+
}
|
|
4724
|
+
workers.push(spawnWorker());
|
|
4725
|
+
});
|
|
4726
|
+
return worker;
|
|
4727
|
+
};
|
|
4728
|
+
for (let i = 0;i < numCPUs; i++) {
|
|
4729
|
+
workers.push(spawnWorker());
|
|
4730
|
+
}
|
|
4731
|
+
return {
|
|
4732
|
+
port: listenPort,
|
|
4733
|
+
hostname,
|
|
4734
|
+
stop: async () => {
|
|
4735
|
+
for (const w of workers) {
|
|
4736
|
+
w.kill("SIGTERM");
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
};
|
|
4740
|
+
}
|
|
4741
|
+
return this.listen(listenPort);
|
|
4176
4742
|
};
|
|
4177
4743
|
}
|
|
4178
4744
|
// src/cors.ts
|
|
@@ -4301,4 +4867,4 @@ export {
|
|
|
4301
4867
|
validateJson
|
|
4302
4868
|
};
|
|
4303
4869
|
|
|
4304
|
-
//# debugId=
|
|
4870
|
+
//# debugId=9C2F2B32771A43B764756E2164756E21
|