Added NodePlatformAdapter and fixed bug in platform independent functions and added Demo.

This commit is contained in:
2022-06-29 15:00:01 +00:00
committed by GitHub
parent f3c0d646e3
commit bdbb5deda9
18 changed files with 432 additions and 105 deletions

View File

@ -3,7 +3,7 @@ export class headers {
public headers: { [key: string]: string } = {};
constructor(headers: { [key: string]: string }) {
this.headers = {};
Object.keys(this.headers).forEach((key) => {
Object.keys(headers).forEach((key) => {
this.headers[lib.firstUpperCase(key)] = headers[key];
});
}
@ -22,5 +22,10 @@ export class headers {
toObject() {
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;

View File

@ -4,4 +4,5 @@ export { method } from "./method";
export { headers } from "./headers";
export { responder } from "./responder";
export const ChainInterrupted = new Error("ChainInterrupted");
export const AllMismatchInterrupted = new Error("AllMismatchInterrupted");
export type path = string | RegExp;

View File

@ -1,4 +1,4 @@
export enum method {
export enum methodENUM {
/**
* The `CONNECT` method establishes a tunnel to the server identified by the
* target resource.
@ -55,4 +55,7 @@ export enum method {
*/
ANY = "ANY",
}
export type method = methodENUM | string;
export default method;

View File

@ -9,12 +9,14 @@ export class request<RequestCustomType> {
public readonly query: URLSearchParams;
public params: { [key: string]: string | undefined };
public custom: RequestCustomType;
public ip: string;
public constructor(
method: method,
url: URL,
headers: headers,
body: any,
params: { [key: string]: string }
params: { [key: string]: string },
ip: string = "0.0.0.0"
) {
this.method = method;
this.url = url;
@ -22,6 +24,7 @@ export class request<RequestCustomType> {
this.body = body;
this.query = new URLSearchParams(url.search);
this.params = params;
this.ip = headers.get("X-REAL-IP") ?? headers.get("X-Forwarded-For")?.split(" ")[0] ?? ip;
}
public extends(custom: RequestCustomType): request<RequestCustomType> {
this.custom = custom;

View File

@ -6,10 +6,10 @@ export class defaultHeaders extends headers {
constructor(headers: { [key: string]: string } = {}) {
super(headers);
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(
"Server",
`Handler.JS/${packageJSON.version} ${platform}/${version}`
`Handlers.JS/${packageJSON.version} ${platform}/${version}`
);
}
}

View File

@ -1,5 +1,5 @@
export const firstUpperCase = ([first, ...rest]: string) =>
first?.toUpperCase() + rest.join("");
first?.toUpperCase() + rest.map((e) => e.toLowerCase()).join("");
export const platform = (() => {
if (typeof process != "undefined") {
return "Node.js";

1
src/platform/export.ts Normal file
View File

@ -0,0 +1 @@
export { NodePlatformAdapter } from "./node";

View File

@ -1,7 +1,20 @@
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;
handleRequest(request: any): request<T>;
handleResponse(response: response<K>, NativeResponse?: any): any;
handleRequest(nativeRequest: any): Promise<request<T>>;
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);
}

View File

@ -1,27 +1,77 @@
import { PlatformAdapater } from ".";
import { platformAdapater } from ".";
import { request, response } from "../interface";
import router from "../router";
import { router } from "../../";
import { headers } from "../interface/headers";
import http from "http";
export class NodePlatformAdapter<T = any, K = any> implements PlatformAdapater {
constructor(Router: )
listen(port: number): void {
export class NodePlatformAdapter<T = any, K = any> implements platformAdapater {
public router: router<T, K>;
constructor(router: router<T, K>) {
this.router = router;
return this;
}
async listen(port: number): Promise<void> {
const server = http.createServer();
server.on(
"request",
(req: http.IncomingMessage, res: http.ServerResponse) => {
const request = this.handleRequest(req);
async (req: http.IncomingMessage, res: http.ServerResponse) => {
const request = await this.handleRequest(req);
const response = await this.router.respond(request);
this.handleResponse(response, res);
}
);
server.listen(port);
return;
}
handleRequest(request: http.IncomingMessage): request<T> {
throw new Error("Method not implemented.");
async handleRequest(
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) {
throw new Error("Method not implemented.");
handleResponse(response: response<K>, nativeResponse: http.ServerResponse) {
nativeResponse.statusCode = response.status;
response.headers.forEach((key, value) => {
nativeResponse.setHeader(key, value);
});
nativeResponse.end(response.body);
}
}

View File

@ -12,11 +12,11 @@ interface matchedStatus {
export class route {
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.handler = handler;
this.handlers = handlers;
}
async exec(path: string): Promise<matchedStatus> {
let Answer = await Promise.all<Promise<matchedStatus>>(

View File

@ -1,17 +1,29 @@
import handler from "./handler";
import {
path,
method,
response,
request,
ChainInterrupted,
AllMismatchInterrupted,
responder,
} from "./interface/index";
import { defaultHeaders } from "./interface/response";
import route from "./route";
import { method, methodENUM } from "./interface/method";
import {
createPlatformAdapater,
platformAdapaterConstructor,
platformAdapater,
} from "./platform";
export class router<K = any, V = any> {
public routes: route[];
public errorResponder: (
errorCode: number,
errorMessage?: string
) => responder<K, V>;
constructor(routes: route[] = []) {
this.routes = routes;
}
@ -22,7 +34,7 @@ export class router<K = any, V = any> {
}
binding(path: path, handler: handler<K, V>) {
this.add(new route([path], handler));
this.add(new route([path], [handler]));
return this;
}
@ -42,11 +54,11 @@ export class router<K = any, V = any> {
const answer = await responder(request);
if (answer instanceof response) {
return answer;
} else if (answer instanceof String) {
} else if (typeof answer == "string") {
return new response(answer);
} else if (answer instanceof Number) {
} else if (typeof answer == "number") {
return new response(answer.toString());
} else if (answer instanceof Object) {
} else if (typeof answer == "object") {
return new response(
JSON.stringify(answer),
200,
@ -63,7 +75,7 @@ export class router<K = any, V = any> {
use(routers: router[], path: path): void {
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;
}
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.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) {
if (
route.handler.method != request.method ||
route.handler.method != method.ANY
) {
continue;
}
const isMatched = await route.exec(request.url.pathname);
if (!isMatched.matched) {
mismatchCount++;
continue;
}
@ -97,35 +108,104 @@ export class router<K = any, V = any> {
request.params[e.name] = e.value;
});
let thisResponse = await route.handler.respond(request, responseMessage);
if (thisResponse instanceof response) {
responseMessage = thisResponse;
try {
let thisResponse: response<V> | void = responseMessage;
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;
}
toHandler(basePath: path): handler<K, V> {
return this.create(method.ANY, (request: request<K>) => {
return this.respond(request, basePath);
});
public respond = this._respond;
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 class rootRouter<K = any, V = any> extends router<K, V> {
private readonly originRespond = this.respond;
async respond(request: request<K>, basePath: path): Promise<response<V>> {
public adapater: platformAdapater<K, 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 {
return this.originRespond(request, basePath);
responseMessage = await this._respond(request, responseMessage);
} catch (e) {
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 {
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;
}
}