mirror of https://github.com/186526/handlers.js
Added NodePlatformAdapter and fixed bug in platform independent functions and added Demo.
This commit is contained in:
parent
f3c0d646e3
commit
bdbb5deda9
|
@ -0,0 +1,141 @@
|
||||||
|
// From https://gist.github.com/186526/82b7372619fb4b029568040ee4a4aa44, Open source with MIT license
|
||||||
|
|
||||||
|
import { response, request } from "..";
|
||||||
|
import { defaultHeaders } from "../src/interface/response";
|
||||||
|
|
||||||
|
function uuid(): string {
|
||||||
|
const s: any[] = [];
|
||||||
|
const hexDigits = "0123456789abcdef";
|
||||||
|
|
||||||
|
for (let i = 0; i < 36; i++) {
|
||||||
|
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
|
||||||
|
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
|
||||||
|
s[8] = s[13] = s[18] = s[23] = "-";
|
||||||
|
|
||||||
|
const uuid = s.join("");
|
||||||
|
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
const codeAlternative = {
|
||||||
|
100: "Continue",
|
||||||
|
101: "Switching Protocols",
|
||||||
|
102: "Processing",
|
||||||
|
103: "Early Hints",
|
||||||
|
200: "OK",
|
||||||
|
201: "Created",
|
||||||
|
202: "Accepted",
|
||||||
|
203: "Non-Authoritative Information",
|
||||||
|
204: "No Content",
|
||||||
|
205: "Reset Content",
|
||||||
|
206: "Partial Content",
|
||||||
|
207: "Multi Status",
|
||||||
|
208: "Already Reported",
|
||||||
|
226: "IM Used",
|
||||||
|
300: "Multiple Choices",
|
||||||
|
301: "Moved Permanently",
|
||||||
|
302: "Found",
|
||||||
|
303: "See Other",
|
||||||
|
304: "Not Modified",
|
||||||
|
305: "Use Proxy",
|
||||||
|
306: "Switch Proxy",
|
||||||
|
307: "Temporary Redirect",
|
||||||
|
308: "Permanent Redirect",
|
||||||
|
400: "Bad Request",
|
||||||
|
401: "Unauthorized",
|
||||||
|
402: "Payment Required",
|
||||||
|
403: "Forbidden",
|
||||||
|
404: "Not Found",
|
||||||
|
405: "Method Not Allowed",
|
||||||
|
406: "Not Acceptable",
|
||||||
|
407: "Proxy Authentication Required",
|
||||||
|
408: "Request Time-out",
|
||||||
|
409: "Conflict",
|
||||||
|
410: "Gone",
|
||||||
|
411: "Length Required",
|
||||||
|
412: "Precondition Failed",
|
||||||
|
413: "Request Entity Too Large",
|
||||||
|
414: "Request-URI Too Large",
|
||||||
|
415: "Unsupported Media Type",
|
||||||
|
416: "Requested Range not Satisfiable",
|
||||||
|
417: "Expectation Failed",
|
||||||
|
418: "I'm a teapot",
|
||||||
|
421: "Misdirected Request",
|
||||||
|
422: "Unprocessable Entity",
|
||||||
|
423: "Locked",
|
||||||
|
424: "Failed Dependency",
|
||||||
|
426: "Upgrade Required",
|
||||||
|
428: "Precondition Required", // RFC 6585
|
||||||
|
429: "Too Many Requests",
|
||||||
|
431: "Request Header Fields Too Large", // RFC 6585
|
||||||
|
451: "Unavailable For Legal Reasons",
|
||||||
|
500: "Internal Server Error",
|
||||||
|
501: "Not Implemented",
|
||||||
|
502: "Bad Gateway",
|
||||||
|
503: "Service Unavailable",
|
||||||
|
504: "Gateway Time-out",
|
||||||
|
505: "HTTP Version not Supported",
|
||||||
|
506: "Variant Also Negotiates",
|
||||||
|
507: "Insufficient Storage",
|
||||||
|
508: "Loop Detected",
|
||||||
|
510: "Not Extended",
|
||||||
|
511: "Network Authentication Required",
|
||||||
|
};
|
||||||
|
|
||||||
|
const template: string =
|
||||||
|
'<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /><meta name="renderer" content="webkit" /><meta name="force-rendering" content="webkit" /><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /><link rel="preconnect" href="https://fonts.gstatic.com"><link href="https://fonts.googleapis.com/css2?family=Google+Sans&family=Fira+Mono&family=Ubuntu&display=swap" rel="stylesheet"><title>${statusCode} | ${codeAlternative}</title><link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"><style type="text/css">:root{ --background-color: #fff; --font-color: #000; --font-color-lighter: rgb(87, 89, 88); --font-size-main: 3.545rem; --font-size-description: 1.245rem; --box-color: #F2F2F2; --working-color: #137333; --working-color-background: #e6f4ea; --error-color-background: #fce8e6; --error-color: #c5221f; --working-with-error-color: #b05a00; --working-with-error-color-background: #fef7e0; --icon-size: 48px;} body{ margin: 2rem 2rem; font-family: Google Sans, Ubuntu, Roboto, Noto Sans SC, sans-serif; color: var(--font-color); background-color: var(--background-color);} nav{ margin-left: 1rem;} nav description{ font-family: Ubuntu, Roboto, Noto Sans SC, sans-serif; font-size: var(--font-size-description); line-height: var(--fonr-size-description); color: var(--font-color-lighter);} nav main{ font-size: var(--font-size-main); line-height: var(--font-size-main); font-family: Fira Mono, Ubuntu, monospace;} code{ font-family: Fira Mono, monospace;} status{ margin-top: 2.5rem; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: center; align-items: center;} status block.status-block{ background-color: var(--box-color); padding: 2rem; margin: 1rem 1rem; min-height: 3rem; border-radius: 9px; flex-grow: 1;} status block.status-block.working-block{ background-color: var(--working-color-background);} status block.status-block.error-block{ background-color: var(--error-color-background);} status block.status-block.working-with-error-block{ background-color: var(--working-with-error-color-background);} .status-block main{ font-size: calc(var(--font-size-description) + 0.1rem);} .status-working{ color: var(--working-color);} .status-error{ color: var(--error-color);} .status-working-with-error{ color: var(--working-with-error-color);} icon{ font-size: var(--icon-size) !important;} a{ text-decoration: none; color: #1967d2;} reason{ display: flex; flex-direction: row; flex-wrap: wrap; justify-content: space-between; align-items: center;} reason>*{ display: block; margin: 1rem; flex-grow: 1; max-width: 40%;} reason main{ font-size: calc(var(--font-size-description) + 0.2rem); font-weight: 550;} footer{ margin: 1rem; color: var(--font-color-lighter); font-size: calc(var(--font-size-description) - 0.4rem);} footer>request-status{ font-size: calc(var(--font-size-description) - 0.6rem);} footer>*{ display: block;} @media screen and (max-width:480px){ body{ margin: 6rem 2rem;} :root{ --font-size-main: 3.0rem; --font-size-description: 1.045rem;} reason>*{ max-width: 100%;} footer{ font-size: calc(var(--font-size-description) - 0.2rem);} footer>request-status{ font-size: calc(var(--font-size-description) - 0.4rem);}} @media screen and (min-width: 768px){ body{ margin: 8% 10%;} nav *{ display: inline-block; margin-left: 1%;}} @media (prefers-color-scheme: dark){ :root{ --font-color: rgba(255, 255, 255, 0.86); --font-color-lighter: rgba(255, 255, 255, 0.4); --background-color: rgb(0, 0, 0); --box-color: rgb(40 40 40 / 73%); --working-color-background: #07220f; --error-color-background: #270501; --working-with-error-color-background: #392605;}} </style></head><body><nav><main>${statusCode}</main><description>${codeAlternative}</description></nav><status><block class="status-block ${clientStatusShort}-block" id="client-status-block"><icon class="material-icons-outlined status-${clientStatusShort}">web</icon><main>Your Client</main><status-text class="status-${clientStatusShort}">${clientStatus}</status-text></block><block class="status-block working-block" id="edge-status-block"><icon class="material-icons-outlined status-working">alt_route</icon><main>Handlers.JS</main><status-text class="status-working">Working </block><block class="status-block ${ServerStatusLowerCase}-block" id="website-status-block"><icon class="material-icons-outlined status-${ServerStatusLowerCase}">widgets</icon><main>Route</main><status-text class="status-${ServerStatusLowerCase}">${ServerStatus}</status-text></block></status><reason><explain><main>${codeAlternative}</main><p>${explain} </p></explain><howto><main>What can I do?</main><p>${howto} </p></howto></reason><footer><provider>Running with <a href="https://git.186526.xyz/186526/handlers.js">Handlers.JS</a>.</provider><br><request-status>Your IP is <code>${ip}</code><br>Request ID is <code>${id}</code></request-status></footer></body></html>';
|
||||||
|
|
||||||
|
export default (
|
||||||
|
errorCode: number,
|
||||||
|
errorMessage: string = `The route you are trying access is error.`
|
||||||
|
) =>
|
||||||
|
async (request: request<any>) => {
|
||||||
|
return new response(
|
||||||
|
template
|
||||||
|
.replaceAll("${statusCode}", errorCode.toString())
|
||||||
|
.replaceAll("${codeAlternative}", codeAlternative[errorCode])
|
||||||
|
.replaceAll("${explain}", errorMessage)
|
||||||
|
.replaceAll(
|
||||||
|
"${howto}",
|
||||||
|
`You can go to the <a href="/">homepage</a> of the website.`
|
||||||
|
)
|
||||||
|
.replaceAll("${id}", uuid())
|
||||||
|
.replaceAll(
|
||||||
|
"${ServerStatus}",
|
||||||
|
errorCode >= 500
|
||||||
|
? "Error"
|
||||||
|
: errorCode <= 500 && errorCode >= 400
|
||||||
|
? `Working but ${codeAlternative[errorCode]}`
|
||||||
|
: "Working"
|
||||||
|
)
|
||||||
|
.replaceAll(
|
||||||
|
"${ServerStatusLowerCase}",
|
||||||
|
errorCode >= 500
|
||||||
|
? "error"
|
||||||
|
: errorCode <= 500 && errorCode >= 400
|
||||||
|
? `working-with-error`
|
||||||
|
: "working"
|
||||||
|
)
|
||||||
|
.replaceAll("${ip}", request.ip)
|
||||||
|
.replaceAll(
|
||||||
|
"${clientStatus}",
|
||||||
|
errorCode <= 500 && errorCode >= 400
|
||||||
|
? `Working but receive ${codeAlternative[errorCode]}`
|
||||||
|
: "Working"
|
||||||
|
)
|
||||||
|
.replaceAll(
|
||||||
|
"${clientStatusShort}",
|
||||||
|
errorCode <= 500 && errorCode >= 400
|
||||||
|
? `working-with-error`
|
||||||
|
: "working"
|
||||||
|
),
|
||||||
|
errorCode,
|
||||||
|
(() => {
|
||||||
|
const Headers = new defaultHeaders();
|
||||||
|
Headers.set("Content-Type", "text/html; charset=utf-8");
|
||||||
|
return Headers;
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,66 @@
|
||||||
|
import * as handlerJS from "..";
|
||||||
|
import errorHandler from "./errorHandler";
|
||||||
|
|
||||||
|
interface requestType {
|
||||||
|
hood: boolean;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = new handlerJS.rootRouter<requestType, any>();
|
||||||
|
|
||||||
|
App.binding(
|
||||||
|
"/(.*)",
|
||||||
|
new handlerJS.handler("ANY", [
|
||||||
|
async (request, response) => {
|
||||||
|
console.log(request);
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
App.binding(
|
||||||
|
"/",
|
||||||
|
App.create(
|
||||||
|
"ANY",
|
||||||
|
(): Promise<string> =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
console.log("Hello World!");
|
||||||
|
resolve("Hello World!");
|
||||||
|
throw handlerJS.ChainInterrupted;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
App.route("/v1/(.*)")
|
||||||
|
.add(
|
||||||
|
new handlerJS.route(
|
||||||
|
["/echo", "/echo/(.*)"],
|
||||||
|
[
|
||||||
|
new handlerJS.handler(handlerJS.method["GET"], [
|
||||||
|
async (request, response) => {
|
||||||
|
response = response ?? new handlerJS.response("");
|
||||||
|
response?.headers.set("Hello", "World");
|
||||||
|
response.body = request.url.pathname;
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
new handlerJS.handler(handlerJS.method["POST"], [
|
||||||
|
async (request, response) => {
|
||||||
|
response = response ?? new handlerJS.response("");
|
||||||
|
response.body = request.body;
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.binding(
|
||||||
|
"/error",
|
||||||
|
App.create(handlerJS.method["ANY"], async () => {
|
||||||
|
throw new Error("Nothing will happen here.");
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.useErrorResponder(errorHandler);
|
||||||
|
|
||||||
|
App.useAdapater(handlerJS.platformAdapater.NodePlatformAdapter);
|
||||||
|
App.adapater.listen(8080);
|
3
index.ts
3
index.ts
|
@ -3,10 +3,11 @@ import { rootRouter } from "./src/router";
|
||||||
export { handler } from "./src/handler";
|
export { handler } from "./src/handler";
|
||||||
export { route } from "./src/route";
|
export { route } from "./src/route";
|
||||||
export { router } from "./src/router";
|
export { router } from "./src/router";
|
||||||
export { method } from "./src/interface/method";
|
export { methodENUM as method } from "./src/interface/method";
|
||||||
export { response } from "./src/interface/response";
|
export { response } from "./src/interface/response";
|
||||||
export { request } from "./src/interface/request";
|
export { request } from "./src/interface/request";
|
||||||
export { ChainInterrupted } from "./src/interface";
|
export { ChainInterrupted } from "./src/interface";
|
||||||
|
export * as platformAdapater from "./src/platform/export";
|
||||||
|
|
||||||
export { rootRouter };
|
export { rootRouter };
|
||||||
export default rootRouter;
|
export default rootRouter;
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
"unified"
|
"unified"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.0.0"
|
"@types/node": "^18.0.0",
|
||||||
}
|
"typescript": "^4.7.4"
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ export class headers {
|
||||||
public headers: { [key: string]: string } = {};
|
public headers: { [key: string]: string } = {};
|
||||||
constructor(headers: { [key: string]: string }) {
|
constructor(headers: { [key: string]: string }) {
|
||||||
this.headers = {};
|
this.headers = {};
|
||||||
Object.keys(this.headers).forEach((key) => {
|
Object.keys(headers).forEach((key) => {
|
||||||
this.headers[lib.firstUpperCase(key)] = headers[key];
|
this.headers[lib.firstUpperCase(key)] = headers[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -22,5 +22,10 @@ export class headers {
|
||||||
toObject() {
|
toObject() {
|
||||||
return this.headers;
|
return this.headers;
|
||||||
}
|
}
|
||||||
|
forEach(func: (key: string, value: string) => any) {
|
||||||
|
Object.keys(this.headers).forEach((key) => {
|
||||||
|
func(key, this.headers[key]);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export default headers;
|
export default headers;
|
|
@ -4,4 +4,5 @@ export { method } from "./method";
|
||||||
export { headers } from "./headers";
|
export { headers } from "./headers";
|
||||||
export { responder } from "./responder";
|
export { responder } from "./responder";
|
||||||
export const ChainInterrupted = new Error("ChainInterrupted");
|
export const ChainInterrupted = new Error("ChainInterrupted");
|
||||||
|
export const AllMismatchInterrupted = new Error("AllMismatchInterrupted");
|
||||||
export type path = string | RegExp;
|
export type path = string | RegExp;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export enum method {
|
export enum methodENUM {
|
||||||
/**
|
/**
|
||||||
* The `CONNECT` method establishes a tunnel to the server identified by the
|
* The `CONNECT` method establishes a tunnel to the server identified by the
|
||||||
* target resource.
|
* target resource.
|
||||||
|
@ -55,4 +55,7 @@ export enum method {
|
||||||
*/
|
*/
|
||||||
ANY = "ANY",
|
ANY = "ANY",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type method = methodENUM | string;
|
||||||
|
|
||||||
export default method;
|
export default method;
|
|
@ -9,12 +9,14 @@ export class request<RequestCustomType> {
|
||||||
public readonly query: URLSearchParams;
|
public readonly query: URLSearchParams;
|
||||||
public params: { [key: string]: string | undefined };
|
public params: { [key: string]: string | undefined };
|
||||||
public custom: RequestCustomType;
|
public custom: RequestCustomType;
|
||||||
|
public ip: string;
|
||||||
public constructor(
|
public constructor(
|
||||||
method: method,
|
method: method,
|
||||||
url: URL,
|
url: URL,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: any,
|
body: any,
|
||||||
params: { [key: string]: string }
|
params: { [key: string]: string },
|
||||||
|
ip: string = "0.0.0.0"
|
||||||
) {
|
) {
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
@ -22,6 +24,7 @@ export class request<RequestCustomType> {
|
||||||
this.body = body;
|
this.body = body;
|
||||||
this.query = new URLSearchParams(url.search);
|
this.query = new URLSearchParams(url.search);
|
||||||
this.params = params;
|
this.params = params;
|
||||||
|
this.ip = headers.get("X-REAL-IP") ?? headers.get("X-Forwarded-For")?.split(" ")[0] ?? ip;
|
||||||
}
|
}
|
||||||
public extends(custom: RequestCustomType): request<RequestCustomType> {
|
public extends(custom: RequestCustomType): request<RequestCustomType> {
|
||||||
this.custom = custom;
|
this.custom = custom;
|
||||||
|
|
|
@ -6,10 +6,10 @@ export class defaultHeaders extends headers {
|
||||||
constructor(headers: { [key: string]: string } = {}) {
|
constructor(headers: { [key: string]: string } = {}) {
|
||||||
super(headers);
|
super(headers);
|
||||||
if (!this.has("Content-Type"))
|
if (!this.has("Content-Type"))
|
||||||
this.set("Content-Type", "plain/text; charset=utf-8");
|
this.set("Content-Type", "text/plain; charset=utf-8");
|
||||||
this.set(
|
this.set(
|
||||||
"Server",
|
"Server",
|
||||||
`Handler.JS/${packageJSON.version} ${platform}/${version}`
|
`Handlers.JS/${packageJSON.version} ${platform}/${version}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export const firstUpperCase = ([first, ...rest]: string) =>
|
export const firstUpperCase = ([first, ...rest]: string) =>
|
||||||
first?.toUpperCase() + rest.join("");
|
first?.toUpperCase() + rest.map((e) => e.toLowerCase()).join("");
|
||||||
export const platform = (() => {
|
export const platform = (() => {
|
||||||
if (typeof process != "undefined") {
|
if (typeof process != "undefined") {
|
||||||
return "Node.js";
|
return "Node.js";
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export { NodePlatformAdapter } from "./node";
|
|
@ -1,7 +1,20 @@
|
||||||
import { request, response } from "../interface";
|
import { request, response } from "../interface";
|
||||||
|
import { router } from "../../index";
|
||||||
|
|
||||||
export interface PlatformAdapater<T = any, K = any> {
|
export interface platformAdapater<T = any, K = any> {
|
||||||
|
router: router<T, K>;
|
||||||
listen(port: number): void;
|
listen(port: number): void;
|
||||||
handleRequest(request: any): request<T>;
|
handleRequest(nativeRequest: any): Promise<request<T>>;
|
||||||
handleResponse(response: response<K>, NativeResponse?: any): any;
|
handleResponse(response: response<K>, nativeResponse?: any): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface platformAdapaterConstructor<T = any, K = any> {
|
||||||
|
new (router: router<T, K>): platformAdapater<T, K>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPlatformAdapater(
|
||||||
|
adapater: platformAdapaterConstructor,
|
||||||
|
router: router
|
||||||
|
): platformAdapater {
|
||||||
|
return new adapater(router);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,77 @@
|
||||||
import { PlatformAdapater } from ".";
|
import { platformAdapater } from ".";
|
||||||
import { request, response } from "../interface";
|
import { request, response } from "../interface";
|
||||||
import router from "../router";
|
import { router } from "../../";
|
||||||
|
import { headers } from "../interface/headers";
|
||||||
|
|
||||||
import http from "http";
|
import http from "http";
|
||||||
|
|
||||||
export class NodePlatformAdapter<T = any, K = any> implements PlatformAdapater {
|
export class NodePlatformAdapter<T = any, K = any> implements platformAdapater {
|
||||||
constructor(Router: )
|
public router: router<T, K>;
|
||||||
listen(port: number): void {
|
|
||||||
|
constructor(router: router<T, K>) {
|
||||||
|
this.router = router;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async listen(port: number): Promise<void> {
|
||||||
const server = http.createServer();
|
const server = http.createServer();
|
||||||
server.on(
|
server.on(
|
||||||
"request",
|
"request",
|
||||||
(req: http.IncomingMessage, res: http.ServerResponse) => {
|
async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||||
const request = this.handleRequest(req);
|
const request = await this.handleRequest(req);
|
||||||
|
const response = await this.router.respond(request);
|
||||||
|
this.handleResponse(response, res);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
server.listen(port);
|
server.listen(port);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRequest(request: http.IncomingMessage): request<T> {
|
async handleRequest(
|
||||||
throw new Error("Method not implemented.");
|
nativeRequest: http.IncomingMessage
|
||||||
|
): Promise<request<T>> {
|
||||||
|
if (
|
||||||
|
typeof nativeRequest.method != "string" ||
|
||||||
|
typeof nativeRequest.url != "string" ||
|
||||||
|
typeof nativeRequest.headers != "object"
|
||||||
|
) {
|
||||||
|
throw new Error("Invalid request");
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: string = "";
|
||||||
|
const ip: string = nativeRequest.socket.remoteAddress?.replace("::ffff:","") ?? "0.0.0.0";
|
||||||
|
const requestHeaders = new headers(<any>nativeRequest.headers);
|
||||||
|
|
||||||
|
if (!["GET", "HEAD", "DELETE", "OPTIONS"].includes(nativeRequest.method)) {
|
||||||
|
nativeRequest.on("data", (data: string) => {
|
||||||
|
body += data;
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
nativeRequest.on("end", () => {
|
||||||
|
resolve(true);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new request<T>(
|
||||||
|
nativeRequest.method,
|
||||||
|
new URL(
|
||||||
|
nativeRequest.url,
|
||||||
|
`http://${requestHeaders.get("host") ?? "localhost"}`
|
||||||
|
),
|
||||||
|
requestHeaders,
|
||||||
|
body,
|
||||||
|
{},
|
||||||
|
ip
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResponse(response: response<K>, NativeResponse: http.ServerResponse) {
|
handleResponse(response: response<K>, nativeResponse: http.ServerResponse) {
|
||||||
throw new Error("Method not implemented.");
|
nativeResponse.statusCode = response.status;
|
||||||
|
response.headers.forEach((key, value) => {
|
||||||
|
nativeResponse.setHeader(key, value);
|
||||||
|
});
|
||||||
|
nativeResponse.end(response.body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,11 @@ interface matchedStatus {
|
||||||
|
|
||||||
export class route {
|
export class route {
|
||||||
public paths: path[];
|
public paths: path[];
|
||||||
public handler: handler<any, any>;
|
public handlers: handler<any, any>[];
|
||||||
|
|
||||||
constructor(paths: path[], handler: handler<any, any>) {
|
constructor(paths: path[], handlers: handler<any, any>[]) {
|
||||||
this.paths = paths;
|
this.paths = paths;
|
||||||
this.handler = handler;
|
this.handlers = handlers;
|
||||||
}
|
}
|
||||||
async exec(path: string): Promise<matchedStatus> {
|
async exec(path: string): Promise<matchedStatus> {
|
||||||
let Answer = await Promise.all<Promise<matchedStatus>>(
|
let Answer = await Promise.all<Promise<matchedStatus>>(
|
||||||
|
|
136
src/router.ts
136
src/router.ts
|
@ -1,17 +1,29 @@
|
||||||
import handler from "./handler";
|
import handler from "./handler";
|
||||||
import {
|
import {
|
||||||
path,
|
path,
|
||||||
method,
|
|
||||||
response,
|
response,
|
||||||
request,
|
request,
|
||||||
ChainInterrupted,
|
ChainInterrupted,
|
||||||
|
AllMismatchInterrupted,
|
||||||
|
responder,
|
||||||
} from "./interface/index";
|
} from "./interface/index";
|
||||||
import { defaultHeaders } from "./interface/response";
|
import { defaultHeaders } from "./interface/response";
|
||||||
import route from "./route";
|
import route from "./route";
|
||||||
|
import { method, methodENUM } from "./interface/method";
|
||||||
|
import {
|
||||||
|
createPlatformAdapater,
|
||||||
|
platformAdapaterConstructor,
|
||||||
|
platformAdapater,
|
||||||
|
} from "./platform";
|
||||||
|
|
||||||
export class router<K = any, V = any> {
|
export class router<K = any, V = any> {
|
||||||
public routes: route[];
|
public routes: route[];
|
||||||
|
|
||||||
|
public errorResponder: (
|
||||||
|
errorCode: number,
|
||||||
|
errorMessage?: string
|
||||||
|
) => responder<K, V>;
|
||||||
|
|
||||||
constructor(routes: route[] = []) {
|
constructor(routes: route[] = []) {
|
||||||
this.routes = routes;
|
this.routes = routes;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +34,7 @@ export class router<K = any, V = any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
binding(path: path, handler: handler<K, V>) {
|
binding(path: path, handler: handler<K, V>) {
|
||||||
this.add(new route([path], handler));
|
this.add(new route([path], [handler]));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,11 +54,11 @@ export class router<K = any, V = any> {
|
||||||
const answer = await responder(request);
|
const answer = await responder(request);
|
||||||
if (answer instanceof response) {
|
if (answer instanceof response) {
|
||||||
return answer;
|
return answer;
|
||||||
} else if (answer instanceof String) {
|
} else if (typeof answer == "string") {
|
||||||
return new response(answer);
|
return new response(answer);
|
||||||
} else if (answer instanceof Number) {
|
} else if (typeof answer == "number") {
|
||||||
return new response(answer.toString());
|
return new response(answer.toString());
|
||||||
} else if (answer instanceof Object) {
|
} else if (typeof answer == "object") {
|
||||||
return new response(
|
return new response(
|
||||||
JSON.stringify(answer),
|
JSON.stringify(answer),
|
||||||
200,
|
200,
|
||||||
|
@ -63,7 +75,7 @@ export class router<K = any, V = any> {
|
||||||
|
|
||||||
use(routers: router[], path: path): void {
|
use(routers: router[], path: path): void {
|
||||||
routers.forEach((router) => {
|
routers.forEach((router) => {
|
||||||
this.binding(path, router.toHandler(path));
|
this.binding(path, router.toHandler());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,23 +85,22 @@ export class router<K = any, V = any> {
|
||||||
return Router;
|
return Router;
|
||||||
}
|
}
|
||||||
|
|
||||||
async respond(request: request<K>, basePath: path): Promise<response<V>> {
|
async _respond(
|
||||||
|
request: request<K>,
|
||||||
|
responseMessage: response<V> = new response<V>("")
|
||||||
|
): Promise<response<V>> {
|
||||||
request.originURL = request.url;
|
request.originURL = request.url;
|
||||||
request.url.pathname = request.url.pathname.replace(basePath, "");
|
request.url.pathname = request.params["0"]
|
||||||
|
? "/" + request.params["0"]
|
||||||
|
: request.originURL.pathname;
|
||||||
|
|
||||||
let responseMessage: response<V> = new response("");
|
let mismatchCount = 0;
|
||||||
|
|
||||||
for (let route of this.routes) {
|
for (let route of this.routes) {
|
||||||
if (
|
|
||||||
route.handler.method != request.method ||
|
|
||||||
route.handler.method != method.ANY
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMatched = await route.exec(request.url.pathname);
|
const isMatched = await route.exec(request.url.pathname);
|
||||||
|
|
||||||
if (!isMatched.matched) {
|
if (!isMatched.matched) {
|
||||||
|
mismatchCount++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,35 +108,104 @@ export class router<K = any, V = any> {
|
||||||
request.params[e.name] = e.value;
|
request.params[e.name] = e.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
let thisResponse = await route.handler.respond(request, responseMessage);
|
try {
|
||||||
if (thisResponse instanceof response) {
|
let thisResponse: response<V> | void = responseMessage;
|
||||||
responseMessage = thisResponse;
|
for (let handler of route.handlers) {
|
||||||
|
if (
|
||||||
|
handler.method != request.method &&
|
||||||
|
handler.method != methodENUM.ANY
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
thisResponse = await handler.respond(
|
||||||
|
request,
|
||||||
|
thisResponse ?? responseMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (thisResponse instanceof response) {
|
||||||
|
responseMessage = thisResponse;
|
||||||
|
} else {
|
||||||
|
// means that the handler is a middleware that doesn't change the response
|
||||||
|
throw AllMismatchInterrupted;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e === ChainInterrupted) {
|
||||||
|
return e.response;
|
||||||
|
}
|
||||||
|
if (e === AllMismatchInterrupted) mismatchCount++;
|
||||||
|
else {
|
||||||
|
if (typeof this.errorResponder == "function") {
|
||||||
|
responseMessage =
|
||||||
|
(await this.errorResponder(500, e.toString() + "\n")(request)) ??
|
||||||
|
new response(e.toString(), 500);
|
||||||
|
console.log(e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mismatchCount == this.routes.length) {
|
||||||
|
throw AllMismatchInterrupted;
|
||||||
|
}
|
||||||
|
|
||||||
return responseMessage;
|
return responseMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
toHandler(basePath: path): handler<K, V> {
|
public respond = this._respond;
|
||||||
return this.create(method.ANY, (request: request<K>) => {
|
|
||||||
return this.respond(request, basePath);
|
toHandler(): handler<K, V> {
|
||||||
});
|
return new handler(methodENUM.ANY, [
|
||||||
|
(request: request<K>, responseMessage?: response<V>) => {
|
||||||
|
return this.respond(request, responseMessage ?? new response(""));
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
useErrorResponder(
|
||||||
|
errorResponder: (
|
||||||
|
errorCode: number,
|
||||||
|
errorMessage?: string
|
||||||
|
) => responder<K, V>
|
||||||
|
): this {
|
||||||
|
this.errorResponder = errorResponder;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
export class rootRouter<K = any, V = any> extends router<K, V> {
|
export class rootRouter<K = any, V = any> extends router<K, V> {
|
||||||
private readonly originRespond = this.respond;
|
public adapater: platformAdapater<K, V>;
|
||||||
async respond(request: request<K>, basePath: path): Promise<response<V>> {
|
errorResponder =
|
||||||
|
(errorCode: number, errorMessage?: string) =>
|
||||||
|
async (_request: request<K>): Promise<response<V>> =>
|
||||||
|
new response(errorMessage ?? "", errorCode);
|
||||||
|
|
||||||
|
respond = async (request: request<K>): Promise<response<V>> => {
|
||||||
|
let responseMessage: response<V> = new response("");
|
||||||
try {
|
try {
|
||||||
return this.originRespond(request, basePath);
|
responseMessage = await this._respond(request, responseMessage);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e === ChainInterrupted) {
|
if (e === ChainInterrupted) {
|
||||||
return e.response;
|
return responseMessage;
|
||||||
|
} else if (e === AllMismatchInterrupted) {
|
||||||
|
responseMessage =
|
||||||
|
(await this.errorResponder(404, "404 Not Found\n")(request)) ??
|
||||||
|
new response("404 Not Found\n", 404);
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
responseMessage =
|
||||||
|
(await this.errorResponder(500, e.toString() + "\n")(request)) ??
|
||||||
|
new response(e.toString(), 500);
|
||||||
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return responseMessage;
|
||||||
|
};
|
||||||
|
useAdapater(adapater: platformAdapaterConstructor): this {
|
||||||
|
this.adapater = createPlatformAdapater(adapater, this);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
import * as handlerJS from "../";
|
|
||||||
|
|
||||||
interface requestType {
|
|
||||||
hood: boolean;
|
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const App = new handlerJS.rootRouter<requestType, any>();
|
|
||||||
|
|
||||||
App.binding(
|
|
||||||
"/",
|
|
||||||
App.create(handlerJS.method["ANY"], async (request) => {
|
|
||||||
Promise.resolve("Hello World!");
|
|
||||||
throw handlerJS.ChainInterrupted;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
App.binding(
|
|
||||||
"/*",
|
|
||||||
App.create(handlerJS.method["ANY"], async (request) => "Fuck World!")
|
|
||||||
);
|
|
||||||
|
|
||||||
App.route("/v1")
|
|
||||||
.add(
|
|
||||||
new handlerJS.route(
|
|
||||||
["/echo", "/echo/*"],
|
|
||||||
new handlerJS.handler(handlerJS.method["GET"], [
|
|
||||||
async (request, response) => {
|
|
||||||
response = response ?? new handlerJS.response("");
|
|
||||||
response?.headers.set("Hello", "World");
|
|
||||||
response.body = "echo";
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.binding(
|
|
||||||
"/:a/echo",
|
|
||||||
App.create(
|
|
||||||
handlerJS.method["GET"],
|
|
||||||
async (request) => `echo with ${request.params.a}`
|
|
||||||
)
|
|
||||||
);
|
|
|
@ -1,12 +1,11 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "esnext",
|
||||||
"module": "commonjs",
|
"module": "esnext",
|
||||||
"lib": [
|
"lib": [
|
||||||
"es6",
|
"ESNext",
|
||||||
"es2017",
|
"WebWorker",
|
||||||
"esnext",
|
"DOM"
|
||||||
"webworker"
|
|
||||||
],
|
],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|
|
@ -11,3 +11,8 @@ path-to-regexp@^6.2.1:
|
||||||
version "6.2.1"
|
version "6.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5"
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5"
|
||||||
integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==
|
integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==
|
||||||
|
|
||||||
|
typescript@^4.7.4:
|
||||||
|
version "4.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
||||||
|
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
||||||
|
|
Loading…
Reference in New Issue