@wxn0brp/falcon-frame 0.2.0 → 0.3.0
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/body-utils.d.ts +6 -0
- package/dist/body-utils.js +66 -0
- package/dist/body.d.ts +3 -2
- package/dist/body.js +5 -49
- package/dist/helpers.js +4 -14
- package/dist/index.d.ts +6 -3
- package/dist/index.js +12 -2
- package/dist/req.js +18 -9
- package/dist/router.d.ts +4 -1
- package/dist/router.js +10 -1
- package/dist/types.d.ts +5 -4
- package/package.json +1 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import FalconFrame from "./index.js";
|
|
2
|
+
import type { FFRequest, ParseBodyFunction, RouteHandler, StandardBodyParserOptions } from "./types.js";
|
|
3
|
+
export declare function parseLimit(limit: string | number): number;
|
|
4
|
+
export declare function getContentType(req: FFRequest): string | undefined;
|
|
5
|
+
export declare function getRawBody(req: FFRequest, limit: number): Promise<string>;
|
|
6
|
+
export declare function getStandardBodyParser(type: string, parser: ParseBodyFunction, FF: FalconFrame, opts: StandardBodyParserOptions): RouteHandler;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export function parseLimit(limit) {
|
|
2
|
+
if (!limit)
|
|
3
|
+
return 0;
|
|
4
|
+
if (typeof limit === "number")
|
|
5
|
+
return limit;
|
|
6
|
+
if (typeof limit !== "string")
|
|
7
|
+
return 0;
|
|
8
|
+
limit = limit.toLowerCase().replace("b", "");
|
|
9
|
+
const match = limit.match(/^(\d+)([kmg])?$/i);
|
|
10
|
+
if (!match)
|
|
11
|
+
return 0;
|
|
12
|
+
const num = parseInt(match[1], 10);
|
|
13
|
+
const unit = match[2]?.toLowerCase();
|
|
14
|
+
switch (unit) {
|
|
15
|
+
case "k":
|
|
16
|
+
return num * 1024;
|
|
17
|
+
case "m":
|
|
18
|
+
return num * 1024 * 1024;
|
|
19
|
+
case "g":
|
|
20
|
+
return num * 1024 * 1024 * 1024;
|
|
21
|
+
default:
|
|
22
|
+
return num;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function getContentType(req) {
|
|
26
|
+
return req.headers["content-type"]?.split(";")[0].toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
export function getRawBody(req, limit) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
let body = "";
|
|
31
|
+
req.on("data", (chunk) => {
|
|
32
|
+
body += chunk.toString();
|
|
33
|
+
if (limit && body.length > limit) {
|
|
34
|
+
const error = new Error("Payload Too Large");
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
error.statusCode = 413;
|
|
37
|
+
req.destroy();
|
|
38
|
+
return reject(error);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
req.on("end", () => {
|
|
42
|
+
resolve(body);
|
|
43
|
+
});
|
|
44
|
+
req.on("error", (err) => {
|
|
45
|
+
reject(err);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function getStandardBodyParser(type, parser, FF, opts) {
|
|
50
|
+
const limit = parseLimit(opts.limit || "100k");
|
|
51
|
+
return async (req, res, next) => {
|
|
52
|
+
if ((typeof req.body == "object" && Object.keys(req.body).length) || getContentType(req) !== type) {
|
|
53
|
+
return next();
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const body = await getRawBody(req, limit);
|
|
57
|
+
req.body = await parser(body, req, FF);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
req.body = {};
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
next();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
package/dist/body.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import FalconFrame from "./index.js";
|
|
2
|
-
import {
|
|
3
|
-
export declare function
|
|
2
|
+
import { RouteHandler, StandardBodyParserOptions } from "./types.js";
|
|
3
|
+
export declare function json(FF: FalconFrame, opts?: StandardBodyParserOptions): RouteHandler;
|
|
4
|
+
export declare function urlencoded(FF: FalconFrame, opts?: StandardBodyParserOptions): RouteHandler;
|
package/dist/body.js
CHANGED
|
@@ -1,52 +1,8 @@
|
|
|
1
1
|
import querystring from "querystring";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
"application/
|
|
5
|
-
body: querystring.parse(body),
|
|
6
|
-
}),
|
|
7
|
-
};
|
|
8
|
-
export async function parseBody(req, body, FF) {
|
|
9
|
-
const funcs = Object.assign({}, parseBodyFunctions, FF.customParsers || {});
|
|
10
|
-
const limit = parseLimit(FF.opts.bodyLimit);
|
|
11
|
-
try {
|
|
12
|
-
if (limit && body.length > limit) {
|
|
13
|
-
await FF.logger.warn(`Body size exceeds limit of ${limit} bytes`);
|
|
14
|
-
return {};
|
|
15
|
-
}
|
|
16
|
-
const type = req.headers["content-type"] || "";
|
|
17
|
-
const func = funcs[type];
|
|
18
|
-
if (!func)
|
|
19
|
-
return {};
|
|
20
|
-
const data = await func(body, req, FF);
|
|
21
|
-
if (!data || typeof data !== "object")
|
|
22
|
-
return {};
|
|
23
|
-
return data;
|
|
24
|
-
}
|
|
25
|
-
catch (e) {
|
|
26
|
-
await FF.logger.warn(`Error parsing body: ${e}`);
|
|
27
|
-
return {};
|
|
28
|
-
}
|
|
2
|
+
import { getStandardBodyParser } from "./body-utils.js";
|
|
3
|
+
export function json(FF, opts = {}) {
|
|
4
|
+
return getStandardBodyParser("application/json", (body) => JSON.parse(body), FF, opts);
|
|
29
5
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
return 0;
|
|
33
|
-
if (typeof limit === "number")
|
|
34
|
-
return limit;
|
|
35
|
-
if (typeof limit !== "string")
|
|
36
|
-
return 0;
|
|
37
|
-
const match = limit.match(/^(\d+)([kmg])?$/i);
|
|
38
|
-
if (!match)
|
|
39
|
-
return 0;
|
|
40
|
-
const num = parseInt(match[1], 10);
|
|
41
|
-
const unit = match[2]?.toLowerCase();
|
|
42
|
-
switch (unit) {
|
|
43
|
-
case "k":
|
|
44
|
-
return num * 1024;
|
|
45
|
-
case "m":
|
|
46
|
-
return num * 1024 * 1024;
|
|
47
|
-
case "g":
|
|
48
|
-
return num * 1024 * 1024 * 1024;
|
|
49
|
-
default:
|
|
50
|
-
return num;
|
|
51
|
-
}
|
|
6
|
+
export function urlencoded(FF, opts = {}) {
|
|
7
|
+
return getStandardBodyParser("application/x-www-form-urlencoded", (body) => querystring.parse(body), FF, opts);
|
|
52
8
|
}
|
package/dist/helpers.js
CHANGED
|
@@ -38,9 +38,10 @@ export function handleStaticFiles(dirPath, opts) {
|
|
|
38
38
|
utf8: true,
|
|
39
39
|
render: true,
|
|
40
40
|
etag: true,
|
|
41
|
+
errorIfDirNotFound: true,
|
|
41
42
|
...opts,
|
|
42
43
|
};
|
|
43
|
-
if (!fs.existsSync(dirPath)) {
|
|
44
|
+
if (opts.errorIfDirNotFound && !fs.existsSync(dirPath)) {
|
|
44
45
|
throw new Error(`Directory ${dirPath} does not exist`);
|
|
45
46
|
}
|
|
46
47
|
const serveFile = (req, res, filePath, stats) => {
|
|
@@ -94,19 +95,8 @@ export function handleStaticFiles(dirPath, opts) {
|
|
|
94
95
|
try {
|
|
95
96
|
const htmlPath = filePath + ".html";
|
|
96
97
|
const htmlStats = fs.statSync(htmlPath);
|
|
97
|
-
if (htmlStats.isFile())
|
|
98
|
-
|
|
99
|
-
const etag = `W/"${htmlStats.size}-${htmlStats.mtime.getTime()}"`;
|
|
100
|
-
if (req.headers["if-none-match"] === etag) {
|
|
101
|
-
res.status(304).end();
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
res.setHeader("ETag", etag);
|
|
105
|
-
}
|
|
106
|
-
res.ct(getContentType(htmlPath, opts.utf8));
|
|
107
|
-
fs.createReadStream(htmlPath).pipe(res);
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
98
|
+
if (htmlStats.isFile())
|
|
99
|
+
return serveFile(req, res, htmlPath, htmlStats);
|
|
110
100
|
}
|
|
111
101
|
catch (e) {
|
|
112
102
|
/* .html file not found, fall through to next() */
|
package/dist/index.d.ts
CHANGED
|
@@ -4,20 +4,23 @@ import { PluginSystem } from "./plugin.js";
|
|
|
4
4
|
import { renderHTML } from "./render.js";
|
|
5
5
|
import { FFResponse } from "./res.js";
|
|
6
6
|
import { Router } from "./router.js";
|
|
7
|
-
import type { BeforeHandleRequest, FFRequest,
|
|
7
|
+
import type { BeforeHandleRequest, FFRequest, RouteHandler } from "./types.js";
|
|
8
8
|
export type EngineCallback = (path: string, options: any, callback: (e: any, rendered?: string) => void) => void;
|
|
9
9
|
export interface Opts {
|
|
10
10
|
loggerOpts?: LoggerOptions;
|
|
11
11
|
bodyLimit?: string;
|
|
12
|
+
disableJsonParser?: boolean;
|
|
13
|
+
disableUrlencodedParser?: boolean;
|
|
12
14
|
}
|
|
13
15
|
export declare class FalconFrame<Vars extends Record<string, any> = any> extends Router {
|
|
14
16
|
logger: Logger;
|
|
15
|
-
|
|
17
|
+
bodyParsers: RouteHandler[];
|
|
16
18
|
vars: Vars;
|
|
17
19
|
opts: Opts;
|
|
18
20
|
engines: Record<string, EngineCallback>;
|
|
19
21
|
constructor(opts?: Partial<Opts>);
|
|
20
|
-
|
|
22
|
+
addBodyParser(parser: RouteHandler): this;
|
|
23
|
+
listen(port: number | string, callback?: (() => void) | boolean, beforeHandleRequest?: BeforeHandleRequest): http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
21
24
|
getApp(beforeHandleRequest?: BeforeHandleRequest): (req: any, res: any) => Promise<void>;
|
|
22
25
|
engine(ext: string, callback: EngineCallback): this;
|
|
23
26
|
setVar(key: keyof Vars, value: typeof this.vars[keyof Vars]): void;
|
package/dist/index.js
CHANGED
|
@@ -6,22 +6,28 @@ import { renderHTML } from "./render.js";
|
|
|
6
6
|
import { handleRequest } from "./req.js";
|
|
7
7
|
import { FFResponse } from "./res.js";
|
|
8
8
|
import { Router } from "./router.js";
|
|
9
|
+
import { json, urlencoded } from "./body.js";
|
|
9
10
|
export class FalconFrame extends Router {
|
|
10
11
|
logger;
|
|
11
|
-
|
|
12
|
+
bodyParsers = [];
|
|
12
13
|
vars = {};
|
|
13
14
|
opts = {};
|
|
14
15
|
engines = {};
|
|
15
16
|
constructor(opts = {}) {
|
|
16
17
|
super();
|
|
18
|
+
const loggerOpts = opts?.loggerOpts || {};
|
|
17
19
|
this.logger = new Logger({
|
|
18
20
|
loggerName: "falcon-frame",
|
|
19
|
-
...
|
|
21
|
+
...loggerOpts,
|
|
20
22
|
});
|
|
21
23
|
this.opts = {
|
|
22
24
|
bodyLimit: "10m",
|
|
23
25
|
...opts,
|
|
24
26
|
};
|
|
27
|
+
if (!this.opts.disableJsonParser)
|
|
28
|
+
this.addBodyParser(json(this, { limit: this.opts.bodyLimit }));
|
|
29
|
+
if (!this.opts.disableUrlencodedParser)
|
|
30
|
+
this.addBodyParser(urlencoded(this, { limit: this.opts.bodyLimit }));
|
|
25
31
|
this.engine(".html", (path, options, callback) => {
|
|
26
32
|
try {
|
|
27
33
|
const content = renderHTML(path, options);
|
|
@@ -32,6 +38,10 @@ export class FalconFrame extends Router {
|
|
|
32
38
|
}
|
|
33
39
|
});
|
|
34
40
|
}
|
|
41
|
+
addBodyParser(parser) {
|
|
42
|
+
this.bodyParsers.push(parser);
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
35
45
|
listen(port, callback, beforeHandleRequest) {
|
|
36
46
|
const server = http.createServer(this.getApp(beforeHandleRequest));
|
|
37
47
|
if (typeof callback === "boolean") {
|
package/dist/req.js
CHANGED
|
@@ -3,7 +3,6 @@ import { parseCookies } from "./helpers.js";
|
|
|
3
3
|
import { FFResponse } from "./res.js";
|
|
4
4
|
import { validate } from "./valid.js";
|
|
5
5
|
import { getMiddlewares, matchMiddleware } from "./middleware.js";
|
|
6
|
-
import { parseBody } from "./body.js";
|
|
7
6
|
export function handleRequest(req, res, FF) {
|
|
8
7
|
Object.setPrototypeOf(res, FFResponse.prototype);
|
|
9
8
|
res.FF = FF;
|
|
@@ -71,6 +70,12 @@ export function handleRequest(req, res, FF) {
|
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
72
|
}
|
|
73
|
+
const hasCustomParserEndpoint = matchedMiddlewares.some(middleware => middleware.customParser);
|
|
74
|
+
if (hasCustomParserEndpoint) {
|
|
75
|
+
req.body = {};
|
|
76
|
+
next();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
74
79
|
if (req.method === "GET" ||
|
|
75
80
|
req.method === "HEAD" ||
|
|
76
81
|
req.method === "OPTIONS") {
|
|
@@ -78,12 +83,16 @@ export function handleRequest(req, res, FF) {
|
|
|
78
83
|
next();
|
|
79
84
|
return;
|
|
80
85
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
req.body = {};
|
|
87
|
+
let bodyParserIndex = 0;
|
|
88
|
+
function nextBodyParser() {
|
|
89
|
+
if (bodyParserIndex >= FF.bodyParsers.length) {
|
|
90
|
+
logger.debug("No more body parsers. Executing middlewares");
|
|
91
|
+
return next();
|
|
92
|
+
}
|
|
93
|
+
logger.debug(`Executing body parser ${bodyParserIndex} of ${FF.bodyParsers.length}`);
|
|
94
|
+
const bodyParser = FF.bodyParsers[bodyParserIndex++];
|
|
95
|
+
bodyParser(req, res, nextBodyParser);
|
|
96
|
+
}
|
|
97
|
+
nextBodyParser();
|
|
89
98
|
}
|
package/dist/router.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { PluginSystem } from "./plugin.js";
|
|
1
2
|
import { Method, Middleware, RouteHandler, StaticServeOptions } from "./types.js";
|
|
3
|
+
export type MiddlewareFn = RouteHandler | Router | PluginSystem;
|
|
2
4
|
export declare class Router {
|
|
3
5
|
middlewares: Middleware[];
|
|
4
6
|
addRoute(method: Method, path: string, ...handlers: RouteHandler[]): number;
|
|
5
|
-
use(path?: string |
|
|
7
|
+
use(path?: string | MiddlewareFn, middlewareFn?: MiddlewareFn, method?: Method): this;
|
|
6
8
|
get(path: string, ...handlers: RouteHandler[]): this;
|
|
7
9
|
post(path: string, ...handlers: RouteHandler[]): this;
|
|
8
10
|
put(path: string, ...handlers: RouteHandler[]): this;
|
|
@@ -10,4 +12,5 @@ export declare class Router {
|
|
|
10
12
|
all(path: string, ...handlers: RouteHandler[]): this;
|
|
11
13
|
static(apiPath: string, dirPath?: string, opts?: StaticServeOptions): this;
|
|
12
14
|
sse(path: string, ...handlers: RouteHandler[]): this;
|
|
15
|
+
customParser(path: string, handler: RouteHandler, method?: Method): this;
|
|
13
16
|
}
|
package/dist/router.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { handleStaticFiles } from "./helpers.js";
|
|
2
|
+
import { PluginSystem } from "./plugin.js";
|
|
2
3
|
export class Router {
|
|
3
4
|
middlewares = [];
|
|
4
5
|
addRoute(method, path, ...handlers) {
|
|
@@ -7,7 +8,7 @@ export class Router {
|
|
|
7
8
|
return this.middlewares.push({ path, middleware: handler, method });
|
|
8
9
|
}
|
|
9
10
|
use(path = "/", middlewareFn, method = "all") {
|
|
10
|
-
if (typeof path === "function" || path instanceof Router) {
|
|
11
|
+
if (typeof path === "function" || path instanceof Router || path instanceof PluginSystem) {
|
|
11
12
|
middlewareFn = path;
|
|
12
13
|
path = "/";
|
|
13
14
|
}
|
|
@@ -20,6 +21,9 @@ export class Router {
|
|
|
20
21
|
if (middlewareFn instanceof Router) {
|
|
21
22
|
middleware.router = middlewareFn.middlewares;
|
|
22
23
|
}
|
|
24
|
+
else if (middlewareFn instanceof PluginSystem) {
|
|
25
|
+
middleware.middleware = middlewareFn.getRouteHandler();
|
|
26
|
+
}
|
|
23
27
|
else {
|
|
24
28
|
middleware.middleware = middlewareFn;
|
|
25
29
|
}
|
|
@@ -59,4 +63,9 @@ export class Router {
|
|
|
59
63
|
this.middlewares[index - 1].sse = true;
|
|
60
64
|
return this;
|
|
61
65
|
}
|
|
66
|
+
customParser(path, handler, method = "post") {
|
|
67
|
+
const index = this.addRoute(method, path, handler);
|
|
68
|
+
this.middlewares[index - 1].customParser = true;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
62
71
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -15,11 +15,10 @@ export interface Query {
|
|
|
15
15
|
export interface Body {
|
|
16
16
|
[key: string]: any;
|
|
17
17
|
}
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
export type ParseBodyFunction = (body: string, req: FFRequest, FF: FalconFrame) => Promise<Record<string, any>>;
|
|
19
|
+
export interface StandardBodyParserOptions {
|
|
20
|
+
limit?: string | number;
|
|
21
21
|
}
|
|
22
|
-
export type ParseBodyFunction = (body: string, req: FFRequest, FF: FalconFrame) => Promise<ParseBody>;
|
|
23
22
|
export declare class FFRequest extends http.IncomingMessage {
|
|
24
23
|
path: string;
|
|
25
24
|
query: Query;
|
|
@@ -36,6 +35,7 @@ export interface Middleware {
|
|
|
36
35
|
use?: true;
|
|
37
36
|
router?: Middleware[];
|
|
38
37
|
sse?: true;
|
|
38
|
+
customParser?: true;
|
|
39
39
|
}
|
|
40
40
|
export interface CookieOptions {
|
|
41
41
|
maxAge?: number;
|
|
@@ -58,4 +58,5 @@ export interface StaticServeOptions {
|
|
|
58
58
|
utf8?: boolean;
|
|
59
59
|
render?: boolean;
|
|
60
60
|
etag?: boolean;
|
|
61
|
+
errorIfDirNotFound?: boolean;
|
|
61
62
|
}
|