@nodefony/http 10.0.0-beta.2 → 10.0.0-beta.4
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/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
- package/dist/index.js +19 -11
- package/dist/nodefony/command/assetsPublishCommand.js +5 -3
- package/dist/nodefony/command/certificatesCommand.js +5 -3
- package/dist/nodefony/command/networkCommand.js +5 -3
- package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
- package/dist/nodefony/config/config.js +45 -6
- package/dist/nodefony/config/defineModuleConfig.js +2 -3
- package/dist/nodefony/interfaces/IReverseProxy.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +2 -3
- package/dist/nodefony/service/ProfilerAdminApi.js +1 -2
- package/dist/nodefony/service/audit-logger.js +22 -16
- package/dist/nodefony/service/certificates.js +228 -19
- package/dist/nodefony/service/error-renderer.js +30 -13
- package/dist/nodefony/service/http-kernel.js +331 -82
- package/dist/nodefony/service/pretty-request-logger.js +5 -4
- package/dist/nodefony/service/request-logger.js +24 -16
- package/dist/nodefony/service/reverse-proxy.js +336 -0
- package/dist/nodefony/service/servers/clientError.js +22 -3
- package/dist/nodefony/service/servers/server-h2c.js +126 -0
- package/dist/nodefony/service/servers/server-http.js +5 -8
- package/dist/nodefony/service/servers/server-https.js +12 -24
- package/dist/nodefony/service/servers/server-static.js +7 -10
- package/dist/nodefony/service/servers/server-websocket-secure.js +18 -18
- package/dist/nodefony/service/servers/server-websocket.js +18 -18
- package/dist/nodefony/service/servers/serverShutdown.js +78 -18
- package/dist/nodefony/service/servers/wsHeartbeat.js +32 -17
- package/dist/nodefony/service/sessions/sessions-service.js +34 -20
- package/dist/nodefony/service/trace.js +1 -2
- package/dist/nodefony/service/upload/upload-service.js +5 -8
- package/dist/nodefony/service/x509.js +1 -2
- package/dist/nodefony/src/assets/collectAssets.js +1 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
- package/dist/nodefony/src/context/Context.js +51 -32
- package/dist/nodefony/src/context/domainMatcher.js +21 -4
- package/dist/nodefony/src/context/forwarded.js +3 -4
- package/dist/nodefony/src/context/http/HttpContext.js +98 -46
- package/dist/nodefony/src/context/http/Request.js +34 -38
- package/dist/nodefony/src/context/http/Response.js +39 -7
- package/dist/nodefony/src/context/http/SseStream.js +349 -0
- package/dist/nodefony/src/context/http/parser.js +36 -10
- package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
- package/dist/nodefony/src/context/http2/Request.js +4 -8
- package/dist/nodefony/src/context/http2/Response.js +22 -9
- package/dist/nodefony/src/context/metaData.js +1 -2
- package/dist/nodefony/src/context/requestId.js +1 -2
- package/dist/nodefony/src/context/responseEnded.js +26 -0
- package/dist/nodefony/src/context/transport.js +45 -0
- package/dist/nodefony/src/context/trustProxy.js +1 -2
- package/dist/nodefony/src/context/websocket/Response.js +42 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +94 -20
- package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
- package/dist/nodefony/src/context/websocket/websocketUrl.js +20 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
- package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
- package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
- package/dist/nodefony/src/cookies/cookie.js +3 -7
- package/dist/nodefony/src/errors/httpError.js +1 -2
- package/dist/nodefony/src/errors/thrownMessage.js +1 -2
- package/dist/nodefony/src/profiler/FrameProfile.js +1 -2
- package/dist/nodefony/src/profiler/Profiler.js +108 -30
- package/dist/nodefony/src/profiler/aggregate.js +422 -0
- package/dist/nodefony/src/proxy/forward.js +500 -0
- package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
- package/dist/nodefony/src/proxy/rules.js +188 -0
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
- package/dist/nodefony/src/servers/http2Limits.js +50 -0
- package/dist/nodefony/src/servers/kernelServers.js +12 -3
- package/dist/nodefony/src/servers/portBinder.js +41 -10
- package/dist/nodefony/src/servers/upgradeDispatch.js +128 -0
- package/dist/nodefony/src/session/session.js +30 -15
- package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
- package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
- package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
- package/dist/types/index.d.ts +12 -3
- package/dist/types/nodefony/config/config.d.ts +16 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +65 -4
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +67 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +6 -0
- package/dist/types/nodefony/interfaces/index.d.ts +3 -2
- package/dist/types/nodefony/service/audit-logger.d.ts +2 -1
- package/dist/types/nodefony/service/certificates.d.ts +147 -5
- package/dist/types/nodefony/service/error-renderer.d.ts +32 -1
- package/dist/types/nodefony/service/http-kernel.d.ts +157 -15
- package/dist/types/nodefony/service/reverse-proxy.d.ts +156 -0
- package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
- package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
- package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +2 -3
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +2 -3
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
- package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +26 -0
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +19 -1
- package/dist/types/nodefony/src/context/Context.d.ts +35 -6
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +11 -1
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +48 -1
- package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
- package/dist/types/nodefony/src/context/http/Response.d.ts +14 -0
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +155 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
- package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +15 -5
- package/dist/types/nodefony/src/context/responseEnded.d.ts +20 -0
- package/dist/types/nodefony/src/context/transport.d.ts +53 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +57 -7
- package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
- package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +16 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +13 -12
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +69 -15
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +215 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +168 -0
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +88 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
- package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
- package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +69 -0
- package/dist/types/nodefony/src/session/session.d.ts +2 -0
- package/docs/cookies.md +85 -78
- package/docs/index.md +5 -1
- package/docs/observabilite.md +81 -81
- package/docs/rate-limit.md +83 -83
- package/docs/reverse-proxy.md +522 -0
- package/docs/servers.md +378 -230
- package/docs/session.md +180 -180
- package/docs/sse.md +287 -0
- package/docs/upload.md +120 -117
- package/package.json +10 -11
- package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import HttpError from "../../errors/httpError.js";
|
|
2
|
-
import { escapeRegExp
|
|
2
|
+
import { escapeRegExp } from "nodefony";
|
|
3
3
|
import QS from "qs";
|
|
4
4
|
import xml2js from "xml2js";
|
|
5
|
-
|
|
5
|
+
// nodefony/src/context/http/parser.ts
|
|
6
6
|
var Parser = class {
|
|
7
7
|
request;
|
|
8
8
|
chunks;
|
|
@@ -73,6 +73,27 @@ var Parser = class {
|
|
|
73
73
|
return this;
|
|
74
74
|
}
|
|
75
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Corps d'un type que le framework ne décode pas : livré TEL QU'IL EST REÇU,
|
|
78
|
+
* jamais remplacé par un objet vide qui mentirait.
|
|
79
|
+
*
|
|
80
|
+
* - `text/*` → une chaîne, décodée au `charset` du `Content-Type` (UTF-8 par
|
|
81
|
+
* défaut) — comme une trame texte RFC 6455 ;
|
|
82
|
+
* - tout autre type, et un corps sans `Content-Type` (RFC 9110 §8.3 : supposé
|
|
83
|
+
* `application/octet-stream`) → les OCTETS : le `Buffer` déjà assemblé dans
|
|
84
|
+
* `request.data`, sans copie — donc un `Uint8Array`, le type d'octets que
|
|
85
|
+
* la socket livrera aussi (#618). Un même schéma passe par les deux portes.
|
|
86
|
+
*
|
|
87
|
+
* Un corps VIDE laisse `body` tel quel : une requête sans charge ne change pas.
|
|
88
|
+
*/
|
|
89
|
+
var ParserRaw = class extends Parser {
|
|
90
|
+
async parse() {
|
|
91
|
+
await super.parse();
|
|
92
|
+
const data = this.request.data;
|
|
93
|
+
if (data.length > 0) this.request.body = this.request.contentType?.startsWith("text/") ? data.toString(this.request.charset) : data;
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
76
97
|
var ParserQs = class extends Parser {
|
|
77
98
|
parserOptions;
|
|
78
99
|
charset = "utf8";
|
|
@@ -83,8 +104,7 @@ var ParserQs = class extends Parser {
|
|
|
83
104
|
}
|
|
84
105
|
async parse() {
|
|
85
106
|
await super.parse();
|
|
86
|
-
this.request.
|
|
87
|
-
this.request.query = extend({}, this.request.query, this.request.queryPost);
|
|
107
|
+
this.request.body = QS.parse(this.request.data.toString(this.charset), this.parserOptions);
|
|
88
108
|
this.request.context.requestEnded = true;
|
|
89
109
|
return this;
|
|
90
110
|
}
|
|
@@ -102,7 +122,7 @@ var ParserXml = class extends Parser {
|
|
|
102
122
|
return new Promise((resolve, reject) => {
|
|
103
123
|
this.xmlParser.parseString(this.request.data.toString(this.charset), (err, result) => {
|
|
104
124
|
if (err) return reject(err);
|
|
105
|
-
this.request.
|
|
125
|
+
this.request.body = result;
|
|
106
126
|
this.request.context.requestEnded = true;
|
|
107
127
|
return resolve(this);
|
|
108
128
|
});
|
|
@@ -110,7 +130,14 @@ var ParserXml = class extends Parser {
|
|
|
110
130
|
}
|
|
111
131
|
};
|
|
112
132
|
/**
|
|
113
|
-
*
|
|
133
|
+
* Vrai pour un objet ou un tableau JSON — ce que `body` peut porter.
|
|
134
|
+
* Un tableau passe : `@Body()` le rend tel quel (envoi en lot).
|
|
135
|
+
*/
|
|
136
|
+
function isJsonContainer(value) {
|
|
137
|
+
return typeof value === "object" && value !== null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Parse un corps `application/json` (ou `*+json`) en objet → `body`
|
|
114
141
|
* (source lue par le décorateur `@Body`). Reprend le rôle de l'ancien plugin
|
|
115
142
|
* `json` de formidable, supprimé avec le passage à busboy (multipart seul).
|
|
116
143
|
* Lenient : un corps JSON malformé est ignoré (pas de throw — `parse()` est
|
|
@@ -127,8 +154,8 @@ var ParserJson = class extends Parser {
|
|
|
127
154
|
await super.parse();
|
|
128
155
|
const text = this.request.data.toString(this.charset).trim();
|
|
129
156
|
if (text) try {
|
|
130
|
-
|
|
131
|
-
|
|
157
|
+
const parsed = JSON.parse(text);
|
|
158
|
+
if (isJsonContainer(parsed)) this.request.body = parsed;
|
|
132
159
|
} catch {}
|
|
133
160
|
this.request.context.requestEnded = true;
|
|
134
161
|
return this;
|
|
@@ -183,5 +210,4 @@ const acceptParser = function(acc) {
|
|
|
183
210
|
return ACCEPT_ANY;
|
|
184
211
|
}
|
|
185
212
|
};
|
|
186
|
-
|
|
187
|
-
export { Parser, ParserJson, ParserQs, ParserXml, acceptParser };
|
|
213
|
+
export { Parser, ParserJson, ParserQs, ParserRaw, ParserXml, acceptParser };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/context/http/urlFastPath.ts
|
|
2
2
|
const PATH_SAFE = /* @__PURE__ */ new Uint8Array(128);
|
|
3
3
|
const SEARCH_SAFE = /* @__PURE__ */ new Uint8Array(128);
|
|
4
4
|
const HOST_SAFE = /* @__PURE__ */ new Uint8Array(128);
|
|
@@ -123,5 +123,4 @@ function isDigitsFrom(s, from) {
|
|
|
123
123
|
}
|
|
124
124
|
return true;
|
|
125
125
|
}
|
|
126
|
-
//#endregion
|
|
127
126
|
export { isCanonicalAuthority, splitTarget };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { requestAuthority } from "../transport.js";
|
|
1
2
|
import HttpRequest from "../http/Request.js";
|
|
2
|
-
|
|
3
|
+
// nodefony/src/context/http2/Request.ts
|
|
3
4
|
var Http2Request = class extends HttpRequest {
|
|
4
5
|
request;
|
|
5
6
|
constructor(request, context) {
|
|
@@ -7,8 +8,9 @@ var Http2Request = class extends HttpRequest {
|
|
|
7
8
|
this.context = context;
|
|
8
9
|
this.request = request;
|
|
9
10
|
}
|
|
11
|
+
/** `:authority`, sinon `Host` — cf `requestAuthority` (RFC 9113 §8.3.1). */
|
|
10
12
|
getHost() {
|
|
11
|
-
return this.headers
|
|
13
|
+
return requestAuthority(this.request.headers);
|
|
12
14
|
}
|
|
13
15
|
getUserAgent() {
|
|
14
16
|
return this.headers["user-agent"];
|
|
@@ -19,11 +21,5 @@ var Http2Request = class extends HttpRequest {
|
|
|
19
21
|
getRawTarget() {
|
|
20
22
|
return this.headers[":path"];
|
|
21
23
|
}
|
|
22
|
-
resolveScheme() {
|
|
23
|
-
if (this.forwarded?.proto) return this.forwarded.proto;
|
|
24
|
-
if (this.headers[":scheme"] === "https") return "https";
|
|
25
|
-
return "http";
|
|
26
|
-
}
|
|
27
24
|
};
|
|
28
|
-
//#endregion
|
|
29
25
|
export { Http2Request as default };
|
|
@@ -2,8 +2,13 @@ import HttpResponse from "../http/Response.js";
|
|
|
2
2
|
import { thenMaybe } from "nodefony";
|
|
3
3
|
import http from "node:http";
|
|
4
4
|
import http2 from "node:http2";
|
|
5
|
-
|
|
5
|
+
// nodefony/src/context/http2/Response.ts
|
|
6
6
|
const HTTP2_HEADER_STATUS = http2.constants.HTTP2_HEADER_STATUS;
|
|
7
|
+
/** Réponse Node déjà libérée : aucun en-tête posé à reprendre. */
|
|
8
|
+
const emptyHeaders = {
|
|
9
|
+
getHeaderNames: () => [],
|
|
10
|
+
getHeader: () => void 0
|
|
11
|
+
};
|
|
7
12
|
/**
|
|
8
13
|
* Compose les en-têtes d'une réponse HTTP/2 : ceux posés sur la réponse, puis
|
|
9
14
|
* ceux passés à `writeHead`, qui gagnent.
|
|
@@ -14,14 +19,19 @@ const HTTP2_HEADER_STATUS = http2.constants.HTTP2_HEADER_STATUS;
|
|
|
14
19
|
* (`ERR_HTTP2_HEADER_SINGLE_VALUE`), soit une 500 sur tout média servi en
|
|
15
20
|
* HTTP/2. HTTP/2 n'a de toute façon que des noms en minuscules (RFC 9113 §8.2.1).
|
|
16
21
|
*
|
|
22
|
+
* Les en-têtes posés se lisent nom par nom (`getHeaderNames`, déjà en
|
|
23
|
+
* minuscules) : `getHeaders()` les recopiait d'abord dans un objet sans
|
|
24
|
+
* prototype, avant cette fusion — 3,8 µs par réponse pour 12 en-têtes,
|
|
25
|
+
* contre 1,1 µs en une passe.
|
|
26
|
+
*
|
|
17
27
|
* @param statusMessage - message de statut, recopié en `x-status-message`
|
|
18
|
-
* @param current - en-têtes déjà posés
|
|
28
|
+
* @param current - la réponse Node qui porte les en-têtes déjà posés
|
|
19
29
|
* @param extra - en-têtes passés à `writeHead` (objet, ou liste plate clé/valeur)
|
|
20
30
|
* @returns les en-têtes à transmettre à `respond()`
|
|
21
31
|
*/
|
|
22
32
|
function mergeResponseHeaders(statusMessage, current, extra) {
|
|
23
33
|
const merged = { "x-status-message": statusMessage };
|
|
24
|
-
for (const
|
|
34
|
+
for (const name of current.getHeaderNames()) merged[name] = current.getHeader(name);
|
|
25
35
|
if (Array.isArray(extra)) for (let i = 0; i + 1 < extra.length; i += 2) merged[String(extra[i]).toLowerCase()] = extra[i + 1];
|
|
26
36
|
else if (extra) for (const key in extra) {
|
|
27
37
|
const value = extra[key];
|
|
@@ -58,7 +68,7 @@ var Http2Response = class extends HttpResponse {
|
|
|
58
68
|
this.statusMessage = this.getStatusMessage();
|
|
59
69
|
this.setLength();
|
|
60
70
|
this.ensureContentTypeHeader();
|
|
61
|
-
this.headers = mergeResponseHeaders(this.statusMessage, this.
|
|
71
|
+
this.headers = mergeResponseHeaders(this.statusMessage, this.response ?? emptyHeaders, headers);
|
|
62
72
|
this.headers[HTTP2_HEADER_STATUS] = this.statusCode;
|
|
63
73
|
const requestId = this.context.requestId;
|
|
64
74
|
if (requestId) this.headers["x-request-id"] = requestId;
|
|
@@ -69,9 +79,9 @@ var Http2Response = class extends HttpResponse {
|
|
|
69
79
|
} else return super.writeHead(statusCode, headers);
|
|
70
80
|
}
|
|
71
81
|
/**
|
|
72
|
-
* Écrit le corps sur le flux HTTP/2
|
|
73
|
-
*
|
|
74
|
-
*
|
|
82
|
+
* Écrit le corps sur le flux HTTP/2 et, pour une réponse unique, le termine
|
|
83
|
+
* d'un seul `end(corps)`. Synchrone, sauf en streaming (`flush()`) : une
|
|
84
|
+
* promesse réglée par le rappel d'écriture, la contre-pression (#505).
|
|
75
85
|
*
|
|
76
86
|
* @returns la réponse, ou sa promesse.
|
|
77
87
|
*/
|
|
@@ -87,6 +97,10 @@ var Http2Response = class extends HttpResponse {
|
|
|
87
97
|
return this;
|
|
88
98
|
}
|
|
89
99
|
if (chunk) this.setBody(chunk);
|
|
100
|
+
if (!_flush && !this.flushing) {
|
|
101
|
+
stream.end(this.payload, this.payloadEncoding);
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
90
104
|
return new Promise((resolve, reject) => {
|
|
91
105
|
stream.write(this.payload, this.payloadEncoding, (error) => {
|
|
92
106
|
if (error) return reject(error);
|
|
@@ -102,7 +116,7 @@ var Http2Response = class extends HttpResponse {
|
|
|
102
116
|
*/
|
|
103
117
|
end(chunk, encoding) {
|
|
104
118
|
if (this.stream) {
|
|
105
|
-
if (this.stream.destroyed || this.stream.closed) return this.stream;
|
|
119
|
+
if (this.stream.writableEnded || this.stream.destroyed || this.stream.closed) return this.stream;
|
|
106
120
|
return this.stream.end(chunk, encoding || this.encoding);
|
|
107
121
|
}
|
|
108
122
|
return super.end(chunk, encoding);
|
|
@@ -121,5 +135,4 @@ var Http2Response = class extends HttpResponse {
|
|
|
121
135
|
};
|
|
122
136
|
}
|
|
123
137
|
};
|
|
124
|
-
//#endregion
|
|
125
138
|
export { Http2Response as default, mergeResponseHeaders };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/context/metaData.ts
|
|
2
2
|
/**
|
|
3
3
|
* Assemble l'enveloppe `metaData` d'une requête EN PLACE (builder monomorphe) :
|
|
4
4
|
* met à jour les champs de `target.nodefony` dans un ordre fixe → V8 garde une
|
|
@@ -43,5 +43,4 @@ function buildMetaData(target, src, override) {
|
|
|
43
43
|
}
|
|
44
44
|
return target;
|
|
45
45
|
}
|
|
46
|
-
//#endregion
|
|
47
46
|
export { buildMetaData };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/context/requestId.ts
|
|
2
2
|
/**
|
|
3
3
|
* Validation de l'identifiant de corrélation `X-Request-Id` fourni par le
|
|
4
4
|
* client, avant qu'il ne soit adopté comme `Context.requestId`.
|
|
@@ -37,5 +37,4 @@ function sanitizeRequestId(raw) {
|
|
|
37
37
|
if (!raw || raw.length > 128) return null;
|
|
38
38
|
return SAFE_REQUEST_ID.test(raw) ? raw : null;
|
|
39
39
|
}
|
|
40
|
-
//#endregion
|
|
41
40
|
export { MAX_REQUEST_ID_LENGTH, sanitizeRequestId };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import http2 from "node:http2";
|
|
2
|
+
// nodefony/src/context/responseEnded.ts
|
|
3
|
+
/**
|
|
4
|
+
* La réponse a-t-elle été TERMINÉE par le serveur ? Règle UNIQUE, pour HTTP/1.1
|
|
5
|
+
* comme pour HTTP/2 — l'écouteur `close` du noyau (client parti → 499) et le
|
|
6
|
+
* timeout par flux de `HttpContext` l'appellent tous deux.
|
|
7
|
+
*
|
|
8
|
+
* Sous HTTP/2, la réponse Nodefony écrit et termine le FLUX
|
|
9
|
+
* (`stream.respond`/`stream.end`), jamais la réponse de compatibilité : son
|
|
10
|
+
* `writableEnded` reste à `false` alors que le client a tout reçu. Mais le
|
|
11
|
+
* flux seul ne suffit pas non plus : un flux ANNULÉ par le pair (RST_STREAM
|
|
12
|
+
* avant la fin) a LUI AUSSI `writableEnded === true`, Node terminant le côté
|
|
13
|
+
* écriture en le détruisant. `aborted` sépare la fin posée par le serveur de la
|
|
14
|
+
* coupure du client — même sémantique que `writableEnded` en HTTP/1.1.
|
|
15
|
+
*
|
|
16
|
+
* @param response - la réponse Node de la requête
|
|
17
|
+
* @returns `true` si le serveur a terminé la réponse (ou son flux HTTP/2) ;
|
|
18
|
+
* `false` s'il ne l'a pas terminée, client parti compris
|
|
19
|
+
*/
|
|
20
|
+
function responseEnded(response) {
|
|
21
|
+
if (response.writableEnded) return true;
|
|
22
|
+
if (!(response instanceof http2.Http2ServerResponse)) return false;
|
|
23
|
+
const stream = response.stream;
|
|
24
|
+
return stream.writableEnded && !stream.aborted;
|
|
25
|
+
}
|
|
26
|
+
export { responseEnded };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// nodefony/src/context/transport.ts
|
|
2
|
+
/**
|
|
3
|
+
* Famille du protocole traité par un contexte, déduite du serveur qui l'a
|
|
4
|
+
* reçu. Règle UNIQUE : `Context` la pose à la construction.
|
|
5
|
+
*
|
|
6
|
+
* @param serverType - identité du serveur (`http`, `https`, `ws`, `wss`)
|
|
7
|
+
* @returns `"websocket"` pour `ws`/`wss`, `"http"` sinon
|
|
8
|
+
*/
|
|
9
|
+
function protocolNameOf(serverType) {
|
|
10
|
+
return serverType === "ws" || serverType === "wss" ? "websocket" : "http";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Protocole du saut qui a porté cette requête jusqu'au processus, au sens du
|
|
14
|
+
* registre IANA des identifiants ALPN. Règle UNIQUE : `HttpContext` la lit pour
|
|
15
|
+
* choisir ses classes de requête et de réponse.
|
|
16
|
+
*
|
|
17
|
+
* La valeur se CONSTATE sur la requête, elle ne se déduit pas du serveur : un
|
|
18
|
+
* même serveur TLS reçoit HTTP/1.1 et h2 (`allowHTTP1`). Pour h2, c'est la
|
|
19
|
+
* session de Node qui tranche — `Http2Session.alpnProtocol` vaut `"h2c"` sur
|
|
20
|
+
* une session en clair, l'ALPN négocié (`"h2"`) sur une session TLS.
|
|
21
|
+
*
|
|
22
|
+
* @param request - la requête Node reçue par le serveur
|
|
23
|
+
* @returns `"http/1.1"`, `"h2"` ou `"h2c"`
|
|
24
|
+
*/
|
|
25
|
+
function alpnProtocolOf(request) {
|
|
26
|
+
if (request.httpVersionMajor !== 2) return "http/1.1";
|
|
27
|
+
return (request.stream?.session)?.alpnProtocol === "h2c" ? "h2c" : "h2";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Autorité (hôte et port) d'une requête, HTTP/1.1 comme HTTP/2. Règle UNIQUE
|
|
31
|
+
* des lecteurs du serveur.
|
|
32
|
+
*
|
|
33
|
+
* `:authority` d'abord, puis `Host` (RFC 9113 §8.3.1). Le repli n'est pas un
|
|
34
|
+
* confort : un intermédiaire qui convertit une requête HTTP/1.1 de forme origine
|
|
35
|
+
* (`GET /chemin`) en HTTP/2 ne DOIT PAS fabriquer `:authority` — il garde
|
|
36
|
+
* `Host`. C'est ce que fait HAProxy `proto h2` vers un port h2c : sans ce repli,
|
|
37
|
+
* l'hôte est introuvable et la requête part en 421.
|
|
38
|
+
*
|
|
39
|
+
* @param headers - en-têtes de la requête Node (`IncomingMessage` ou `Http2ServerRequest`).
|
|
40
|
+
* @returns l'autorité, ou `undefined` si la requête n'en porte aucune.
|
|
41
|
+
*/
|
|
42
|
+
function requestAuthority(headers) {
|
|
43
|
+
return headers[":authority"] ?? headers.host;
|
|
44
|
+
}
|
|
45
|
+
export { alpnProtocolOf, protocolNameOf, requestAuthority };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BlockList, isIPv4, isIPv6 } from "node:net";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/context/trustProxy.ts
|
|
3
3
|
const PRESETS = {
|
|
4
4
|
loopback: [[
|
|
5
5
|
"127.0.0.0",
|
|
@@ -164,5 +164,4 @@ function extractClientIp(xff, socketAddress, checker) {
|
|
|
164
164
|
}
|
|
165
165
|
return resolveFromRight(s.split(",").map((part) => part.trim()).filter(Boolean), socketAddress, checker);
|
|
166
166
|
}
|
|
167
|
-
//#endregion
|
|
168
167
|
export { buildTrustProxy, extractClientIp, resolveFromRight };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import Cookie from "../../cookies/cookie.js";
|
|
2
2
|
import { decideSend, readBackpressureOptions } from "./wsBackpressure.js";
|
|
3
3
|
import { toWsCloseReason } from "./wsCloseReason.js";
|
|
4
|
+
import { RequestContext } from "nodefony";
|
|
4
5
|
import http from "node:http";
|
|
5
6
|
import Ws from "ws";
|
|
6
|
-
|
|
7
|
+
// nodefony/src/context/websocket/Response.ts
|
|
7
8
|
/**
|
|
8
9
|
* Codes d'erreur d'écriture « le client est PARTI » : reload de page, onglet
|
|
9
10
|
* fermé, HMR — la socket TCP est morte côté client pendant qu'une frame était
|
|
@@ -70,7 +71,11 @@ var WebsocketResponse = class {
|
|
|
70
71
|
const payload = data ?? this.body;
|
|
71
72
|
if (payload == null) throw new Error("no data");
|
|
72
73
|
return new Promise((resolve, reject) => {
|
|
73
|
-
if (!this.connection || this.connection.readyState
|
|
74
|
+
if (!this.connection || this.connection.readyState === Ws.CONNECTING) return reject(/* @__PURE__ */ new Error("WebSocket not open"));
|
|
75
|
+
if (this.connection.readyState !== Ws.OPEN) {
|
|
76
|
+
this.log("WS send: client déjà déconnecté — frame perdue", "DEBUG");
|
|
77
|
+
return resolve(this);
|
|
78
|
+
}
|
|
74
79
|
const { max, policy, closeAfterDrops } = readBackpressureOptions(this.context.server);
|
|
75
80
|
const decision = decideSend(this.connection, max, policy, closeAfterDrops);
|
|
76
81
|
if (decision !== "send") {
|
|
@@ -121,7 +126,7 @@ var WebsocketResponse = class {
|
|
|
121
126
|
setBody(ele, encoding) {
|
|
122
127
|
if (typeof ele === "string") this.body = Buffer.from(ele, encoding ?? this.encoding);
|
|
123
128
|
else if (ele instanceof ArrayBuffer || ele instanceof SharedArrayBuffer) this.body = Buffer.from(ele);
|
|
124
|
-
else if (
|
|
129
|
+
else if (ArrayBuffer.isView(ele)) this.body = Buffer.from(ele.buffer, ele.byteOffset, ele.byteLength);
|
|
125
130
|
return this.body;
|
|
126
131
|
}
|
|
127
132
|
drop(reasonCode, description) {
|
|
@@ -156,6 +161,14 @@ var WebsocketResponse = class {
|
|
|
156
161
|
if (isNaN(status)) status = 500;
|
|
157
162
|
}
|
|
158
163
|
if (!status) status = 500;
|
|
164
|
+
const sink = RequestContext.get()?.renderSink;
|
|
165
|
+
if (sink) {
|
|
166
|
+
sink.status = status;
|
|
167
|
+
return {
|
|
168
|
+
code: status,
|
|
169
|
+
message: message ?? ""
|
|
170
|
+
};
|
|
171
|
+
}
|
|
159
172
|
this.statusCode = status;
|
|
160
173
|
if (!message) message = WS_CLOSE_DESCRIPTIONS[this.statusCode] ?? http.STATUS_CODES[this.statusCode];
|
|
161
174
|
this.statusMessage = message ?? "";
|
|
@@ -167,14 +180,38 @@ var WebsocketResponse = class {
|
|
|
167
180
|
clean() {
|
|
168
181
|
this.connection = null;
|
|
169
182
|
this.body = null;
|
|
183
|
+
this.handshakeHeaders = null;
|
|
170
184
|
}
|
|
171
185
|
setEncoding(encoding) {
|
|
172
186
|
return this.encoding = encoding;
|
|
173
187
|
}
|
|
174
|
-
|
|
188
|
+
/**
|
|
189
|
+
* En-têtes de la réponse au handshake, retenus tant que la socket n'est pas
|
|
190
|
+
* acceptée : ils ne partent que dans un REFUS (`refuseHandshake`) — le `101`
|
|
191
|
+
* de `ws` ne les porte pas. `null` tant que personne n'en pose (le cas
|
|
192
|
+
* nominal : aucune allocation).
|
|
193
|
+
*/
|
|
194
|
+
handshakeHeaders = null;
|
|
195
|
+
/**
|
|
196
|
+
* Retient un en-tête du handshake (cf {@link handshakeHeaders}) ; sans effet
|
|
197
|
+
* une fois la socket ouverte, où il n'y a plus de réponse HTTP.
|
|
198
|
+
*
|
|
199
|
+
* @param name - nom de l'en-tête
|
|
200
|
+
* @param value - valeur
|
|
201
|
+
* @returns toujours `true`
|
|
202
|
+
*/
|
|
203
|
+
setHeader(name, value) {
|
|
204
|
+
if (this.connection !== null) return true;
|
|
205
|
+
this.handshakeHeaders ??= {};
|
|
206
|
+
this.handshakeHeaders[name] = typeof value === "object" ? value.join(", ") : value;
|
|
175
207
|
return true;
|
|
176
208
|
}
|
|
177
|
-
setHeaders() {
|
|
209
|
+
setHeaders(obj) {
|
|
210
|
+
if (this.connection !== null) return true;
|
|
211
|
+
for (const name of Object.keys(obj)) {
|
|
212
|
+
const value = obj[name];
|
|
213
|
+
if (value !== void 0) this.setHeader(name, value);
|
|
214
|
+
}
|
|
178
215
|
return true;
|
|
179
216
|
}
|
|
180
217
|
addCookie(cookie) {
|
|
@@ -184,5 +221,4 @@ var WebsocketResponse = class {
|
|
|
184
221
|
setCookies() {}
|
|
185
222
|
setCookie(_cookie) {}
|
|
186
223
|
};
|
|
187
|
-
//#endregion
|
|
188
224
|
export { WebsocketResponse as default, isPeerGoneError };
|
|
@@ -5,11 +5,12 @@ import WebsocketResponse from "./Response.js";
|
|
|
5
5
|
import { sanitizeRequestId } from "../requestId.js";
|
|
6
6
|
import { FrameProfile } from "../../profiler/FrameProfile.js";
|
|
7
7
|
import { formatWsLogContent } from "./wsLogContent.js";
|
|
8
|
+
import { parseWebsocketUrl } from "./websocketUrl.js";
|
|
8
9
|
import { RequestContext, logColor } from "nodefony";
|
|
9
|
-
import Ws from "ws";
|
|
10
10
|
import { AsyncResource } from "node:async_hooks";
|
|
11
11
|
import url, { URL } from "node:url";
|
|
12
|
-
|
|
12
|
+
import Ws from "ws";
|
|
13
|
+
// nodefony/src/context/websocket/WebsocketContext.ts
|
|
13
14
|
/**
|
|
14
15
|
* Appelle l'action résolue et rend TOUJOURS une promesse.
|
|
15
16
|
*
|
|
@@ -53,6 +54,51 @@ function toWsCloseCode(code) {
|
|
|
53
54
|
return 1011;
|
|
54
55
|
}
|
|
55
56
|
/**
|
|
57
|
+
* Statut HTTP d'un handshake REFUSÉ avant le `101` (RFC 6455 §4.2.2) — la
|
|
58
|
+
* réciproque de {@link toWsCloseCode} : une erreur levée pendant la décision
|
|
59
|
+
* porte un code HTTP (pare-feu, garde) ou, par héritage, un code de fermeture
|
|
60
|
+
* WS (origine `1008`, protocole `1002`, introuvable `4004`) ; le client qui
|
|
61
|
+
* lit le statut doit y reconnaître le même sens.
|
|
62
|
+
*
|
|
63
|
+
* @param code - code porté par l'erreur
|
|
64
|
+
* @returns un statut HTTP 4xx/5xx
|
|
65
|
+
*/
|
|
66
|
+
function toHandshakeStatus(code) {
|
|
67
|
+
if (typeof code !== "number" || !Number.isInteger(code)) return 500;
|
|
68
|
+
if (code >= 400 && code <= 599) return code;
|
|
69
|
+
switch (code) {
|
|
70
|
+
case 1002:
|
|
71
|
+
case 1003:
|
|
72
|
+
case 1007: return 400;
|
|
73
|
+
case 1008:
|
|
74
|
+
case 4003: return 403;
|
|
75
|
+
case 4001: return 401;
|
|
76
|
+
case 1009: return 413;
|
|
77
|
+
case 1013: return 503;
|
|
78
|
+
case 4004: return 404;
|
|
79
|
+
default: return 500;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Statut HTTP que reçoit le client d'un handshake refusé — ou `null` si la
|
|
84
|
+
* connexion était déjà acceptée.
|
|
85
|
+
*
|
|
86
|
+
* La ligne de journal d'un refus s'écrit AVANT `refuseHandshake` : sans ce
|
|
87
|
+
* calcul, elle affichait le code de fermeture par défaut (`1000`) en ERROR,
|
|
88
|
+
* pour un `400` que le client reçoit. Même conversion que le refus
|
|
89
|
+
* ({@link toHandshakeStatus}), donc même chiffre. Chemin d'ERREUR seulement :
|
|
90
|
+
* 1 comparaison et 1 `typeof`, aucune allocation.
|
|
91
|
+
*
|
|
92
|
+
* @param context - le contexte WS de la ligne
|
|
93
|
+
* @param error - l'erreur qui a interrompu le handshake
|
|
94
|
+
* @returns le statut HTTP du refus, ou `null` après l'acceptation
|
|
95
|
+
*/
|
|
96
|
+
function handshakeRefusalStatus(context, error) {
|
|
97
|
+
if (context.webSocketState !== "handshake") return null;
|
|
98
|
+
const code = error.code;
|
|
99
|
+
return toHandshakeStatus(typeof code === "number" ? code : void 0);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
56
102
|
* Drapeau « logger le CONTENU des messages WS » — résolu **une seule fois** au
|
|
57
103
|
* premier message (kernel présent). `true` hors production : le Suivi de requête
|
|
58
104
|
* (Studio) affiche alors les frames RECEIVE/SEND/BROADCAST. En prod → `false` →
|
|
@@ -67,16 +113,21 @@ var WebsocketContext = class extends Context {
|
|
|
67
113
|
rejected = false;
|
|
68
114
|
teardownWired = false;
|
|
69
115
|
connection = null;
|
|
116
|
+
/**
|
|
117
|
+
* Le verdict du handshake encore à rendre : posé par le noyau tant que le
|
|
118
|
+
* pipeline décide (pare-feu, garde de l'action), `null` dès que la socket
|
|
119
|
+
* est acceptée ou refusée. Tant qu'il est là, AUCUN `101` n'est parti.
|
|
120
|
+
*/
|
|
121
|
+
admission = null;
|
|
70
122
|
origin;
|
|
71
123
|
proxy = null;
|
|
72
124
|
forwarded = null;
|
|
73
125
|
wsUrl = null;
|
|
74
|
-
|
|
75
|
-
queryRequest = {};
|
|
126
|
+
query = {};
|
|
76
127
|
wsPath = "";
|
|
77
128
|
#frameSeq = 0;
|
|
78
|
-
constructor(scope, req, ws,
|
|
79
|
-
super(scope,
|
|
129
|
+
constructor(scope, req, ws, serverType) {
|
|
130
|
+
super(scope, serverType);
|
|
80
131
|
this.webSocketState = "handshake";
|
|
81
132
|
this.request = req;
|
|
82
133
|
this.connection = ws;
|
|
@@ -88,20 +139,16 @@ var WebsocketContext = class extends Context {
|
|
|
88
139
|
if (checker && checker.isTrusted(socketAddress) && hasForwardingHeaders(req.headers)) this.forwarded = resolveForwarded(req.headers, socketAddress, checker);
|
|
89
140
|
this.remoteAddress = this.getRemoteAddress();
|
|
90
141
|
this.acceptedProtocol = req.headers["sec-websocket-protocol"];
|
|
91
|
-
this.scheme =
|
|
142
|
+
this.scheme = serverType === "wss" ? "wss" : "ws";
|
|
92
143
|
const incomingId = sanitizeRequestId(req.headers["x-request-id"]);
|
|
93
144
|
if (incomingId) this.requestId = incomingId;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
this.wsUrl = new URL(`${this.scheme}://${host}${rawUrl}`);
|
|
97
|
-
this.queryGet = Object.fromEntries(this.wsUrl.searchParams.entries());
|
|
98
|
-
this.queryRequest = { ...this.queryGet };
|
|
145
|
+
this.wsUrl = parseWebsocketUrl(this.scheme, req.headers.host, req.url);
|
|
146
|
+
this.query = Object.fromEntries(this.wsUrl.searchParams.entries());
|
|
99
147
|
this.wsPath = this.wsUrl.pathname + this.wsUrl.search;
|
|
100
148
|
this.url = url.format(this.wsUrl);
|
|
101
|
-
this.port = parseInt(this.wsUrl.port, 10) || (
|
|
149
|
+
this.port = parseInt(this.wsUrl.port, 10) || (serverType === "wss" ? 443 : 80);
|
|
102
150
|
this.request.url = this.wsUrl;
|
|
103
|
-
this.request.
|
|
104
|
-
this.request.query = this.queryRequest;
|
|
151
|
+
this.request.query = this.query;
|
|
105
152
|
this.request.path = this.wsPath;
|
|
106
153
|
try {
|
|
107
154
|
this.originUrl = new URL(this.origin);
|
|
@@ -142,6 +189,34 @@ var WebsocketContext = class extends Context {
|
|
|
142
189
|
return;
|
|
143
190
|
}
|
|
144
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Rattache la socket que le handshake vient d'ouvrir (`101` envoyé) : le
|
|
194
|
+
* contexte a été construit et jugé AVANT, sans elle.
|
|
195
|
+
*
|
|
196
|
+
* @param ws - la socket acceptée
|
|
197
|
+
*/
|
|
198
|
+
acceptConnection(ws) {
|
|
199
|
+
this.admission = null;
|
|
200
|
+
this.connection = ws;
|
|
201
|
+
this.response?.setConnection(ws);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Refuse le handshake par un statut HTTP, avant tout `101` (RFC 6455
|
|
205
|
+
* §4.2.2) — avec les en-têtes posés pendant la décision (`WWW-Authenticate`
|
|
206
|
+
* du pare-feu, `Retry-After`).
|
|
207
|
+
*
|
|
208
|
+
* @param status - statut HTTP du refus
|
|
209
|
+
* @returns `false` si le verdict était déjà rendu (rien n'est écrit)
|
|
210
|
+
*/
|
|
211
|
+
refuseHandshake(status) {
|
|
212
|
+
const admission = this.admission;
|
|
213
|
+
if (admission === null) return false;
|
|
214
|
+
this.admission = null;
|
|
215
|
+
this.rejected = true;
|
|
216
|
+
this.webSocketState = "error";
|
|
217
|
+
admission.refuse(status, this.response?.handshakeHeaders ?? null);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
145
220
|
async connect() {
|
|
146
221
|
if (!this.response || !this.request || !this.connection) throw new Error("Nodefony Websocket Bad request/response/connection");
|
|
147
222
|
if (this.rejected) throw new Error("Nodefony Websocket rejected");
|
|
@@ -338,12 +413,12 @@ var WebsocketContext = class extends Context {
|
|
|
338
413
|
}
|
|
339
414
|
onClose(code, reason) {
|
|
340
415
|
const description = reason.toString();
|
|
341
|
-
this.log(`${logColor.cyan("URL")} : ${this.url} ${logColor.cyan("FROM")} : ${this.remoteAddress} ${logColor.cyan("ORIGIN")} : ${this.originUrl?.host} ${logColor.cyan("ID")} : ${this.requestId} ${logColor.cyan("Description")} : ${description}`, "INFO", `${this.
|
|
416
|
+
this.log(`${logColor.cyan("URL")} : ${this.url} ${logColor.cyan("FROM")} : ${this.remoteAddress} ${logColor.cyan("ORIGIN")} : ${this.originUrl?.host} ${logColor.cyan("ID")} : ${this.requestId} ${logColor.cyan("Description")} : ${description}`, "INFO", `${this.serverType} ${logColor.magenta(code)} CLOSE ${this.method}`);
|
|
342
417
|
if (this.connection?.readyState !== Ws.CLOSED) {
|
|
343
418
|
try {
|
|
344
419
|
this.response?.drop(code, description);
|
|
345
420
|
} catch (e) {
|
|
346
|
-
this.log(`${logColor.cyan("URL")} : ${this.url} ${logColor.cyan("FROM")} : ${this.remoteAddress} ${logColor.cyan("ORIGIN")} : ${this.originUrl?.host} ${logColor.cyan("ID")} : ${this.requestId} ${logColor.cyan("error")} : ${e.message}`, "ERROR", `${this.
|
|
421
|
+
this.log(`${logColor.cyan("URL")} : ${this.url} ${logColor.cyan("FROM")} : ${this.remoteAddress} ${logColor.cyan("ORIGIN")} : ${this.originUrl?.host} ${logColor.cyan("ID")} : ${this.requestId} ${logColor.cyan("error")} : ${e.message}`, "ERROR", `${this.serverType} CLOSE ${logColor.red(this.method ?? "")}`);
|
|
347
422
|
}
|
|
348
423
|
this.fire("onClose", code, description, this.connection);
|
|
349
424
|
} else this.fire("onClose", code, description, this.connection);
|
|
@@ -360,7 +435,7 @@ var WebsocketContext = class extends Context {
|
|
|
360
435
|
*/
|
|
361
436
|
onConnectionError(error) {
|
|
362
437
|
this.webSocketState = "error";
|
|
363
|
-
this.log(`${logColor.cyan("URL")} : ${this.url} ${logColor.cyan("FROM")} : ${this.remoteAddress} ${logColor.cyan("ID")} : ${this.requestId} ${logColor.cyan("error")} : ${error.message}`, "ERROR", `${this.
|
|
438
|
+
this.log(`${logColor.cyan("URL")} : ${this.url} ${logColor.cyan("FROM")} : ${this.remoteAddress} ${logColor.cyan("ID")} : ${this.requestId} ${logColor.cyan("error")} : ${error.message}`, "ERROR", `${this.serverType} ${logColor.red("SOCKET ERROR")} ${this.method}`);
|
|
364
439
|
this.fire("onError", error, this);
|
|
365
440
|
}
|
|
366
441
|
setScheme() {
|
|
@@ -418,5 +493,4 @@ var WebsocketContext = class extends Context {
|
|
|
418
493
|
this.rejected = true;
|
|
419
494
|
}
|
|
420
495
|
};
|
|
421
|
-
|
|
422
|
-
export { WebsocketContext as default, toWsCloseCode };
|
|
496
|
+
export { WebsocketContext as default, handshakeRefusalStatus, toHandshakeStatus, toWsCloseCode };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// nodefony/src/context/websocket/isWebsocketContext.ts
|
|
2
|
+
/**
|
|
3
|
+
* Dit si un contexte sert une connexion WebSocket — la garde qui rétrécit
|
|
4
|
+
* `ContextType` à `WebsocketContext` sans conversion.
|
|
5
|
+
*
|
|
6
|
+
* Elle lit la famille (`type`) posée à la construction du contexte, jamais
|
|
7
|
+
* `instanceof` : deux copies installées de `@nodefony/http` font échouer
|
|
8
|
+
* `instanceof` en silence, quand ce champ dit vrai pour les deux. Deux
|
|
9
|
+
* Une comparaison de chaînes, aucune allocation.
|
|
10
|
+
*
|
|
11
|
+
* @param context - le contexte à éprouver.
|
|
12
|
+
* @returns `true` si le contexte traite une connexion WebSocket (`ws` ou `wss`).
|
|
13
|
+
*/
|
|
14
|
+
function isWebsocketContext(context) {
|
|
15
|
+
return context.type === "websocket";
|
|
16
|
+
}
|
|
17
|
+
export { isWebsocketContext };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// nodefony/src/context/websocket/websocketUrl.ts
|
|
2
|
+
/**
|
|
3
|
+
* URL d'une requête d'upgrade WebSocket, telle que le pipeline la lit.
|
|
4
|
+
*
|
|
5
|
+
* Seule construction : le contexte WebSocket en tire son chemin et sa requête,
|
|
6
|
+
* et le contrôle d'avant `101` en tire le chemin qu'il confronte aux routes.
|
|
7
|
+
* Deux constructions divergeraient — `//a/b` lu comme un chemin d'un côté, comme
|
|
8
|
+
* un hôte de l'autre — et une route acceptée par l'une serait refusée par
|
|
9
|
+
* l'autre.
|
|
10
|
+
*
|
|
11
|
+
* @param scheme - `ws` ou `wss`
|
|
12
|
+
* @param host - en-tête `Host` de la requête (`localhost` s'il manque)
|
|
13
|
+
* @param rawUrl - cible brute de la requête (`/` si elle manque)
|
|
14
|
+
* @returns l'URL analysée
|
|
15
|
+
* @throws TypeError si l'hôte ou la cible ne forment pas une URL
|
|
16
|
+
*/
|
|
17
|
+
function parseWebsocketUrl(scheme, host, rawUrl) {
|
|
18
|
+
return new URL(`${scheme}://${host ?? "localhost"}${rawUrl ?? "/"}`);
|
|
19
|
+
}
|
|
20
|
+
export { parseWebsocketUrl };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/context/websocket/wsBackpressure.ts
|
|
2
2
|
/**
|
|
3
3
|
* Relit le seuil + la politique depuis les options du `WebSocketServer`.
|
|
4
4
|
*
|
|
@@ -52,5 +52,4 @@ const decideSend = (ws, max, policy, closeAfterDrops = 0) => {
|
|
|
52
52
|
}
|
|
53
53
|
return "drop";
|
|
54
54
|
};
|
|
55
|
-
//#endregion
|
|
56
55
|
export { decideSend, readBackpressureOptions };
|