Skip to content
This repository has been archived by the owner on Sep 15, 2024. It is now read-only.

Commit

Permalink
Update code
Browse files Browse the repository at this point in the history
Signed-off-by: GitHub <noreply@github.com>
  • Loading branch information
Sirherobrine23 authored Oct 26, 2023
1 parent 99f7d2e commit 2ba7b9f
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 42 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"busboy": "^1.6.0",
"cookie": "^0.5.0",
"path-to-regexp": "^6.2.1",
"ws": "^8.14.2"
"ws": "^8.14.2",
"yaml": "^2.3.3"
}
}
64 changes: 42 additions & 22 deletions src/application.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { IncomingMessage, Server, ServerResponse } from "http";
import { AddressInfo, ListenOptions } from "net";
import path from "path";
import { parse } from "url";
import util from "util";
import { WebSocket, WebSocketServer } from "ws";
import { ErrorRequestHandler, Handlers, Layer, NextFunction, RequestHandler, WsRequestHandler, assignRequest, assignResponse, assignWsResponse } from "./handler.js";
import { Methods, methods } from "./util.js";

export type RouterSettingsConfig = Record<string, any>;
export class RouterSettings extends Map<string, any> {
constructor(sets?: RouterSettingsConfig) {
super();
if (sets) for (const k in sets) this.set(k, sets[k]);
}
toJSON(): RouterSettingsConfig {
return Array.from(this.entries()).reduce((acc, [key, value]) => Object.assign(acc, {[key]: value}), {});
}
}

