mirror of
https://github.com/186526/handlers.js
synced 2024-10-13 00:29:43 +00:00
Added NodePlatformAdapter and fixed bug in platform independent functions and added Demo.
This commit is contained in:
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
@ -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;
|
||||
|
@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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
1
src/platform/export.ts
Normal file
@ -0,0 +1 @@
|
||||
export { NodePlatformAdapter } from "./node";
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>>(
|
||||
|
136
src/router.ts
136
src/router.ts
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user