|
|
@@ -1836,7 +1836,6 @@ var require_dist = __commonJS({
|
|
|
// src/index.js
|
|
|
var src_exports = {};
|
|
|
__export(src_exports, {
|
|
|
- BUFFER_ENCODING_LIST: () => BUFFER_ENCODING_LIST,
|
|
|
BodyParser: () => BodyParser,
|
|
|
CookieParser: () => CookieParser,
|
|
|
DataSender: () => DataSender,
|
|
|
@@ -1855,18 +1854,7 @@ __export(src_exports, {
|
|
|
RouterOptions: () => RouterOptions,
|
|
|
TrieRouter: () => TrieRouter,
|
|
|
UNPARSABLE_MEDIA_TYPES: () => UNPARSABLE_MEDIA_TYPES,
|
|
|
- createCookieString: () => createCookieString,
|
|
|
- createDebugger: () => createDebugger,
|
|
|
- createError: () => createError,
|
|
|
- fetchRequestBody: () => fetchRequestBody,
|
|
|
- getRequestPathname: () => getRequestPathname,
|
|
|
- isPromise: () => isPromise,
|
|
|
- isReadableStream: () => isReadableStream,
|
|
|
- isResponseSent: () => isResponseSent,
|
|
|
- isWritableStream: () => isWritableStream,
|
|
|
- parseCookie: () => parseCookie,
|
|
|
- parseJsonBody: () => parseJsonBody,
|
|
|
- toCamelCase: () => toCamelCase
|
|
|
+ parseJsonBody: () => parseJsonBody
|
|
|
});
|
|
|
module.exports = __toCommonJS(src_exports);
|
|
|
|
|
|
@@ -2161,23 +2149,6 @@ function fetchRequestBody(req, bodyBytesLimit = 0) {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-// src/utils/create-cookie-string.js
|
|
|
-function createCookieString(data) {
|
|
|
- if (!data || typeof data !== "object" || Array.isArray(data))
|
|
|
- throw new Errorf(
|
|
|
- 'The first parameter of "createCookieString" should be an Object, but %v given.',
|
|
|
- data
|
|
|
- );
|
|
|
- let cookies = "";
|
|
|
- for (const key in data) {
|
|
|
- if (!Object.prototype.hasOwnProperty.call(data, key)) continue;
|
|
|
- const val = data[key];
|
|
|
- if (val == null) continue;
|
|
|
- cookies += `${key}=${val}; `;
|
|
|
- }
|
|
|
- return cookies.trim();
|
|
|
-}
|
|
|
-
|
|
|
// src/utils/get-request-pathname.js
|
|
|
function getRequestPathname(req) {
|
|
|
if (!req || typeof req !== "object" || Array.isArray(req) || typeof req.url !== "string") {
|
|
|
@@ -2189,135 +2160,6 @@ function getRequestPathname(req) {
|
|
|
return (req.url || "/").replace(/\?.*$/, "");
|
|
|
}
|
|
|
|
|
|
-// src/route.js
|
|
|
-var HTTP_METHOD = {
|
|
|
- GET: "GET",
|
|
|
- POST: "POST",
|
|
|
- PUT: "PUT",
|
|
|
- PATCH: "PATCH",
|
|
|
- DELETE: "DELETE"
|
|
|
-};
|
|
|
-var debug = createDebugger("route");
|
|
|
-var Route = class {
|
|
|
- /**
|
|
|
- * Method.
|
|
|
- *
|
|
|
- * @type {string}
|
|
|
- * @private
|
|
|
- */
|
|
|
- _method;
|
|
|
- /**
|
|
|
- * Getter of the method.
|
|
|
- *
|
|
|
- * @returns {string}
|
|
|
- */
|
|
|
- get method() {
|
|
|
- return this._method;
|
|
|
- }
|
|
|
- /**
|
|
|
- * Path template.
|
|
|
- *
|
|
|
- * @type {string}
|
|
|
- * @private
|
|
|
- */
|
|
|
- _path;
|
|
|
- /**
|
|
|
- * Getter of the path.
|
|
|
- *
|
|
|
- * @returns {string}
|
|
|
- */
|
|
|
- get path() {
|
|
|
- return this._path;
|
|
|
- }
|
|
|
- /**
|
|
|
- * Handler.
|
|
|
- *
|
|
|
- * @type {RouteHandler}
|
|
|
- * @private
|
|
|
- */
|
|
|
- _handler;
|
|
|
- /**
|
|
|
- * Getter of the handler.
|
|
|
- *
|
|
|
- * @returns {*}
|
|
|
- */
|
|
|
- get handler() {
|
|
|
- return this._handler;
|
|
|
- }
|
|
|
- /**
|
|
|
- * Hook registry.
|
|
|
- *
|
|
|
- * @type {HookRegistry}
|
|
|
- * @private
|
|
|
- */
|
|
|
- _hookRegistry = new HookRegistry();
|
|
|
- /**
|
|
|
- * Getter of the hook registry.
|
|
|
- *
|
|
|
- * @returns {HookRegistry}
|
|
|
- */
|
|
|
- get hookRegistry() {
|
|
|
- return this._hookRegistry;
|
|
|
- }
|
|
|
- /**
|
|
|
- * Constructor.
|
|
|
- *
|
|
|
- * @param {RouteDefinition} routeDef
|
|
|
- */
|
|
|
- constructor(routeDef) {
|
|
|
- if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef))
|
|
|
- throw new Errorf(
|
|
|
- "The first parameter of Route.controller should be an Object, but %v given.",
|
|
|
- routeDef
|
|
|
- );
|
|
|
- if (!routeDef.method || typeof routeDef.method !== "string")
|
|
|
- throw new Errorf(
|
|
|
- 'The option "method" of the Route should be a non-empty String, but %v given.',
|
|
|
- routeDef.method
|
|
|
- );
|
|
|
- this._method = routeDef.method.toUpperCase();
|
|
|
- if (typeof routeDef.path !== "string")
|
|
|
- throw new Errorf(
|
|
|
- 'The option "path" of the Route should be a String, but %v given.',
|
|
|
- routeDef.path
|
|
|
- );
|
|
|
- this._path = routeDef.path;
|
|
|
- if (typeof routeDef.handler !== "function")
|
|
|
- throw new Errorf(
|
|
|
- 'The option "handler" of the Route should be a Function, but %v given.',
|
|
|
- routeDef.handler
|
|
|
- );
|
|
|
- this._handler = routeDef.handler;
|
|
|
- if (routeDef.preHandler != null) {
|
|
|
- const preHandlerHooks = Array.isArray(routeDef.preHandler) ? routeDef.preHandler : [routeDef.preHandler];
|
|
|
- preHandlerHooks.forEach((hook) => {
|
|
|
- this._hookRegistry.addHook(HOOK_NAME.PRE_HANDLER, hook);
|
|
|
- });
|
|
|
- }
|
|
|
- if (routeDef.postHandler != null) {
|
|
|
- const postHandlerHooks = Array.isArray(routeDef.postHandler) ? routeDef.postHandler : [routeDef.postHandler];
|
|
|
- postHandlerHooks.forEach((hook) => {
|
|
|
- this._hookRegistry.addHook(HOOK_NAME.POST_HANDLER, hook);
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- /**
|
|
|
- * Handle request.
|
|
|
- *
|
|
|
- * @param {RequestContext} context
|
|
|
- * @returns {*}
|
|
|
- */
|
|
|
- handle(context) {
|
|
|
- const requestPath = getRequestPathname(context.req);
|
|
|
- debug(
|
|
|
- "Invoking the Route handler for the request %s %v.",
|
|
|
- this.method.toUpperCase(),
|
|
|
- requestPath
|
|
|
- );
|
|
|
- return this._handler(context);
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
// node_modules/@e22m4u/js-service/src/errors/invalid-argument-error.js
|
|
|
var InvalidArgumentError = class extends Errorf {
|
|
|
};
|
|
|
@@ -2669,261 +2511,132 @@ var HookInvoker = class extends DebuggableService {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-// src/parsers/body-parser.js
|
|
|
-var import_http_errors2 = __toESM(require_http_errors(), 1);
|
|
|
-
|
|
|
-// src/router-options.js
|
|
|
-var RouterOptions = class extends DebuggableService {
|
|
|
+// src/route.js
|
|
|
+var HTTP_METHOD = {
|
|
|
+ GET: "GET",
|
|
|
+ POST: "POST",
|
|
|
+ PUT: "PUT",
|
|
|
+ PATCH: "PATCH",
|
|
|
+ DELETE: "DELETE"
|
|
|
+};
|
|
|
+var debug = createDebugger("route");
|
|
|
+var Route = class {
|
|
|
/**
|
|
|
- * Request body bytes limit.
|
|
|
+ * Method.
|
|
|
*
|
|
|
- * @type {number}
|
|
|
+ * @type {string}
|
|
|
* @private
|
|
|
*/
|
|
|
- _requestBodyBytesLimit = 512e3;
|
|
|
- // 512kb
|
|
|
+ _method;
|
|
|
/**
|
|
|
- * Getter of request body bytes limit.
|
|
|
+ * Getter of the method.
|
|
|
*
|
|
|
- * @returns {number}
|
|
|
+ * @returns {string}
|
|
|
*/
|
|
|
- get requestBodyBytesLimit() {
|
|
|
- return this._requestBodyBytesLimit;
|
|
|
+ get method() {
|
|
|
+ return this._method;
|
|
|
}
|
|
|
/**
|
|
|
- * Set request body bytes limit.
|
|
|
+ * Path template.
|
|
|
*
|
|
|
- * @param {number} input
|
|
|
- * @returns {RouterOptions}
|
|
|
+ * @type {string}
|
|
|
+ * @private
|
|
|
*/
|
|
|
- setRequestBodyBytesLimit(input) {
|
|
|
- if (typeof input !== "number" || input < 0)
|
|
|
- throw new Errorf(
|
|
|
- 'The option "requestBodyBytesLimit" must be a positive Number or 0, but %v given.',
|
|
|
- input
|
|
|
- );
|
|
|
- this._requestBodyBytesLimit = input;
|
|
|
- return this;
|
|
|
+ _path;
|
|
|
+ /**
|
|
|
+ * Getter of the path.
|
|
|
+ *
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+ get path() {
|
|
|
+ return this._path;
|
|
|
}
|
|
|
-};
|
|
|
-
|
|
|
-// src/parsers/body-parser.js
|
|
|
-var METHODS_WITH_BODY = ["POST", "PUT", "PATCH", "DELETE"];
|
|
|
-var UNPARSABLE_MEDIA_TYPES = ["multipart/form-data"];
|
|
|
-var BodyParser = class extends DebuggableService {
|
|
|
/**
|
|
|
- * Parsers.
|
|
|
+ * Handler.
|
|
|
*
|
|
|
- * @type {{[mime: string]: Function}}
|
|
|
+ * @type {RouteHandler}
|
|
|
+ * @private
|
|
|
*/
|
|
|
- _parsers = {
|
|
|
- "text/plain": (v) => String(v),
|
|
|
- "application/json": parseJsonBody
|
|
|
- };
|
|
|
+ _handler;
|
|
|
/**
|
|
|
- * Set parser.
|
|
|
+ * Getter of the handler.
|
|
|
*
|
|
|
- * @param {string} mediaType
|
|
|
- * @param {Function} parser
|
|
|
- * @returns {this}
|
|
|
+ * @returns {*}
|
|
|
*/
|
|
|
- defineParser(mediaType, parser) {
|
|
|
- if (!mediaType || typeof mediaType !== "string")
|
|
|
- throw new Errorf(
|
|
|
- 'The parameter "mediaType" of BodyParser.defineParser should be a non-empty String, but %v given.',
|
|
|
- mediaType
|
|
|
- );
|
|
|
- if (!parser || typeof parser !== "function")
|
|
|
- throw new Errorf(
|
|
|
- 'The parameter "parser" of BodyParser.defineParser should be a Function, but %v given.',
|
|
|
- parser
|
|
|
- );
|
|
|
- this._parsers[mediaType] = parser;
|
|
|
- return this;
|
|
|
+ get handler() {
|
|
|
+ return this._handler;
|
|
|
}
|
|
|
/**
|
|
|
- * Has parser.
|
|
|
+ * Hook registry.
|
|
|
*
|
|
|
- * @param {string} mediaType
|
|
|
- * @returns {boolean}
|
|
|
+ * @type {HookRegistry}
|
|
|
+ * @private
|
|
|
*/
|
|
|
- hasParser(mediaType) {
|
|
|
- if (!mediaType || typeof mediaType !== "string")
|
|
|
- throw new Errorf(
|
|
|
- 'The parameter "mediaType" of BodyParser.hasParser should be a non-empty String, but %v given.',
|
|
|
- mediaType
|
|
|
- );
|
|
|
- return Boolean(this._parsers[mediaType]);
|
|
|
- }
|
|
|
+ _hookRegistry = new HookRegistry();
|
|
|
/**
|
|
|
- * Delete parser.
|
|
|
+ * Getter of the hook registry.
|
|
|
*
|
|
|
- * @param {string} mediaType
|
|
|
- * @returns {this}
|
|
|
+ * @returns {HookRegistry}
|
|
|
*/
|
|
|
- deleteParser(mediaType) {
|
|
|
- if (!mediaType || typeof mediaType !== "string")
|
|
|
- throw new Errorf(
|
|
|
- 'The parameter "mediaType" of BodyParser.deleteParser should be a non-empty String, but %v given.',
|
|
|
- mediaType
|
|
|
- );
|
|
|
- const parser = this._parsers[mediaType];
|
|
|
- if (!parser) throw new Errorf("The parser of %v is not found.", mediaType);
|
|
|
- delete this._parsers[mediaType];
|
|
|
- return this;
|
|
|
+ get hookRegistry() {
|
|
|
+ return this._hookRegistry;
|
|
|
}
|
|
|
/**
|
|
|
- * Parse.
|
|
|
+ * Constructor.
|
|
|
*
|
|
|
- * @param {import('http').IncomingMessage} req
|
|
|
- * @returns {Promise<*>|undefined}
|
|
|
+ * @param {RouteDefinition} routeDef
|
|
|
*/
|
|
|
- parse(req) {
|
|
|
- if (!METHODS_WITH_BODY.includes(req.method.toUpperCase())) {
|
|
|
- this.debug(
|
|
|
- "Body parsing was skipped for the %s request.",
|
|
|
- req.method.toUpperCase()
|
|
|
+ constructor(routeDef) {
|
|
|
+ if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef))
|
|
|
+ throw new Errorf(
|
|
|
+ "The first parameter of Route.controller should be an Object, but %v given.",
|
|
|
+ routeDef
|
|
|
);
|
|
|
- return;
|
|
|
- }
|
|
|
- const contentType = (req.headers["content-type"] || "").replace(
|
|
|
- /^([^;]+);.*$/,
|
|
|
- "$1"
|
|
|
- );
|
|
|
- if (!contentType) {
|
|
|
- this.debug(
|
|
|
- "Body parsing was skipped because the request has no content type."
|
|
|
+ if (!routeDef.method || typeof routeDef.method !== "string")
|
|
|
+ throw new Errorf(
|
|
|
+ 'The option "method" of the Route should be a non-empty String, but %v given.',
|
|
|
+ routeDef.method
|
|
|
);
|
|
|
- return;
|
|
|
- }
|
|
|
- const { mediaType } = parseContentType(contentType);
|
|
|
- if (!mediaType)
|
|
|
- throw createError(
|
|
|
- import_http_errors2.default.BadRequest,
|
|
|
- 'Unable to parse the "content-type" header.'
|
|
|
+ this._method = routeDef.method.toUpperCase();
|
|
|
+ if (typeof routeDef.path !== "string")
|
|
|
+ throw new Errorf(
|
|
|
+ 'The option "path" of the Route should be a String, but %v given.',
|
|
|
+ routeDef.path
|
|
|
);
|
|
|
- const parser = this._parsers[mediaType];
|
|
|
- if (!parser) {
|
|
|
- if (UNPARSABLE_MEDIA_TYPES.includes(mediaType)) {
|
|
|
- this.debug("Body parsing was skipped for %v.", mediaType);
|
|
|
- return;
|
|
|
- }
|
|
|
- throw createError(
|
|
|
- import_http_errors2.default.UnsupportedMediaType,
|
|
|
- "Media type %v is not supported.",
|
|
|
- mediaType
|
|
|
+ this._path = routeDef.path;
|
|
|
+ if (typeof routeDef.handler !== "function")
|
|
|
+ throw new Errorf(
|
|
|
+ 'The option "handler" of the Route should be a Function, but %v given.',
|
|
|
+ routeDef.handler
|
|
|
);
|
|
|
- }
|
|
|
- const bodyBytesLimit = this.getService(RouterOptions).requestBodyBytesLimit;
|
|
|
- return fetchRequestBody(req, bodyBytesLimit).then((rawBody) => {
|
|
|
- if (rawBody != null) return parser(rawBody);
|
|
|
- return rawBody;
|
|
|
- });
|
|
|
- }
|
|
|
-};
|
|
|
-function parseJsonBody(input) {
|
|
|
- if (typeof input !== "string") return void 0;
|
|
|
- try {
|
|
|
- return JSON.parse(input);
|
|
|
- } catch (error) {
|
|
|
- if (process.env["DEBUG"] || process.env["NODE_ENV"] === "development")
|
|
|
- console.warn(error);
|
|
|
- throw createError(import_http_errors2.default.BadRequest, "Unable to parse request body.");
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// src/parsers/query-parser.js
|
|
|
-var import_querystring = __toESM(require("querystring"), 1);
|
|
|
-var QueryParser = class extends DebuggableService {
|
|
|
- /**
|
|
|
- * Parse
|
|
|
- *
|
|
|
- * @param {import('http').IncomingMessage} req
|
|
|
- * @returns {object}
|
|
|
- */
|
|
|
- parse(req) {
|
|
|
- const queryStr = req.url.replace(/^[^?]*\??/, "");
|
|
|
- const query = queryStr ? import_querystring.default.parse(queryStr) : {};
|
|
|
- const queryKeys = Object.keys(query);
|
|
|
- if (queryKeys.length) {
|
|
|
- queryKeys.forEach((key) => {
|
|
|
- this.debug("The query %v has the value %v.", key, query[key]);
|
|
|
+ this._handler = routeDef.handler;
|
|
|
+ if (routeDef.preHandler != null) {
|
|
|
+ const preHandlerHooks = Array.isArray(routeDef.preHandler) ? routeDef.preHandler : [routeDef.preHandler];
|
|
|
+ preHandlerHooks.forEach((hook) => {
|
|
|
+ this._hookRegistry.addHook(HOOK_NAME.PRE_HANDLER, hook);
|
|
|
});
|
|
|
- } else {
|
|
|
- this.debug(
|
|
|
- "The request %s %v has no query.",
|
|
|
- req.method,
|
|
|
- getRequestPathname(req)
|
|
|
- );
|
|
|
}
|
|
|
- return query;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-// src/parsers/cookie-parser.js
|
|
|
-var CookieParser = class extends DebuggableService {
|
|
|
- /**
|
|
|
- * Parse
|
|
|
- *
|
|
|
- * @param {import('http').IncomingMessage} req
|
|
|
- * @returns {object}
|
|
|
- */
|
|
|
- parse(req) {
|
|
|
- const cookieString = req.headers["cookie"] || "";
|
|
|
- const cookie = parseCookie(cookieString);
|
|
|
- const cookieKeys = Object.keys(cookie);
|
|
|
- if (cookieKeys.length) {
|
|
|
- cookieKeys.forEach((key) => {
|
|
|
- this.debug("The cookie %v has the value %v.", key, cookie[key]);
|
|
|
+ if (routeDef.postHandler != null) {
|
|
|
+ const postHandlerHooks = Array.isArray(routeDef.postHandler) ? routeDef.postHandler : [routeDef.postHandler];
|
|
|
+ postHandlerHooks.forEach((hook) => {
|
|
|
+ this._hookRegistry.addHook(HOOK_NAME.POST_HANDLER, hook);
|
|
|
});
|
|
|
- } else {
|
|
|
- this.debug(
|
|
|
- "The request %s %v has no cookie.",
|
|
|
- req.method,
|
|
|
- getRequestPathname(req)
|
|
|
- );
|
|
|
}
|
|
|
- return cookie;
|
|
|
}
|
|
|
-};
|
|
|
-
|
|
|
-// src/parsers/request-parser.js
|
|
|
-var import_http2 = require("http");
|
|
|
-var RequestParser = class extends DebuggableService {
|
|
|
/**
|
|
|
- * Parse.
|
|
|
+ * Handle request.
|
|
|
*
|
|
|
- * @param {IncomingMessage} req
|
|
|
- * @returns {Promise<object>|object}
|
|
|
+ * @param {RequestContext} context
|
|
|
+ * @returns {*}
|
|
|
*/
|
|
|
- parse(req) {
|
|
|
- if (!(req instanceof import_http2.IncomingMessage))
|
|
|
- throw new Errorf(
|
|
|
- "The first argument of RequestParser.parse should be an instance of IncomingMessage, but %v given.",
|
|
|
- req
|
|
|
- );
|
|
|
- const data = {};
|
|
|
- const promises = [];
|
|
|
- const parsedQuery = this.getService(QueryParser).parse(req);
|
|
|
- if (isPromise(parsedQuery)) {
|
|
|
- promises.push(parsedQuery.then((v) => data.query = v));
|
|
|
- } else {
|
|
|
- data.query = parsedQuery;
|
|
|
- }
|
|
|
- const parsedCookie = this.getService(CookieParser).parse(req);
|
|
|
- if (isPromise(parsedCookie)) {
|
|
|
- promises.push(parsedCookie.then((v) => data.cookie = v));
|
|
|
- } else {
|
|
|
- data.cookie = parsedCookie;
|
|
|
- }
|
|
|
- const parsedBody = this.getService(BodyParser).parse(req);
|
|
|
- if (isPromise(parsedBody)) {
|
|
|
- promises.push(parsedBody.then((v) => data.body = v));
|
|
|
- } else {
|
|
|
- data.body = parsedBody;
|
|
|
- }
|
|
|
- data.headers = JSON.parse(JSON.stringify(req.headers));
|
|
|
- return promises.length ? Promise.all(promises).then(() => data) : data;
|
|
|
+ handle(context) {
|
|
|
+ const requestPath = getRequestPathname(context.req);
|
|
|
+ debug(
|
|
|
+ "Invoking the Route handler for the request %s %v.",
|
|
|
+ this.method.toUpperCase(),
|
|
|
+ requestPath
|
|
|
+ );
|
|
|
+ return this._handler(context);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -3054,117 +2767,261 @@ var ErrorSender = class extends DebuggableService {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-// src/request-context.js
|
|
|
-var RequestContext = class {
|
|
|
- /**
|
|
|
- * Service container.
|
|
|
- *
|
|
|
- * @type {import('@e22m4u/js-service').ServiceContainer}
|
|
|
- */
|
|
|
- container;
|
|
|
+// src/parsers/body-parser.js
|
|
|
+var import_http_errors2 = __toESM(require_http_errors(), 1);
|
|
|
+
|
|
|
+// src/router-options.js
|
|
|
+var RouterOptions = class extends DebuggableService {
|
|
|
/**
|
|
|
- * Request.
|
|
|
+ * Request body bytes limit.
|
|
|
*
|
|
|
- * @type {import('http').IncomingMessage}
|
|
|
+ * @type {number}
|
|
|
+ * @private
|
|
|
*/
|
|
|
- req;
|
|
|
+ _requestBodyBytesLimit = 512e3;
|
|
|
+ // 512kb
|
|
|
/**
|
|
|
- * Response.
|
|
|
+ * Getter of request body bytes limit.
|
|
|
*
|
|
|
- * @type {import('http').ServerResponse}
|
|
|
+ * @returns {number}
|
|
|
*/
|
|
|
- res;
|
|
|
+ get requestBodyBytesLimit() {
|
|
|
+ return this._requestBodyBytesLimit;
|
|
|
+ }
|
|
|
/**
|
|
|
- * Query.
|
|
|
+ * Set request body bytes limit.
|
|
|
*
|
|
|
- * @type {object}
|
|
|
+ * @param {number} input
|
|
|
+ * @returns {RouterOptions}
|
|
|
*/
|
|
|
- query = {};
|
|
|
+ setRequestBodyBytesLimit(input) {
|
|
|
+ if (typeof input !== "number" || input < 0)
|
|
|
+ throw new Errorf(
|
|
|
+ 'The option "requestBodyBytesLimit" must be a positive Number or 0, but %v given.',
|
|
|
+ input
|
|
|
+ );
|
|
|
+ this._requestBodyBytesLimit = input;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// src/parsers/body-parser.js
|
|
|
+var METHODS_WITH_BODY = ["POST", "PUT", "PATCH", "DELETE"];
|
|
|
+var UNPARSABLE_MEDIA_TYPES = ["multipart/form-data"];
|
|
|
+var BodyParser = class extends DebuggableService {
|
|
|
/**
|
|
|
- * Path parameters.
|
|
|
+ * Parsers.
|
|
|
*
|
|
|
- * @type {object}
|
|
|
+ * @type {{[mime: string]: Function}}
|
|
|
*/
|
|
|
- params = {};
|
|
|
+ _parsers = {
|
|
|
+ "text/plain": (v) => String(v),
|
|
|
+ "application/json": parseJsonBody
|
|
|
+ };
|
|
|
/**
|
|
|
- * Parsed body.
|
|
|
+ * Set parser.
|
|
|
*
|
|
|
- * @type {*}
|
|
|
+ * @param {string} mediaType
|
|
|
+ * @param {Function} parser
|
|
|
+ * @returns {this}
|
|
|
*/
|
|
|
- body;
|
|
|
+ defineParser(mediaType, parser) {
|
|
|
+ if (!mediaType || typeof mediaType !== "string")
|
|
|
+ throw new Errorf(
|
|
|
+ 'The parameter "mediaType" of BodyParser.defineParser should be a non-empty String, but %v given.',
|
|
|
+ mediaType
|
|
|
+ );
|
|
|
+ if (!parser || typeof parser !== "function")
|
|
|
+ throw new Errorf(
|
|
|
+ 'The parameter "parser" of BodyParser.defineParser should be a Function, but %v given.',
|
|
|
+ parser
|
|
|
+ );
|
|
|
+ this._parsers[mediaType] = parser;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
/**
|
|
|
- * Headers.
|
|
|
+ * Has parser.
|
|
|
*
|
|
|
- * @type {object}
|
|
|
+ * @param {string} mediaType
|
|
|
+ * @returns {boolean}
|
|
|
*/
|
|
|
- headers = {};
|
|
|
+ hasParser(mediaType) {
|
|
|
+ if (!mediaType || typeof mediaType !== "string")
|
|
|
+ throw new Errorf(
|
|
|
+ 'The parameter "mediaType" of BodyParser.hasParser should be a non-empty String, but %v given.',
|
|
|
+ mediaType
|
|
|
+ );
|
|
|
+ return Boolean(this._parsers[mediaType]);
|
|
|
+ }
|
|
|
/**
|
|
|
- * Parsed cookie.
|
|
|
+ * Delete parser.
|
|
|
*
|
|
|
- * @type {object}
|
|
|
+ * @param {string} mediaType
|
|
|
+ * @returns {this}
|
|
|
*/
|
|
|
- cookie = {};
|
|
|
+ deleteParser(mediaType) {
|
|
|
+ if (!mediaType || typeof mediaType !== "string")
|
|
|
+ throw new Errorf(
|
|
|
+ 'The parameter "mediaType" of BodyParser.deleteParser should be a non-empty String, but %v given.',
|
|
|
+ mediaType
|
|
|
+ );
|
|
|
+ const parser = this._parsers[mediaType];
|
|
|
+ if (!parser) throw new Errorf("The parser of %v is not found.", mediaType);
|
|
|
+ delete this._parsers[mediaType];
|
|
|
+ return this;
|
|
|
+ }
|
|
|
/**
|
|
|
- * Method.
|
|
|
+ * Parse.
|
|
|
*
|
|
|
- * @returns {string}
|
|
|
+ * @param {import('http').IncomingMessage} req
|
|
|
+ * @returns {Promise<*>|undefined}
|
|
|
*/
|
|
|
- get method() {
|
|
|
- return this.req.method.toUpperCase();
|
|
|
+ parse(req) {
|
|
|
+ if (!METHODS_WITH_BODY.includes(req.method.toUpperCase())) {
|
|
|
+ this.debug(
|
|
|
+ "Body parsing was skipped for the %s request.",
|
|
|
+ req.method.toUpperCase()
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const contentType = (req.headers["content-type"] || "").replace(
|
|
|
+ /^([^;]+);.*$/,
|
|
|
+ "$1"
|
|
|
+ );
|
|
|
+ if (!contentType) {
|
|
|
+ this.debug(
|
|
|
+ "Body parsing was skipped because the request has no content type."
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const { mediaType } = parseContentType(contentType);
|
|
|
+ if (!mediaType)
|
|
|
+ throw createError(
|
|
|
+ import_http_errors2.default.BadRequest,
|
|
|
+ 'Unable to parse the "content-type" header.'
|
|
|
+ );
|
|
|
+ const parser = this._parsers[mediaType];
|
|
|
+ if (!parser) {
|
|
|
+ if (UNPARSABLE_MEDIA_TYPES.includes(mediaType)) {
|
|
|
+ this.debug("Body parsing was skipped for %v.", mediaType);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ throw createError(
|
|
|
+ import_http_errors2.default.UnsupportedMediaType,
|
|
|
+ "Media type %v is not supported.",
|
|
|
+ mediaType
|
|
|
+ );
|
|
|
+ }
|
|
|
+ const bodyBytesLimit = this.getService(RouterOptions).requestBodyBytesLimit;
|
|
|
+ return fetchRequestBody(req, bodyBytesLimit).then((rawBody) => {
|
|
|
+ if (rawBody != null) return parser(rawBody);
|
|
|
+ return rawBody;
|
|
|
+ });
|
|
|
}
|
|
|
- /**
|
|
|
- * Path.
|
|
|
- *
|
|
|
- * @returns {string}
|
|
|
- */
|
|
|
- get path() {
|
|
|
- return this.req.url;
|
|
|
+};
|
|
|
+function parseJsonBody(input) {
|
|
|
+ if (typeof input !== "string") return void 0;
|
|
|
+ try {
|
|
|
+ return JSON.parse(input);
|
|
|
+ } catch (error) {
|
|
|
+ if (process.env["DEBUG"] || process.env["NODE_ENV"] === "development")
|
|
|
+ console.warn(error);
|
|
|
+ throw createError(import_http_errors2.default.BadRequest, "Unable to parse request body.");
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+// src/parsers/query-parser.js
|
|
|
+var import_querystring = __toESM(require("querystring"), 1);
|
|
|
+var QueryParser = class extends DebuggableService {
|
|
|
/**
|
|
|
- * Pathname.
|
|
|
+ * Parse
|
|
|
*
|
|
|
- * @type {string|undefined}
|
|
|
- * @private
|
|
|
+ * @param {import('http').IncomingMessage} req
|
|
|
+ * @returns {object}
|
|
|
*/
|
|
|
- _pathname = void 0;
|
|
|
+ parse(req) {
|
|
|
+ const queryStr = req.url.replace(/^[^?]*\??/, "");
|
|
|
+ const query = queryStr ? import_querystring.default.parse(queryStr) : {};
|
|
|
+ const queryKeys = Object.keys(query);
|
|
|
+ if (queryKeys.length) {
|
|
|
+ queryKeys.forEach((key) => {
|
|
|
+ this.debug("The query %v has the value %v.", key, query[key]);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.debug(
|
|
|
+ "The request %s %v has no query.",
|
|
|
+ req.method,
|
|
|
+ getRequestPathname(req)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return query;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// src/parsers/cookie-parser.js
|
|
|
+var CookieParser = class extends DebuggableService {
|
|
|
/**
|
|
|
- * Pathname.
|
|
|
+ * Parse
|
|
|
*
|
|
|
- * @returns {string}
|
|
|
+ * @param {import('http').IncomingMessage} req
|
|
|
+ * @returns {object}
|
|
|
*/
|
|
|
- get pathname() {
|
|
|
- if (this._pathname != null) return this._pathname;
|
|
|
- this._pathname = getRequestPathname(this.req);
|
|
|
- return this._pathname;
|
|
|
+ parse(req) {
|
|
|
+ const cookieString = req.headers["cookie"] || "";
|
|
|
+ const cookie = parseCookie(cookieString);
|
|
|
+ const cookieKeys = Object.keys(cookie);
|
|
|
+ if (cookieKeys.length) {
|
|
|
+ cookieKeys.forEach((key) => {
|
|
|
+ this.debug("The cookie %v has the value %v.", key, cookie[key]);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.debug(
|
|
|
+ "The request %s %v has no cookie.",
|
|
|
+ req.method,
|
|
|
+ getRequestPathname(req)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return cookie;
|
|
|
}
|
|
|
+};
|
|
|
+
|
|
|
+// src/parsers/request-parser.js
|
|
|
+var import_http2 = require("http");
|
|
|
+var RequestParser = class extends DebuggableService {
|
|
|
/**
|
|
|
- * Constructor.
|
|
|
+ * Parse.
|
|
|
*
|
|
|
- * @param {ServiceContainer} container
|
|
|
- * @param {import('http').IncomingMessage} request
|
|
|
- * @param {import('http').ServerResponse} response
|
|
|
+ * @param {IncomingMessage} req
|
|
|
+ * @returns {Promise<object>|object}
|
|
|
*/
|
|
|
- constructor(container, request, response) {
|
|
|
- if (!(container instanceof ServiceContainer))
|
|
|
- throw new Errorf(
|
|
|
- 'The parameter "container" of RequestContext.constructor should be an instance of ServiceContainer, but %v given.',
|
|
|
- container
|
|
|
- );
|
|
|
- this.container = container;
|
|
|
- if (!request || typeof request !== "object" || Array.isArray(request) || !isReadableStream(request)) {
|
|
|
+ parse(req) {
|
|
|
+ if (!(req instanceof import_http2.IncomingMessage))
|
|
|
throw new Errorf(
|
|
|
- 'The parameter "request" of RequestContext.constructor should be an instance of IncomingMessage, but %v given.',
|
|
|
- request
|
|
|
+ "The first argument of RequestParser.parse should be an instance of IncomingMessage, but %v given.",
|
|
|
+ req
|
|
|
);
|
|
|
+ const data = {};
|
|
|
+ const promises = [];
|
|
|
+ const parsedQuery = this.getService(QueryParser).parse(req);
|
|
|
+ if (isPromise(parsedQuery)) {
|
|
|
+ promises.push(parsedQuery.then((v) => data.query = v));
|
|
|
+ } else {
|
|
|
+ data.query = parsedQuery;
|
|
|
}
|
|
|
- this.req = request;
|
|
|
- if (!response || typeof response !== "object" || Array.isArray(response) || !isWritableStream(response)) {
|
|
|
- throw new Errorf(
|
|
|
- 'The parameter "response" of RequestContext.constructor should be an instance of ServerResponse, but %v given.',
|
|
|
- response
|
|
|
- );
|
|
|
+ const parsedCookie = this.getService(CookieParser).parse(req);
|
|
|
+ if (isPromise(parsedCookie)) {
|
|
|
+ promises.push(parsedCookie.then((v) => data.cookie = v));
|
|
|
+ } else {
|
|
|
+ data.cookie = parsedCookie;
|
|
|
}
|
|
|
- this.res = response;
|
|
|
+ const parsedBody = this.getService(BodyParser).parse(req);
|
|
|
+ if (isPromise(parsedBody)) {
|
|
|
+ promises.push(parsedBody.then((v) => data.body = v));
|
|
|
+ } else {
|
|
|
+ data.body = parsedBody;
|
|
|
+ }
|
|
|
+ data.headers = JSON.parse(JSON.stringify(req.headers));
|
|
|
+ return promises.length ? Promise.all(promises).then(() => data) : data;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -3521,6 +3378,120 @@ var RouteRegistry = class extends DebuggableService {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// src/request-context.js
|
|
|
+var RequestContext = class {
|
|
|
+ /**
|
|
|
+ * Service container.
|
|
|
+ *
|
|
|
+ * @type {import('@e22m4u/js-service').ServiceContainer}
|
|
|
+ */
|
|
|
+ container;
|
|
|
+ /**
|
|
|
+ * Request.
|
|
|
+ *
|
|
|
+ * @type {import('http').IncomingMessage}
|
|
|
+ */
|
|
|
+ req;
|
|
|
+ /**
|
|
|
+ * Response.
|
|
|
+ *
|
|
|
+ * @type {import('http').ServerResponse}
|
|
|
+ */
|
|
|
+ res;
|
|
|
+ /**
|
|
|
+ * Query.
|
|
|
+ *
|
|
|
+ * @type {object}
|
|
|
+ */
|
|
|
+ query = {};
|
|
|
+ /**
|
|
|
+ * Path parameters.
|
|
|
+ *
|
|
|
+ * @type {object}
|
|
|
+ */
|
|
|
+ params = {};
|
|
|
+ /**
|
|
|
+ * Parsed body.
|
|
|
+ *
|
|
|
+ * @type {*}
|
|
|
+ */
|
|
|
+ body;
|
|
|
+ /**
|
|
|
+ * Headers.
|
|
|
+ *
|
|
|
+ * @type {object}
|
|
|
+ */
|
|
|
+ headers = {};
|
|
|
+ /**
|
|
|
+ * Parsed cookie.
|
|
|
+ *
|
|
|
+ * @type {object}
|
|
|
+ */
|
|
|
+ cookie = {};
|
|
|
+ /**
|
|
|
+ * Method.
|
|
|
+ *
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+ get method() {
|
|
|
+ return this.req.method.toUpperCase();
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Path.
|
|
|
+ *
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+ get path() {
|
|
|
+ return this.req.url;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Pathname.
|
|
|
+ *
|
|
|
+ * @type {string|undefined}
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _pathname = void 0;
|
|
|
+ /**
|
|
|
+ * Pathname.
|
|
|
+ *
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+ get pathname() {
|
|
|
+ if (this._pathname != null) return this._pathname;
|
|
|
+ this._pathname = getRequestPathname(this.req);
|
|
|
+ return this._pathname;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Constructor.
|
|
|
+ *
|
|
|
+ * @param {ServiceContainer} container
|
|
|
+ * @param {import('http').IncomingMessage} request
|
|
|
+ * @param {import('http').ServerResponse} response
|
|
|
+ */
|
|
|
+ constructor(container, request, response) {
|
|
|
+ if (!(container instanceof ServiceContainer))
|
|
|
+ throw new Errorf(
|
|
|
+ 'The parameter "container" of RequestContext.constructor should be an instance of ServiceContainer, but %v given.',
|
|
|
+ container
|
|
|
+ );
|
|
|
+ this.container = container;
|
|
|
+ if (!request || typeof request !== "object" || Array.isArray(request) || !isReadableStream(request)) {
|
|
|
+ throw new Errorf(
|
|
|
+ 'The parameter "request" of RequestContext.constructor should be an instance of IncomingMessage, but %v given.',
|
|
|
+ request
|
|
|
+ );
|
|
|
+ }
|
|
|
+ this.req = request;
|
|
|
+ if (!response || typeof response !== "object" || Array.isArray(response) || !isWritableStream(response)) {
|
|
|
+ throw new Errorf(
|
|
|
+ 'The parameter "response" of RequestContext.constructor should be an instance of ServerResponse, but %v given.',
|
|
|
+ response
|
|
|
+ );
|
|
|
+ }
|
|
|
+ this.res = response;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
// src/trie-router.js
|
|
|
var TrieRouter = class extends DebuggableService {
|
|
|
/**
|
|
|
@@ -3669,7 +3640,6 @@ var TrieRouter = class extends DebuggableService {
|
|
|
};
|
|
|
// Annotate the CommonJS export names for ESM import in node:
|
|
|
0 && (module.exports = {
|
|
|
- BUFFER_ENCODING_LIST,
|
|
|
BodyParser,
|
|
|
CookieParser,
|
|
|
DataSender,
|
|
|
@@ -3688,18 +3658,7 @@ var TrieRouter = class extends DebuggableService {
|
|
|
RouterOptions,
|
|
|
TrieRouter,
|
|
|
UNPARSABLE_MEDIA_TYPES,
|
|
|
- createCookieString,
|
|
|
- createDebugger,
|
|
|
- createError,
|
|
|
- fetchRequestBody,
|
|
|
- getRequestPathname,
|
|
|
- isPromise,
|
|
|
- isReadableStream,
|
|
|
- isResponseSent,
|
|
|
- isWritableStream,
|
|
|
- parseCookie,
|
|
|
- parseJsonBody,
|
|
|
- toCamelCase
|
|
|
+ parseJsonBody
|
|
|
});
|
|
|
/*! Bundled license information:
|
|
|
|