export interface Router {
(req: IncomingMessage, res: ServerResponse, next?: (err?: any) => void): void;
(req: IncomingMessage, socket: WebSocket, next?: (err?: any) => void): void;
Expand All @@ -21,16 +33,25 @@ export interface Router {
}

export class Router extends Function {
constructor(opts?: any) {
constructor(routeOpts?: RouterSettingsConfig) {
super("if (typeof this.handler === 'function') { this.handler.apply(this, arguments); } else if (typeof arguments.callee === 'function') { arguments.callee.apply(arguments.callee, arguments); } else { throw new Error('Cannot get Router class'); }");
this.settings = new RouterSettings(routeOpts);
this.settings.set("env", process.env.NODE_ENV || "development");
this.settings.set("path resolve", true).set("json space", 2);
this.settings.set("json replacer", (_key: string, value: any) => {
if (value instanceof BigInt || typeof value === "bigint") return { type: "bigint", value: value.toString() };
else if (value && typeof value.toJSON === "function") return value.toJSON();
return value;
});
}

layers: Layer[] = [];
wsRooms: Map<string, WsRequestHandler[]> = new Map();
settings: RouterSettings;

handler(req: IncomingMessage, res: WebSocket|ServerResponse, next?: NextFunction) {
if (typeof next !== "function") next = (err) => {
if (err && !(err === "router" || err === "route")) console.error(err);
if (typeof next !== "function") next = (err?: any) => {
if (err && !(err === "router" || err === "route") && this.settings.get("env") !== "production") console.error(err);
if (res instanceof WebSocket) {
res.send("Close connection!");
res.close();
Expand All @@ -45,12 +66,13 @@ export class Router extends Function {
}
}

if (!req["path"]) req["path"] = (parse(req.url)).pathname;
const { layers } = this, method = (res instanceof WebSocket ? "ws" : (String(req.method||"").toLowerCase())), saveParms = Object.freeze(req["params"] || {}), originalPath = req["path"];;
const { layers } = this, method = (res instanceof WebSocket ? "ws" : (String(req.method||"").toLowerCase())), saveParms = Object.freeze(req["params"] || {});
let originalPath: string = req["path"]||(parse(req.url)).pathname;
if (this.settings.get("path resolve")) originalPath = path.posix.resolve("/", originalPath);
if (this.settings.has("app path") && typeof this.settings.get("app path") === "string" && originalPath.startsWith(this.settings.get("app path"))) originalPath = path.posix.resolve("/", originalPath.slice(path.posix.resolve("/", this.settings.get("app path")).length));
let layersIndex = 0;

nextHandler().catch(next);
async function nextHandler(err?: any) {
const nextHandler = async (err?: any) => {
req["path"] = originalPath;
req["params"] = Object.assign({}, saveParms);
if (err && err === "route") return next();
Expand All @@ -60,37 +82,39 @@ export class Router extends Function {
else if (layer.method && layer.method !== method) return nextHandler(err);
const layerMatch = layer.match(req["path"]);
if (!layerMatch) return nextHandler(err);
req["path"] = layerMatch.path;
if (err && layer.handler.length !== 4) return nextHandler(err);
try {
if (err) {
if (res instanceof WebSocket) return nextHandler(err);
const fn = layer.handler as ErrorRequestHandler;
await fn(err, assignRequest(req, method, Object.assign({}, saveParms, layerMatch.params)), assignResponse(res), nextHandler);
await fn(err, assignRequest(this, req, method, Object.assign({}, saveParms, layerMatch.params)), assignResponse(this, res), nextHandler);
} else {
if (res instanceof WebSocket) {
const fn = layer.handler as WsRequestHandler;
await fn(assignRequest(req, method, Object.assign({}, saveParms, layerMatch.params)), assignWsResponse(res, this), nextHandler);
await fn(assignRequest(this, req, method, Object.assign({}, saveParms, layerMatch.params)), assignWsResponse(res), nextHandler);
} else {
const fn = layer.handler as RequestHandler;
await fn(assignRequest(req, method, Object.assign({}, saveParms, layerMatch.params)), assignResponse(res), nextHandler);
await fn(assignRequest(this, req, method, Object.assign({}, saveParms, layerMatch.params)), assignResponse(this, res), nextHandler);
}
}
} catch (err) {
nextHandler(err);
}
}
nextHandler().catch(next);
}


use(...fn: Handlers[]): this;
use(path: string|RegExp, ...fn: Handlers[]): this;
use(...fn: RequestHandler[]): this;
use(path: string|RegExp, ...fn: RequestHandler[]): this;
use() {
let p: [string|RegExp, Handlers[]];
if (!(arguments[0] instanceof RegExp || typeof arguments[0] === "string" && arguments[0].trim())) p = ["/", Array.from(arguments)];
if (!(arguments[0] instanceof RegExp || typeof arguments[0] === "string" && arguments[0].trim())) p = ["(.*)", Array.from(arguments)];
else p = [arguments[0], Array.from(arguments).slice(1)];
for (const fn of p[1]) {
if (typeof fn !== "function") throw new Error(util.format("Invalid middleare, require function, recived %s", typeof fn));
this.layers.push(new Layer(p[0], fn, { strict: false, end: false }));
this.layers.push(new Layer(p[0], fn, { isRoute: true, strict: false, end: false }));
}
return this;
}
Expand All @@ -109,7 +133,7 @@ export class Router extends Function {
methods.forEach(method => Router.prototype[method] = function(this: Router) { return this.__method.apply(this, ([method] as any[]).concat(Array.from(arguments))) } as any)

export class Neste extends Router {
httpServer: Server;
httpServer?: Server;
listen(port?: number, hostname?: string, backlog?: number, listeningListener?: () => void): this;
listen(port?: number, hostname?: string, listeningListener?: () => void): this;
listen(port?: number, backlog?: number, listeningListener?: () => void): this;
Expand Down Expand Up @@ -144,7 +168,7 @@ export class Neste extends Router {
const wsServer = new WebSocketServer({ noServer: true });
this.httpServer.on("upgrade", (req, sock, head) => wsServer.handleUpgrade(req, sock, head, (client) => this.handler(req, client)));
return this.httpServer;
})()).address()
})()).address();
}

closeAllConnections(): void {
Expand All @@ -153,7 +177,7 @@ export class Neste extends Router {
const wsServer = new WebSocketServer({ noServer: true });
this.httpServer.on("upgrade", (req, sock, head) => wsServer.handleUpgrade(req, sock, head, (client) => this.handler(req, client)));
return this.httpServer;
})()).closeAllConnections()
})()).closeAllConnections();
}

closeIdleConnections(): void {
Expand Down Expand Up @@ -187,8 +211,4 @@ export class Neste extends Router {
})()).setTimeout.apply(this.httpServer, arguments);
return this;
}
}

