@morojs/moro 1.8.15 → 1.8.17
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/core/http/moro-engine-server.d.ts +20 -0
- package/dist/core/http/moro-engine-server.js +140 -47
- package/dist/core/http/moro-engine-server.js.map +1 -1
- package/dist/core/http/utils/compression.d.ts +34 -4
- package/dist/core/http/utils/compression.js +146 -27
- package/dist/core/http/utils/compression.js.map +1 -1
- package/dist/core/http/uws-http-server.js +2 -2
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/middleware/built-in/compression/core.d.ts +4 -2
- package/dist/core/middleware/built-in/compression/core.js +13 -3
- package/dist/core/middleware/built-in/compression/core.js.map +1 -1
- package/dist/core/middleware/built-in/static/core.d.ts +88 -0
- package/dist/core/middleware/built-in/static/core.js +321 -15
- package/dist/core/middleware/built-in/static/core.js.map +1 -1
- package/dist/core/networking/adapters/engine-adapter.js +91 -11
- package/dist/core/networking/adapters/engine-adapter.js.map +1 -1
- package/dist/core/utilities/package-utils.d.ts +5 -0
- package/dist/core/utilities/package-utils.js +1 -0
- package/dist/core/utilities/package-utils.js.map +1 -1
- package/dist/types/config.d.ts +6 -0
- package/dist/types/config.js.map +1 -1
- package/package.json +1 -1
|
@@ -190,6 +190,14 @@ export declare class MoroEngineServer {
|
|
|
190
190
|
/** @internal */ _getBody: (reqId: number) => ArrayBuffer | null;
|
|
191
191
|
/** @internal */ _getRemoteAddress: (reqId: number) => string | undefined;
|
|
192
192
|
/** @internal */ _getMethod: (reqId: number) => string | undefined;
|
|
193
|
+
/** @internal */ _wsSend: (wsId: number, data: any, isBinary: boolean) => boolean;
|
|
194
|
+
/** @internal */ _wsClose: (wsId: number, code?: number, reason?: string) => void;
|
|
195
|
+
/** @internal */ _upgradeToWebSocket: (reqId: number) => number;
|
|
196
|
+
private _setWsAutoUpgrade?;
|
|
197
|
+
private _wsGetHeaders?;
|
|
198
|
+
private _wsGetQuery?;
|
|
199
|
+
private _wsGetRemoteAddress?;
|
|
200
|
+
private _wsAutoOn;
|
|
193
201
|
/** @internal */ _prepareResponse?: (serverId: number, status: number, headers: string[] | null) => number;
|
|
194
202
|
/** @internal */ _respondPrepared?: (reqId: number, tplId: number, body: any) => void;
|
|
195
203
|
/** @internal */ _respondPreparedEmpty?: (reqId: number, tplId: number) => void;
|
|
@@ -305,6 +313,17 @@ export declare class MoroEngineServer {
|
|
|
305
313
|
onMessage(wsId: number, data: any, isBinary: boolean): void;
|
|
306
314
|
onClose(wsId: number, code: number): void;
|
|
307
315
|
}): void;
|
|
316
|
+
/**
|
|
317
|
+
* The handshake of a WebSocket the engine upgraded itself: the peer's
|
|
318
|
+
* address, the request headers as the engine's flat list and the raw query
|
|
319
|
+
* string. undefined without the capability, or once the socket is gone
|
|
320
|
+
* (the engine keeps it for the socket's lifetime, through onWsClose).
|
|
321
|
+
*/
|
|
322
|
+
wsHandshake(wsId: number): {
|
|
323
|
+
ip: string;
|
|
324
|
+
flat: string[] | undefined;
|
|
325
|
+
query: string;
|
|
326
|
+
} | undefined;
|
|
308
327
|
/** Send a frame on a WebSocket. Returns false on backpressure. */
|
|
309
328
|
wsSend(wsId: number, data: string | ArrayBuffer | Buffer, isBinary: boolean): boolean;
|
|
310
329
|
/** Close a WebSocket (RFC 6455 close frame). */
|
|
@@ -328,6 +347,7 @@ export declare class MoroEngineServer {
|
|
|
328
347
|
private dispatchDirectRoute;
|
|
329
348
|
setNativeRouteProvider(_provider: () => Array<any>, _onError?: (...args: any[]) => any): void;
|
|
330
349
|
private onRequest;
|
|
350
|
+
private dispatchRouter;
|
|
331
351
|
private _armMicrotaskDrain;
|
|
332
352
|
private onAborted;
|
|
333
353
|
private onWritable;
|
|
@@ -756,7 +756,7 @@ export class EngineResponse extends LazyEventEmitter {
|
|
|
756
756
|
if (shouldInline(bytes, s)) {
|
|
757
757
|
let compressed;
|
|
758
758
|
try {
|
|
759
|
-
compressed = compressBufferSync(body, encoding, s.level);
|
|
759
|
+
compressed = compressBufferSync(body, encoding, s.level, s);
|
|
760
760
|
}
|
|
761
761
|
catch {
|
|
762
762
|
compressed = undefined;
|
|
@@ -773,7 +773,7 @@ export class EngineResponse extends LazyEventEmitter {
|
|
|
773
773
|
}
|
|
774
774
|
// Async path: compress, then respond (guarding against an abort that
|
|
775
775
|
// raced the await - the reqId would be invalid).
|
|
776
|
-
void compressBuffer(body, encoding, s.level)
|
|
776
|
+
void compressBuffer(body, encoding, s.level, s)
|
|
777
777
|
.then(compressed => {
|
|
778
778
|
if (this._ended || this._server._isAborted(this._reqId))
|
|
779
779
|
return;
|
|
@@ -1480,6 +1480,22 @@ export class MoroEngineServer {
|
|
|
1480
1480
|
/** @internal */ _getBody;
|
|
1481
1481
|
/** @internal */ _getRemoteAddress;
|
|
1482
1482
|
/** @internal */ _getMethod;
|
|
1483
|
+
// The WebSocket natives, hoisted for the same reason: wsSend runs once per
|
|
1484
|
+
// frame, and through the loader's Proxy every call paid the `get` trap.
|
|
1485
|
+
/** @internal */ _wsSend;
|
|
1486
|
+
/** @internal */ _wsClose;
|
|
1487
|
+
/** @internal */ _upgradeToWebSocket;
|
|
1488
|
+
// Engine-side upgrades (engine >= 1.1.15, capabilities.wsAutoUpgrade): a
|
|
1489
|
+
// valid handshake is answered inside the engine and onWsOpen fires without
|
|
1490
|
+
// the request ever reaching onRequest - no header materialisation, no
|
|
1491
|
+
// native round trips, no request/response objects per upgrade. The
|
|
1492
|
+
// handshake is read from the socket on first access (wsHandshake). Assigned
|
|
1493
|
+
// only when the flag AND every function are present, like the templates.
|
|
1494
|
+
_setWsAutoUpgrade;
|
|
1495
|
+
_wsGetHeaders;
|
|
1496
|
+
_wsGetQuery;
|
|
1497
|
+
_wsGetRemoteAddress;
|
|
1498
|
+
_wsAutoOn = false;
|
|
1483
1499
|
// Prepared response templates (engine >= 1.1.6): assigned only when the
|
|
1484
1500
|
// engine advertises capabilities.responseTemplates AND the functions exist,
|
|
1485
1501
|
// so a flag/binary mismatch can never put `undefined` into a hot call site.
|
|
@@ -1614,6 +1630,9 @@ export class MoroEngineServer {
|
|
|
1614
1630
|
this._getBody = surface.getBody;
|
|
1615
1631
|
this._getRemoteAddress = surface.getRemoteAddress;
|
|
1616
1632
|
this._getMethod = surface.getMethod;
|
|
1633
|
+
this._wsSend = surface.wsSend;
|
|
1634
|
+
this._wsClose = surface.wsClose;
|
|
1635
|
+
this._upgradeToWebSocket = surface.upgradeToWebSocket;
|
|
1617
1636
|
if (this._capabilities?.responseTemplates === true &&
|
|
1618
1637
|
typeof surface.prepareResponse === 'function' &&
|
|
1619
1638
|
typeof surface.respondPrepared === 'function' &&
|
|
@@ -1633,6 +1652,16 @@ export class MoroEngineServer {
|
|
|
1633
1652
|
this._setParamRoute = surface.setParamRoute;
|
|
1634
1653
|
this._paramOn = true;
|
|
1635
1654
|
}
|
|
1655
|
+
if (this._capabilities?.wsAutoUpgrade === true &&
|
|
1656
|
+
typeof surface.setWsAutoUpgrade === 'function' &&
|
|
1657
|
+
typeof surface.wsGetHeaders === 'function' &&
|
|
1658
|
+
typeof surface.wsGetQuery === 'function' &&
|
|
1659
|
+
typeof surface.wsGetRemoteAddress === 'function') {
|
|
1660
|
+
this._setWsAutoUpgrade = surface.setWsAutoUpgrade;
|
|
1661
|
+
this._wsGetHeaders = surface.wsGetHeaders;
|
|
1662
|
+
this._wsGetQuery = surface.wsGetQuery;
|
|
1663
|
+
this._wsGetRemoteAddress = surface.wsGetRemoteAddress;
|
|
1664
|
+
}
|
|
1636
1665
|
if (this._capabilities?.batchDispatch === true &&
|
|
1637
1666
|
typeof surface.getBatchBuffers === 'function' &&
|
|
1638
1667
|
typeof surface.getPath === 'function') {
|
|
@@ -1932,14 +1961,35 @@ export class MoroEngineServer {
|
|
|
1932
1961
|
/** Register the engine WebSocket bridge; upgrade requests then route natively. */
|
|
1933
1962
|
enableWebSocket(bridge) {
|
|
1934
1963
|
this._wsBridge = bridge;
|
|
1964
|
+
// With the capability the engine answers valid handshakes itself; the
|
|
1965
|
+
// onRequest branch below stays for an invalid one (surfaced as a plain
|
|
1966
|
+
// request, answered 400 by the router) and for older engines.
|
|
1967
|
+
if (this._setWsAutoUpgrade) {
|
|
1968
|
+
this._setWsAutoUpgrade(this.serverId, true);
|
|
1969
|
+
this._wsAutoOn = true;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* The handshake of a WebSocket the engine upgraded itself: the peer's
|
|
1974
|
+
* address, the request headers as the engine's flat list and the raw query
|
|
1975
|
+
* string. undefined without the capability, or once the socket is gone
|
|
1976
|
+
* (the engine keeps it for the socket's lifetime, through onWsClose).
|
|
1977
|
+
*/
|
|
1978
|
+
wsHandshake(wsId) {
|
|
1979
|
+
if (!this._wsGetHeaders || !this._wsGetQuery || !this._wsGetRemoteAddress)
|
|
1980
|
+
return undefined;
|
|
1981
|
+
const flat = this._wsGetHeaders(wsId);
|
|
1982
|
+
if (flat === undefined)
|
|
1983
|
+
return undefined;
|
|
1984
|
+
return { ip: this._wsGetRemoteAddress(wsId) || '', flat, query: this._wsGetQuery(wsId) || '' };
|
|
1935
1985
|
}
|
|
1936
1986
|
/** Send a frame on a WebSocket. Returns false on backpressure. */
|
|
1937
1987
|
wsSend(wsId, data, isBinary) {
|
|
1938
|
-
return this.
|
|
1988
|
+
return this._wsSend(wsId, data, isBinary) !== false;
|
|
1939
1989
|
}
|
|
1940
1990
|
/** Close a WebSocket (RFC 6455 close frame). */
|
|
1941
1991
|
wsClose(wsId, code, reason) {
|
|
1942
|
-
this.
|
|
1992
|
+
this._wsClose(wsId, code, reason);
|
|
1943
1993
|
}
|
|
1944
1994
|
// Direct router dispatch - called after global middleware without the
|
|
1945
1995
|
// per-middleware promise machinery (mirrors MoroHttpServer.setRouterHandler)
|
|
@@ -2093,29 +2143,18 @@ export class MoroEngineServer {
|
|
|
2093
2143
|
const upgrade = this._getHeader(reqId, 'upgrade');
|
|
2094
2144
|
if (upgrade && upgrade.toLowerCase() === 'websocket') {
|
|
2095
2145
|
// Snapshot handshake data now - the upgrade invalidates the reqId,
|
|
2096
|
-
// after which these fetchers return empty values.
|
|
2146
|
+
// after which these fetchers return empty values. The headers stay
|
|
2147
|
+
// the engine's flat [name, value, ...] list: the adapter builds the
|
|
2148
|
+
// object (joining duplicates like EngineRequest.headers) the first
|
|
2149
|
+
// time a handler reads socket.headers, so an upgrade whose handlers
|
|
2150
|
+
// never look pays for no object at all.
|
|
2097
2151
|
const ip = this._getRemoteAddress(reqId) || '';
|
|
2098
|
-
const headers = {};
|
|
2099
2152
|
const flat = this._getHeaders(reqId);
|
|
2100
|
-
if (flat) {
|
|
2101
|
-
// Join duplicates exactly like the EngineRequest.headers getter -
|
|
2102
|
-
// last-wins would let a client send two X-Forwarded-For lines and
|
|
2103
|
-
// hide the earlier one from IP-based WS auth / rate-limiting.
|
|
2104
|
-
for (let i = 0; i + 1 < flat.length; i += 2) {
|
|
2105
|
-
const key = flat[i];
|
|
2106
|
-
const val = flat[i + 1];
|
|
2107
|
-
if (key === undefined || val === undefined)
|
|
2108
|
-
continue;
|
|
2109
|
-
const existing = headers[key];
|
|
2110
|
-
headers[key] =
|
|
2111
|
-
existing === undefined ? val : existing + (key === 'cookie' ? '; ' : ', ') + val;
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
2153
|
// Capture the query string too (invalidated by the upgrade like the
|
|
2115
2154
|
// rest), so handlers can read handshake query params (e.g. orgId).
|
|
2116
2155
|
const query = this._getQuery(reqId) || '';
|
|
2117
|
-
this._pendingWsInfo = { ip,
|
|
2118
|
-
const wsId = this.
|
|
2156
|
+
this._pendingWsInfo = { ip, flat, query };
|
|
2157
|
+
const wsId = this._upgradeToWebSocket(reqId);
|
|
2119
2158
|
this._pendingWsInfo = undefined;
|
|
2120
2159
|
if (wsId !== -1)
|
|
2121
2160
|
return; // WS took over this connection
|
|
@@ -2141,40 +2180,58 @@ export class MoroEngineServer {
|
|
|
2141
2180
|
}
|
|
2142
2181
|
this.requestCounter++;
|
|
2143
2182
|
// SYNC FAST PATH (parity with MoroHttpServer's handleRequest split): with
|
|
2144
|
-
// no
|
|
2145
|
-
// the router dispatch
|
|
2146
|
-
// handleRequest below would allocate a promise
|
|
2147
|
-
// request only to reach zero awaits.
|
|
2148
|
-
//
|
|
2149
|
-
//
|
|
2183
|
+
// no request/response hooks, the body parse, the global middleware chain
|
|
2184
|
+
// and the router dispatch all run synchronously in this frame whenever
|
|
2185
|
+
// they can - the async handleRequest below would allocate a promise and
|
|
2186
|
+
// an async context per request only to reach zero awaits. The engine
|
|
2187
|
+
// buffered the body before onRequest fired, so parsing it is synchronous;
|
|
2188
|
+
// a middleware chain whose members call next() synchronously costs no
|
|
2189
|
+
// promise either (executeMiddleware returns undefined then), and only a
|
|
2190
|
+
// chain that genuinely goes async hands the rest of the request to a
|
|
2191
|
+
// continuation. Only the framework's own frame is de-asynced: an async
|
|
2192
|
+
// route handler still returns its promise through the router and is
|
|
2193
|
+
// driven to completion here.
|
|
2150
2194
|
const hookManager = this.hookManager;
|
|
2151
2195
|
const hooksActive = hookManager &&
|
|
2152
2196
|
(hookManager.hasHooks === undefined ||
|
|
2153
2197
|
hookManager.hasHooks('request') ||
|
|
2154
2198
|
hookManager.hasHooks('response'));
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2199
|
+
if (!hooksActive && this.routerHandler) {
|
|
2200
|
+
let proceed = true;
|
|
2201
|
+
if (method.charCodeAt(0) === 80 && // 'P' char code
|
|
2202
|
+
(method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
2203
|
+
try {
|
|
2204
|
+
this.parseBody(httpReq);
|
|
2205
|
+
}
|
|
2206
|
+
catch (error) {
|
|
2207
|
+
proceed = false;
|
|
2208
|
+
void this.handleDispatchError(error, httpReq, httpRes);
|
|
2209
|
+
}
|
|
2161
2210
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2211
|
+
if (proceed && this.globalMiddleware.length > 0) {
|
|
2212
|
+
let chain;
|
|
2213
|
+
try {
|
|
2214
|
+
chain = this.executeMiddleware(this.globalMiddleware, httpReq, httpRes);
|
|
2215
|
+
}
|
|
2216
|
+
catch (error) {
|
|
2217
|
+
proceed = false;
|
|
2218
|
+
chain = undefined;
|
|
2219
|
+
void this.handleDispatchError(error, httpReq, httpRes);
|
|
2220
|
+
}
|
|
2221
|
+
if (chain) {
|
|
2222
|
+
// The chain went async: route once it settles, unless it answered.
|
|
2223
|
+
proceed = false;
|
|
2224
|
+
chain.then(() => {
|
|
2225
|
+
if (!httpRes.headersSent)
|
|
2226
|
+
this.dispatchRouter(httpReq, httpRes);
|
|
2171
2227
|
}, error => void this.handleDispatchError(error, httpReq, httpRes));
|
|
2172
2228
|
}
|
|
2173
|
-
else {
|
|
2174
|
-
|
|
2175
|
-
void this.finishUnrouted(httpReq, httpRes);
|
|
2229
|
+
else if (httpRes.headersSent) {
|
|
2230
|
+
proceed = false; // a middleware answered the request itself
|
|
2176
2231
|
}
|
|
2177
2232
|
}
|
|
2233
|
+
if (proceed)
|
|
2234
|
+
this.dispatchRouter(httpReq, httpRes);
|
|
2178
2235
|
}
|
|
2179
2236
|
else {
|
|
2180
2237
|
void this.handleRequest(httpReq, httpRes);
|
|
@@ -2192,6 +2249,36 @@ export class MoroEngineServer {
|
|
|
2192
2249
|
this._armMicrotaskDrain();
|
|
2193
2250
|
}
|
|
2194
2251
|
}
|
|
2252
|
+
// The router dispatch of the sync fast path, and of a global middleware
|
|
2253
|
+
// chain's async continuation: the unified router, then the direct route
|
|
2254
|
+
// table and the 404 behind it, with the same error shaping as handleRequest.
|
|
2255
|
+
dispatchRouter(httpReq, httpRes) {
|
|
2256
|
+
const routerHandler = this.routerHandler;
|
|
2257
|
+
if (!routerHandler) {
|
|
2258
|
+
void this.finishUnrouted(httpReq, httpRes);
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
let handled = true;
|
|
2262
|
+
try {
|
|
2263
|
+
handled = routerHandler(httpReq, httpRes);
|
|
2264
|
+
}
|
|
2265
|
+
catch (error) {
|
|
2266
|
+
// Error path owns the response from here (handled stays true)
|
|
2267
|
+
void this.handleDispatchError(error, httpReq, httpRes);
|
|
2268
|
+
}
|
|
2269
|
+
if (handled !== true) {
|
|
2270
|
+
if (handled && typeof handled.then === 'function') {
|
|
2271
|
+
handled.then(ok => {
|
|
2272
|
+
if (!ok)
|
|
2273
|
+
void this.finishUnrouted(httpReq, httpRes);
|
|
2274
|
+
}, error => void this.handleDispatchError(error, httpReq, httpRes));
|
|
2275
|
+
}
|
|
2276
|
+
else {
|
|
2277
|
+
// Sync miss from the router - direct-route fallback, then 404
|
|
2278
|
+
void this.finishUnrouted(httpReq, httpRes);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2195
2282
|
// The engine invokes onRequest with a plain V8 call from its own uv
|
|
2196
2283
|
// callback, outside a Node callback scope, so microtasks queued during
|
|
2197
2284
|
// dispatch - an await on an already-settled promise, a .then chain, the
|
|
@@ -2357,7 +2444,11 @@ export class MoroEngineServer {
|
|
|
2357
2444
|
const raw = this._getBody(httpReq._reqId);
|
|
2358
2445
|
if (!raw || raw.byteLength === 0)
|
|
2359
2446
|
return;
|
|
2360
|
-
|
|
2447
|
+
// One header through one crossing: materialising the header object here
|
|
2448
|
+
// would cost every request with a body a string per header line.
|
|
2449
|
+
const contentType = (httpReq._headers !== undefined
|
|
2450
|
+
? httpReq._headers['content-type']
|
|
2451
|
+
: this._getHeader(httpReq._reqId, 'content-type')) || '';
|
|
2361
2452
|
const isMultipart = contentType.includes('multipart/form-data');
|
|
2362
2453
|
const maxSize = isMultipart ? this.maxUploadSize : this.maxBodySize;
|
|
2363
2454
|
// The engine natively enforces the larger of the two limits; the tighter
|
|
@@ -2554,6 +2645,8 @@ export class MoroEngineServer {
|
|
|
2554
2645
|
this.serverId = this.registerWithEngine();
|
|
2555
2646
|
this._resetTemplates();
|
|
2556
2647
|
this._reapplyStaticRoutes();
|
|
2648
|
+
if (this._wsAutoOn && this._setWsAutoUpgrade)
|
|
2649
|
+
this._setWsAutoUpgrade(this.serverId, true);
|
|
2557
2650
|
this.closed = false;
|
|
2558
2651
|
}
|
|
2559
2652
|
// Synchronous bind; throws on bind errors, returns the actual port
|