@morojs/moro 1.8.8 → 1.8.9
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 +1 -0
- package/dist/core/http/moro-engine-server.js +104 -35
- package/dist/core/http/moro-engine-server.js.map +1 -1
- package/dist/core/http/uws-http-server.d.ts +1 -0
- package/dist/core/http/uws-http-server.js +102 -20
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/routing/unified-router.js +60 -29
- package/dist/core/routing/unified-router.js.map +1 -1
- package/package.json +1 -1
|
@@ -268,6 +268,7 @@ export declare class MoroEngineServer {
|
|
|
268
268
|
private handleDispatchError;
|
|
269
269
|
private parseBody;
|
|
270
270
|
private executeMiddleware;
|
|
271
|
+
private dispatchMiddleware;
|
|
271
272
|
use(middleware: Middleware): void;
|
|
272
273
|
setRequestTracking(enabled: boolean): void;
|
|
273
274
|
setHookManager(hookManager: any): void;
|
|
@@ -80,6 +80,12 @@ const SENDFILE_BUFFER_LIMIT = 1024 * 1024;
|
|
|
80
80
|
// the response has no user-set headers, so no header object or flat array is
|
|
81
81
|
// allocated. The engine only reads it; never mutate.
|
|
82
82
|
const JSON_HEADER_PAIR = ['content-type', 'application/json'];
|
|
83
|
+
// Content-Type sniff for send(): does the body start with '{' or '[' after
|
|
84
|
+
// optional leading whitespace? Regex `\s` matches exactly the set trimStart()
|
|
85
|
+
// strips, and the match stops at the first non-space character, so this costs
|
|
86
|
+
// O(leading whitespace) and allocates nothing - trimStart() copied the entire
|
|
87
|
+
// body just to read one character.
|
|
88
|
+
const JSON_BODY_START = /^\s*[{[]/;
|
|
83
89
|
/**
|
|
84
90
|
* Prototype-based request object for the engine adapter.
|
|
85
91
|
*
|
|
@@ -705,11 +711,9 @@ export class EngineResponse extends LazyEventEmitter {
|
|
|
705
711
|
// Buffers, text/plain otherwise.
|
|
706
712
|
if (!('content-type' in this.responseHeaders)) {
|
|
707
713
|
if (typeof data === 'string') {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
? 'application/json; charset=utf-8'
|
|
712
|
-
: 'text/plain; charset=utf-8';
|
|
714
|
+
this.responseHeaders['content-type'] = JSON_BODY_START.test(data)
|
|
715
|
+
? 'application/json; charset=utf-8'
|
|
716
|
+
: 'text/plain; charset=utf-8';
|
|
713
717
|
}
|
|
714
718
|
else {
|
|
715
719
|
this.responseHeaders['content-type'] = 'application/octet-stream';
|
|
@@ -1816,9 +1820,13 @@ export class MoroEngineServer {
|
|
|
1816
1820
|
// rejection). Registration of the response hooks above still stands.
|
|
1817
1821
|
if (httpRes.headersSent)
|
|
1818
1822
|
return;
|
|
1819
|
-
// Execute global middleware chain
|
|
1823
|
+
// Execute global middleware chain. executeMiddleware returns undefined
|
|
1824
|
+
// when the whole chain completed synchronously, so a sync chain costs
|
|
1825
|
+
// zero promise allocations.
|
|
1820
1826
|
if (this.globalMiddleware.length > 0) {
|
|
1821
|
-
|
|
1827
|
+
const mwResult = this.executeMiddleware(this.globalMiddleware, httpReq, httpRes);
|
|
1828
|
+
if (mwResult)
|
|
1829
|
+
await mwResult;
|
|
1822
1830
|
if (httpRes.headersSent)
|
|
1823
1831
|
return;
|
|
1824
1832
|
}
|
|
@@ -1957,42 +1965,103 @@ export class MoroEngineServer {
|
|
|
1957
1965
|
throw error;
|
|
1958
1966
|
}
|
|
1959
1967
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
+
// Middleware execution with Express-compatible error propagation.
|
|
1969
|
+
// Supports: 3-arg (req, res, next), 4-arg (err, req, res, next) error middlewares,
|
|
1970
|
+
// and next(err) to skip forward to the next error middleware.
|
|
1971
|
+
//
|
|
1972
|
+
// Sync-aware dispatch (mirrors MoroHttpServer.executeMiddleware): middleware
|
|
1973
|
+
// that calls next() synchronously (or throws synchronously) advances the
|
|
1974
|
+
// chain in a plain loop with ZERO promise allocations. A promise is only
|
|
1975
|
+
// created when a middleware actually completes asynchronously. Returns
|
|
1976
|
+
// undefined when the whole chain ran synchronously, so the caller can skip
|
|
1977
|
+
// its await - one sync global middleware used to cost a promise, a closure
|
|
1978
|
+
// and an async-context suspension on every single request.
|
|
1979
|
+
executeMiddleware(middleware, req, res) {
|
|
1980
|
+
return this.dispatchMiddleware(middleware, req, res, 0, undefined);
|
|
1981
|
+
}
|
|
1982
|
+
dispatchMiddleware(middleware, req, res, startIndex, initialError) {
|
|
1983
|
+
const len = middleware.length;
|
|
1984
|
+
let activeError = initialError;
|
|
1985
|
+
let i = startIndex;
|
|
1986
|
+
while (i < len) {
|
|
1968
1987
|
if (res.headersSent)
|
|
1969
|
-
|
|
1988
|
+
return;
|
|
1989
|
+
const mw = middleware[i];
|
|
1990
|
+
i++;
|
|
1970
1991
|
const isErrorHandler = mw.length >= 4;
|
|
1971
|
-
|
|
1992
|
+
// Non-error middleware is skipped while an error is active; error middleware
|
|
1993
|
+
// is skipped while no error is active. Matches Express semantics.
|
|
1994
|
+
if (activeError !== undefined && !isErrorHandler)
|
|
1972
1995
|
continue;
|
|
1973
|
-
if (
|
|
1996
|
+
if (activeError === undefined && isErrorHandler)
|
|
1974
1997
|
continue;
|
|
1998
|
+
let settled = false;
|
|
1999
|
+
let settledError = undefined;
|
|
2000
|
+
let asyncResolve;
|
|
2001
|
+
const next = (err) => {
|
|
2002
|
+
if (settled)
|
|
2003
|
+
return;
|
|
2004
|
+
settled = true;
|
|
2005
|
+
settledError = err;
|
|
2006
|
+
if (asyncResolve)
|
|
2007
|
+
asyncResolve(err);
|
|
2008
|
+
};
|
|
2009
|
+
let result;
|
|
1975
2010
|
try {
|
|
1976
|
-
|
|
1977
|
-
const next = (err) => (err ? reject(err) : resolve());
|
|
1978
|
-
try {
|
|
1979
|
-
const result = error ? mw(error, req, res, next) : mw(req, res, next);
|
|
1980
|
-
if (result && typeof result.then === 'function') {
|
|
1981
|
-
result.then(() => resolve(), reject);
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
catch (e) {
|
|
1985
|
-
reject(e);
|
|
1986
|
-
}
|
|
1987
|
-
});
|
|
1988
|
-
error = null; // handler (error or normal) completed cleanly
|
|
2011
|
+
result = isErrorHandler ? mw(activeError, req, res, next) : mw(req, res, next);
|
|
1989
2012
|
}
|
|
1990
|
-
catch (
|
|
1991
|
-
|
|
2013
|
+
catch (err) {
|
|
2014
|
+
if (!settled) {
|
|
2015
|
+
settled = true;
|
|
2016
|
+
settledError = err;
|
|
2017
|
+
}
|
|
1992
2018
|
}
|
|
2019
|
+
const isThenable = result && typeof result.then === 'function';
|
|
2020
|
+
if (settled) {
|
|
2021
|
+
// Completed synchronously (next() called sync, or threw sync). A still
|
|
2022
|
+
// pending returned promise no longer gates the chain once next() has
|
|
2023
|
+
// been called; swallow late rejections so they never go unhandled.
|
|
2024
|
+
if (isThenable) {
|
|
2025
|
+
result.then(undefined, () => { });
|
|
2026
|
+
}
|
|
2027
|
+
activeError = settledError;
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
if (!isThenable && res.headersSent) {
|
|
2031
|
+
// Terminal middleware (Express style): it answered the request and
|
|
2032
|
+
// deliberately never calls next(). The chain is finished - skip
|
|
2033
|
+
// allocating a promise that nothing would ever settle (the response was
|
|
2034
|
+
// already sent, so this only ever produced immediate garbage).
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
if (!isThenable) {
|
|
2038
|
+
// Sync-looking middleware that neither called next() nor returned a
|
|
2039
|
+
// promise: it will call next() later (e.g. from an event callback).
|
|
2040
|
+
// Fall back to a promise for the remainder of the chain.
|
|
2041
|
+
const resumeIndex = i;
|
|
2042
|
+
return new Promise(resolve => {
|
|
2043
|
+
asyncResolve = resolve;
|
|
2044
|
+
}).then(err => this.dispatchMiddleware(middleware, req, res, resumeIndex, err));
|
|
2045
|
+
}
|
|
2046
|
+
// Async middleware: completion is next() OR promise settle, whichever
|
|
2047
|
+
// happens first (same semantics as the previous implementation).
|
|
2048
|
+
const resumeIndex = i;
|
|
2049
|
+
return new Promise(resolve => {
|
|
2050
|
+
asyncResolve = resolve;
|
|
2051
|
+
result.then(() => {
|
|
2052
|
+
if (!settled)
|
|
2053
|
+
next();
|
|
2054
|
+
}, (err) => {
|
|
2055
|
+
if (!settled)
|
|
2056
|
+
next(err);
|
|
2057
|
+
});
|
|
2058
|
+
}).then(err => this.dispatchMiddleware(middleware, req, res, resumeIndex, err));
|
|
2059
|
+
}
|
|
2060
|
+
// If an error is still unhandled after the chain, re-throw so the top-level
|
|
2061
|
+
// catch in handleRequest can invoke the registered errorHandler / default 500.
|
|
2062
|
+
if (activeError !== undefined) {
|
|
2063
|
+
throw activeError;
|
|
1993
2064
|
}
|
|
1994
|
-
if (error)
|
|
1995
|
-
throw error;
|
|
1996
2065
|
}
|
|
1997
2066
|
// Public API - matches MoroHttpServer/UWebSocketsHttpServer interface
|
|
1998
2067
|
use(middleware) {
|