const app = new Neste();
app.listen(3000, () => console.log("Http 3000"))
app.get("/", ({headers, Cookies, hostname}, res) => res.json({ req: { headers, Cookies: Cookies.toJSON(), hostname } }))
}
82 changes: 65 additions & 17 deletions src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { parse } from "url";
import { isIP } from "net";
import { defineProperties, mixin } from "./util.js";
import stream from "stream";
import yaml from "yaml";
import * as ranger from "./ranger.js";
import { Router } from "./application.js";
import { posix } from "path";

export class CookieManeger extends Map<string, [string, cookie.CookieSerializeOptions]> {
constructor(public initialCookies: string) {
Expand Down Expand Up @@ -39,6 +41,7 @@ export class CookieManeger extends Map<string, [string, cookie.CookieSerializeOp
}

export interface Request extends IncomingMessage {
app: Router;
protocol: "https"|"http";
secure: boolean;
path: string;
Expand Down Expand Up @@ -198,6 +201,7 @@ export const codes = {

export interface Response extends Omit<ServerResponse, "req"> {
req: Request;
app: Router;
}
export class Response {
set(key: string, value: string|string[]|number) {
Expand Down Expand Up @@ -248,8 +252,46 @@ export class Response {
* res.json({ user: 'tj' });
*/
json(obj: any) {
if (!(this.has("Content-Type"))) this.set("Content-Type", "application/json")
return this.send(JSON.stringify(obj, null, 2));
if (!(obj && typeof obj === "object")) throw new TypeError("Require body object");
else if (!(this.has("Content-Type"))) this.set("Content-Type", "application/json")

// settings
const escape = this.app.settings.get("json escape")
const replacer = this.app.settings.get("json replacer");
const spaces = this.app.settings.get("json space");

// JSON Body
let jsonBody = JSON.stringify(obj, replacer, spaces);
if (escape && typeof jsonBody === "string") {
jsonBody = jsonBody.replace(/[<>&]/g, function (c) {
switch (c.charCodeAt(0)) {
case 0x3c:
return "\\u003c"
case 0x3e:
return "\\u003e"
case 0x26:
return "\\u0026"
/* istanbul ignore next: unreachable default */
default:
return c
}
});
}
return this.send(jsonBody);
}

/**
* Send YAML response.
*
* Examples:
*
* res.yaml(null);
* res.yaml({ user: 'tj' });
*/
yaml(obj: any) {
if (!(obj && typeof obj === "object")) throw new TypeError("Require body object");
else if (!(this.has("Content-Type"))) this.set("Content-Type", "application/x-yaml; text/yaml")
return this.send(yaml.stringify(obj, null, 2));
}

/**
Expand Down Expand Up @@ -280,8 +322,9 @@ export class Response {
* res.send({ some: 'json' });
* res.send('<p>some html</p>');
*/
send(body: any) {
if (body === undefined || body === null) throw new TypeError("Require body");
send(body: any): this {
if (body === undefined) throw new TypeError("Require body");
else if (body === null) body = "";
let encoding: BufferEncoding = "utf8";
const bodyType = typeof body;
if (bodyType === "string") {
Expand Down Expand Up @@ -370,34 +413,38 @@ export type Handlers = WsRequestHandler|RequestHandler|ErrorRequestHandler;

export class Layer {
method?: string;
isRoute?: boolean;
handler: Handlers;
matchFunc: MatchFunction;
match(path: string): undefined|{ path: string, params: Record<string, string> } {
const value = this.matchFunc(path);
if (!value) return undefined;
return {
path: value.path,
path: this.isRoute ? (value.path !== path ? posix.join("/", path.slice(value.path.length)) : value.path) : value.path,
params: value.params as any,
};
}

constructor(path: string|RegExp, fn: Handlers, options?: Omit<ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions, "decode">) {
constructor(path: string|RegExp, fn: Handlers, options?: Omit<ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions & { isRoute?: boolean }, "decode"|"encode">) {
if (!(typeof fn === "function")) throw new Error("Register function");
if (!(options)) options = {};
if (path === "*") path = "(.*)";
this.isRoute = !!options.isRoute;
this.handler = fn;
this.matchFunc = regexMatch(path, {...options, decode: decodeURIComponent });
this.matchFunc = regexMatch(path, {...options, decode: decodeURIComponent, encode: encodeURI });
}
}

export function assignRequest(req: IncomingMessage, method: string, params: Record<string, string>): Request {
export function assignRequest(app: Router, req: IncomingMessage, method: string, params: Record<string, string>): Request {
if (req["__Neste"]) return req as any;
const parseQuery = new URLSearchParams(parse(req.url).query);
mixin(req, Request.prototype, false);
defineProperties(req, {
method: { configurable: false, enumerable: false, writable: false, value: method },
Cookies: { configurable: false, enumerable: false, writable: false, value: new CookieManeger((req.headers||{}).cookie||"") },
query: { configurable: true, enumerable: true, writable: true, value: Object.assign(Array.from(parseQuery.keys()).reduce<Record<string, string>>((acc, key) => { acc[key] = parseQuery.get(key); return acc; }, {}), req["query"]) },
params: { configurable: false, enumerable: false, writable: false, value: params },
app: { writable: true, configurable: true, enumerable: true, value: app },
method: { writable: true, configurable: true, enumerable: true, value: method },
Cookies: { writable: true, configurable: true, enumerable: true, value: new CookieManeger((req.headers||{}).cookie||"") },
query: { writable: true, configurable: true, enumerable: true, value: Object.assign(Array.from(parseQuery.keys()).reduce<Record<string, string>>((acc, key) => { acc[key] = parseQuery.get(key); return acc; }, {}), req["query"]) },
params: { writable: true, configurable: true, enumerable: true, value: params },
protocol: {
configurable: true,
enumerable: true,
Expand Down Expand Up @@ -464,15 +511,16 @@ export function assignRequest(req: IncomingMessage, method: string, params: Reco
return req as any;
}

export function assignResponse(res: ServerResponse): Response {
export function assignResponse(app: Router, res: ServerResponse): Response {
if (res["__Neste"]) return res as any;
mixin(res, Response.prototype, false);
defineProperties(res, {});
defineProperties(res, {
app: { writable: true, configurable: true, enumerable: true, value: app }
});
return res as any;
}

export function assignWsResponse(res: WebSocket, router: Router): WsResponse {
export function assignWsResponse(res: WebSocket): WsResponse {
mixin(res, WsResponse.prototype, false);
router.wsRooms
defineProperties(res, {});
return res as any;
}
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { CookieManeger, Handlers, ErrorRequestHandler, RequestHandler, WsRequestHandler, Layer, NextFunction, Request, Response, WsResponse } from "./handler.js";
import { Neste, Router, RouterSettings, RouterSettingsConfig } from "./application.js";

function router() { return new Router(); }
function neste() { return new Neste(); }
export { neste, router, CookieManeger, Handlers, ErrorRequestHandler, RequestHandler, WsRequestHandler, Layer, NextFunction, Request, Response, WsResponse, Neste, Router, RouterSettings, RouterSettingsConfig };
export default neste;
1 change: 1 addition & 0 deletions src/middles/bodyParse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type FileStorage = {
restore(fn: (err: any, stream?: stream.Readable) => void): void;
delete(fn: (err?: any) => void): void;
}

export class LocalFile extends Map<string, FileStorage> {
rootDir?: string;
async deleteFile(file: string) {
Expand Down
5 changes: 4 additions & 1 deletion src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export function mixin<T, C>(dest: T, src: C, redefine?: boolean): T & C {
}

export function defineProperties(obj: any, config: Record<string, PropertyDescriptor & ThisType<any>>) {
for (let key in config) Object.defineProperty(obj, key, config[key]);
for (let key in config) {
if (config[key].value !== undefined) obj[key] = config[key].value;
else Object.defineProperty(obj, key, config[key]);
}
return obj;
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"declaration": true,
"strict": false,
"noUnusedLocals": true,
"isolatedModules": true,
"isolatedModules": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
Expand Down

0 comments on commit 2ba7b9f

Please sign in to comment.