@morojs/moro 1.8.15 → 1.8.16
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 +4 -0
- package/dist/core/http/moro-engine-server.js +96 -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 +65 -10
- package/dist/core/networking/adapters/engine-adapter.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,9 @@ 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;
|
|
193
196
|
/** @internal */ _prepareResponse?: (serverId: number, status: number, headers: string[] | null) => number;
|
|
194
197
|
/** @internal */ _respondPrepared?: (reqId: number, tplId: number, body: any) => void;
|
|
195
198
|
/** @internal */ _respondPreparedEmpty?: (reqId: number, tplId: number) => void;
|
|
@@ -328,6 +331,7 @@ export declare class MoroEngineServer {
|
|
|
328
331
|
private dispatchDirectRoute;
|
|
329
332
|
setNativeRouteProvider(_provider: () => Array<any>, _onError?: (...args: any[]) => any): void;
|
|
330
333
|
private onRequest;
|
|
334
|
+
private dispatchRouter;
|
|
331
335
|
private _armMicrotaskDrain;
|
|
332
336
|
private onAborted;
|
|
333
337
|
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,11 @@ 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;
|
|
1483
1488
|
// Prepared response templates (engine >= 1.1.6): assigned only when the
|
|
1484
1489
|
// engine advertises capabilities.responseTemplates AND the functions exist,
|
|
1485
1490
|
// so a flag/binary mismatch can never put `undefined` into a hot call site.
|
|
@@ -1614,6 +1619,9 @@ export class MoroEngineServer {
|
|
|
1614
1619
|
this._getBody = surface.getBody;
|
|
1615
1620
|
this._getRemoteAddress = surface.getRemoteAddress;
|
|
1616
1621
|
this._getMethod = surface.getMethod;
|
|
1622
|
+
this._wsSend = surface.wsSend;
|
|
1623
|
+
this._wsClose = surface.wsClose;
|
|
1624
|
+
this._upgradeToWebSocket = surface.upgradeToWebSocket;
|
|
1617
1625
|
if (this._capabilities?.responseTemplates === true &&
|
|
1618
1626
|
typeof surface.prepareResponse === 'function' &&
|
|
1619
1627
|
typeof surface.respondPrepared === 'function' &&
|
|
@@ -1935,11 +1943,11 @@ export class MoroEngineServer {
|
|
|
1935
1943
|
}
|
|
1936
1944
|
/** Send a frame on a WebSocket. Returns false on backpressure. */
|
|
1937
1945
|
wsSend(wsId, data, isBinary) {
|
|
1938
|
-
return this.
|
|
1946
|
+
return this._wsSend(wsId, data, isBinary) !== false;
|
|
1939
1947
|
}
|
|
1940
1948
|
/** Close a WebSocket (RFC 6455 close frame). */
|
|
1941
1949
|
wsClose(wsId, code, reason) {
|
|
1942
|
-
this.
|
|
1950
|
+
this._wsClose(wsId, code, reason);
|
|
1943
1951
|
}
|
|
1944
1952
|
// Direct router dispatch - called after global middleware without the
|
|
1945
1953
|
// per-middleware promise machinery (mirrors MoroHttpServer.setRouterHandler)
|
|
@@ -2093,29 +2101,18 @@ export class MoroEngineServer {
|
|
|
2093
2101
|
const upgrade = this._getHeader(reqId, 'upgrade');
|
|
2094
2102
|
if (upgrade && upgrade.toLowerCase() === 'websocket') {
|
|
2095
2103
|
// Snapshot handshake data now - the upgrade invalidates the reqId,
|
|
2096
|
-
// after which these fetchers return empty values.
|
|
2104
|
+
// after which these fetchers return empty values. The headers stay
|
|
2105
|
+
// the engine's flat [name, value, ...] list: the adapter builds the
|
|
2106
|
+
// object (joining duplicates like EngineRequest.headers) the first
|
|
2107
|
+
// time a handler reads socket.headers, so an upgrade whose handlers
|
|
2108
|
+
// never look pays for no object at all.
|
|
2097
2109
|
const ip = this._getRemoteAddress(reqId) || '';
|
|
2098
|
-
const headers = {};
|
|
2099
2110
|
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
2111
|
// Capture the query string too (invalidated by the upgrade like the
|
|
2115
2112
|
// rest), so handlers can read handshake query params (e.g. orgId).
|
|
2116
2113
|
const query = this._getQuery(reqId) || '';
|
|
2117
|
-
this._pendingWsInfo = { ip,
|
|
2118
|
-
const wsId = this.
|
|
2114
|
+
this._pendingWsInfo = { ip, flat, query };
|
|
2115
|
+
const wsId = this._upgradeToWebSocket(reqId);
|
|
2119
2116
|
this._pendingWsInfo = undefined;
|
|
2120
2117
|
if (wsId !== -1)
|
|
2121
2118
|
return; // WS took over this connection
|
|
@@ -2141,40 +2138,58 @@ export class MoroEngineServer {
|
|
|
2141
2138
|
}
|
|
2142
2139
|
this.requestCounter++;
|
|
2143
2140
|
// 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
|
-
//
|
|
2141
|
+
// no request/response hooks, the body parse, the global middleware chain
|
|
2142
|
+
// and the router dispatch all run synchronously in this frame whenever
|
|
2143
|
+
// they can - the async handleRequest below would allocate a promise and
|
|
2144
|
+
// an async context per request only to reach zero awaits. The engine
|
|
2145
|
+
// buffered the body before onRequest fired, so parsing it is synchronous;
|
|
2146
|
+
// a middleware chain whose members call next() synchronously costs no
|
|
2147
|
+
// promise either (executeMiddleware returns undefined then), and only a
|
|
2148
|
+
// chain that genuinely goes async hands the rest of the request to a
|
|
2149
|
+
// continuation. Only the framework's own frame is de-asynced: an async
|
|
2150
|
+
// route handler still returns its promise through the router and is
|
|
2151
|
+
// driven to completion here.
|
|
2150
2152
|
const hookManager = this.hookManager;
|
|
2151
2153
|
const hooksActive = hookManager &&
|
|
2152
2154
|
(hookManager.hasHooks === undefined ||
|
|
2153
2155
|
hookManager.hasHooks('request') ||
|
|
2154
2156
|
hookManager.hasHooks('response'));
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2157
|
+
if (!hooksActive && this.routerHandler) {
|
|
2158
|
+
let proceed = true;
|
|
2159
|
+
if (method.charCodeAt(0) === 80 && // 'P' char code
|
|
2160
|
+
(method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
2161
|
+
try {
|
|
2162
|
+
this.parseBody(httpReq);
|
|
2163
|
+
}
|
|
2164
|
+
catch (error) {
|
|
2165
|
+
proceed = false;
|
|
2166
|
+
void this.handleDispatchError(error, httpReq, httpRes);
|
|
2167
|
+
}
|
|
2161
2168
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2169
|
+
if (proceed && this.globalMiddleware.length > 0) {
|
|
2170
|
+
let chain;
|
|
2171
|
+
try {
|
|
2172
|
+
chain = this.executeMiddleware(this.globalMiddleware, httpReq, httpRes);
|
|
2173
|
+
}
|
|
2174
|
+
catch (error) {
|
|
2175
|
+
proceed = false;
|
|
2176
|
+
chain = undefined;
|
|
2177
|
+
void this.handleDispatchError(error, httpReq, httpRes);
|
|
2178
|
+
}
|
|
2179
|
+
if (chain) {
|
|
2180
|
+
// The chain went async: route once it settles, unless it answered.
|
|
2181
|
+
proceed = false;
|
|
2182
|
+
chain.then(() => {
|
|
2183
|
+
if (!httpRes.headersSent)
|
|
2184
|
+
this.dispatchRouter(httpReq, httpRes);
|
|
2171
2185
|
}, error => void this.handleDispatchError(error, httpReq, httpRes));
|
|
2172
2186
|
}
|
|
2173
|
-
else {
|
|
2174
|
-
|
|
2175
|
-
void this.finishUnrouted(httpReq, httpRes);
|
|
2187
|
+
else if (httpRes.headersSent) {
|
|
2188
|
+
proceed = false; // a middleware answered the request itself
|
|
2176
2189
|
}
|
|
2177
2190
|
}
|
|
2191
|
+
if (proceed)
|
|
2192
|
+
this.dispatchRouter(httpReq, httpRes);
|
|
2178
2193
|
}
|
|
2179
2194
|
else {
|
|
2180
2195
|
void this.handleRequest(httpReq, httpRes);
|
|
@@ -2192,6 +2207,36 @@ export class MoroEngineServer {
|
|
|
2192
2207
|
this._armMicrotaskDrain();
|
|
2193
2208
|
}
|
|
2194
2209
|
}
|
|
2210
|
+
// The router dispatch of the sync fast path, and of a global middleware
|
|
2211
|
+
// chain's async continuation: the unified router, then the direct route
|
|
2212
|
+
// table and the 404 behind it, with the same error shaping as handleRequest.
|
|
2213
|
+
dispatchRouter(httpReq, httpRes) {
|
|
2214
|
+
const routerHandler = this.routerHandler;
|
|
2215
|
+
if (!routerHandler) {
|
|
2216
|
+
void this.finishUnrouted(httpReq, httpRes);
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
let handled = true;
|
|
2220
|
+
try {
|
|
2221
|
+
handled = routerHandler(httpReq, httpRes);
|
|
2222
|
+
}
|
|
2223
|
+
catch (error) {
|
|
2224
|
+
// Error path owns the response from here (handled stays true)
|
|
2225
|
+
void this.handleDispatchError(error, httpReq, httpRes);
|
|
2226
|
+
}
|
|
2227
|
+
if (handled !== true) {
|
|
2228
|
+
if (handled && typeof handled.then === 'function') {
|
|
2229
|
+
handled.then(ok => {
|
|
2230
|
+
if (!ok)
|
|
2231
|
+
void this.finishUnrouted(httpReq, httpRes);
|
|
2232
|
+
}, error => void this.handleDispatchError(error, httpReq, httpRes));
|
|
2233
|
+
}
|
|
2234
|
+
else {
|
|
2235
|
+
// Sync miss from the router - direct-route fallback, then 404
|
|
2236
|
+
void this.finishUnrouted(httpReq, httpRes);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2195
2240
|
// The engine invokes onRequest with a plain V8 call from its own uv
|
|
2196
2241
|
// callback, outside a Node callback scope, so microtasks queued during
|
|
2197
2242
|
// dispatch - an await on an already-settled promise, a .then chain, the
|
|
@@ -2357,7 +2402,11 @@ export class MoroEngineServer {
|
|
|
2357
2402
|
const raw = this._getBody(httpReq._reqId);
|
|
2358
2403
|
if (!raw || raw.byteLength === 0)
|
|
2359
2404
|
return;
|
|
2360
|
-
|
|
2405
|
+
// One header through one crossing: materialising the header object here
|
|
2406
|
+
// would cost every request with a body a string per header line.
|
|
2407
|
+
const contentType = (httpReq._headers !== undefined
|
|
2408
|
+
? httpReq._headers['content-type']
|
|
2409
|
+
: this._getHeader(httpReq._reqId, 'content-type')) || '';
|
|
2361
2410
|
const isMultipart = contentType.includes('multipart/form-data');
|
|
2362
2411
|
const maxSize = isMultipart ? this.maxUploadSize : this.maxBodySize;
|
|
2363
2412
|
// The engine natively enforces the larger of the two limits; the tighter
|