This commit is contained in:
2024-07-19 12:38:11 +08:00
parent fccf33d953
commit c216c0a62f
34 changed files with 1302 additions and 1196 deletions

View File

@ -1,43 +1,46 @@
import { method, request, responder, response } from "./interface/index";
import { method, request, responder, response } from './interface/index';
export class handler<RequestCustomType, ResponseCustomType> {
public responders: responder<RequestCustomType, ResponseCustomType>[];
public method: method;
public responders: responder<RequestCustomType, ResponseCustomType>[];
public method: method;
constructor(
method: method,
responders: responder<RequestCustomType, ResponseCustomType>[]
) {
this.responders = responders;
this.method = method;
}
constructor(
method: method,
responders: responder<RequestCustomType, ResponseCustomType>[],
) {
this.responders = responders;
this.method = method;
}
add(responder: responder<RequestCustomType, ResponseCustomType>) {
this.responders.push(responder);
}
add(responder: responder<RequestCustomType, ResponseCustomType>) {
this.responders.push(responder);
}
async respond(
request: request<RequestCustomType>,
responseMessage: response<ResponseCustomType> = new response<ResponseCustomType>(
""
)
): Promise<response<ResponseCustomType> | void> {
switch (this.responders.length) {
case 0:
Promise.reject("No responders found in this handler.");
break;
case 1:
return this.responders[0](request, responseMessage);
default:
for (let responder of this.responders) {
let thisResponse = await responder(request, responseMessage);
if (thisResponse instanceof response) {
responseMessage = thisResponse;
}
}
return responseMessage;
}
}
async respond(
request: request<RequestCustomType>,
responseMessage: response<ResponseCustomType> = new response<ResponseCustomType>(
'',
),
): Promise<response<ResponseCustomType> | void> {
switch (this.responders.length) {
case 0:
Promise.reject('No responders found in this handler.');
break;
case 1:
return this.responders[0](request, responseMessage);
default:
for (let responder of this.responders) {
let thisResponse = await responder(
request,
responseMessage,
);
if (thisResponse instanceof response) {
responseMessage = thisResponse;
}
}
return responseMessage;
}
}
}
export default handler;

View File

@ -1,31 +1,31 @@
import * as lib from "../lib";
import * as lib from '../lib';
export class headers {
public headers: { [key: string]: string } = {};
constructor(headers: { [key: string]: string }) {
this.headers = {};
Object.keys(headers).forEach((key) => {
this.headers[lib.firstUpperCase(key)] = headers[key];
});
}
delete(key: string) {
delete this.headers[lib.firstUpperCase(key)];
}
get(key: string): string | undefined {
return this.headers[lib.firstUpperCase(key)];
}
has(key: string): boolean {
return this.headers.hasOwnProperty(lib.firstUpperCase(key));
}
set(key: string, value: string) {
this.headers[lib.firstUpperCase(key)] = value;
}
toObject() {
return this.headers;
}
forEach(func: (key: string, value: string) => any) {
Object.keys(this.headers).forEach((key) => {
func(key, this.headers[key]);
})
}
public headers: { [key: string]: string } = {};
constructor(headers: { [key: string]: string }) {
this.headers = {};
Object.keys(headers).forEach((key) => {
this.headers[lib.firstUpperCase(key)] = headers[key];
});
}
delete(key: string) {
delete this.headers[lib.firstUpperCase(key)];
}
get(key: string): string | undefined {
return this.headers[lib.firstUpperCase(key)];
}
has(key: string): boolean {
return this.headers.hasOwnProperty(lib.firstUpperCase(key));
}
set(key: string, value: string) {
this.headers[lib.firstUpperCase(key)] = value;
}
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;

View File

@ -1,7 +1,7 @@
export { request } from "./request";
export { response } from "./response";
export { method } from "./method";
export { headers } from "./headers";
export { responder } from "./responder";
export const AllMismatchInterrupted = new Error("AllMismatchInterrupted");
export { request } from './request';
export { response } from './response';
export { method } from './method';
export { headers } from './headers';
export { responder } from './responder';
export const AllMismatchInterrupted = new Error('AllMismatchInterrupted');
export type path = string | RegExp;

View File

@ -1,61 +1,72 @@
export enum methodENUM {
/**
* The `CONNECT` method establishes a tunnel to the server identified by the
* target resource.
*/
CONNECT = "CONNECT",
/**
* The `CONNECT` method establishes a tunnel to the server identified by the
* target resource.
*/
CONNECT = 'CONNECT',
/**
* The `DELETE` method deletes the specified resource.
*/
DELETE = "DELETE",
/**
* The `DELETE` method deletes the specified resource.
*/
DELETE = 'DELETE',
/**
* The `GET` method requests a representation of the specified resource.
* Requests using GET should only retrieve data.
*/
GET = "GET",
/**
* The `GET` method requests a representation of the specified resource.
* Requests using GET should only retrieve data.
*/
GET = 'GET',
/**
* The `HEAD` method asks for a response identical to that of a GET request,
* but without the response body.
*/
HEAD = "HEAD",
/**
* The `HEAD` method asks for a response identical to that of a GET request,
* but without the response body.
*/
HEAD = 'HEAD',
/**
* The `OPTIONS` method is used to describe the communication options for the
* target resource.
*/
OPTIONS = "OPTIONS",
/**
* The `OPTIONS` method is used to describe the communication options for the
* target resource.
*/
OPTIONS = 'OPTIONS',
/**
* The PATCH method is used to apply partial modifications to a resource.
*/
PATCH = "PATCH",
/**
* The PATCH method is used to apply partial modifications to a resource.
*/
PATCH = 'PATCH',
/**
* The `POST` method is used to submit an entity to the specified resource,
* often causing a change in state or side effects on the server.
*/
POST = "POST",
/**
* The `POST` method is used to submit an entity to the specified resource,
* often causing a change in state or side effects on the server.
*/
POST = 'POST',
/**
* The `PUT` method replaces all current representations of the target
* resource with the request payload.
*/
PUT = "PUT",
/**
* The `PUT` method replaces all current representations of the target
* resource with the request payload.
*/
PUT = 'PUT',
/**
* The `TRACE` method performs a message loop-back test along the path to the
* target resource.
*/
TRACE = "TRACE",
/**
* The `ANY` method will match any method.
*/
ANY = "ANY",
/**
* The `TRACE` method performs a message loop-back test along the path to the
* target resource.
*/
TRACE = 'TRACE',
/**
* The `ANY` method will match any method.
*/
ANY = 'ANY',
}
export type method = "CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE" | "ANY" | methodENUM;
export type method =
| 'CONNECT'
| 'DELETE'
| 'GET'
| 'HEAD'
| 'OPTIONS'
| 'PATCH'
| 'POST'
| 'PUT'
| 'TRACE'
| 'ANY'
| methodENUM;
export default method;
export default method;

View File

@ -1,34 +1,37 @@
import method from "./method";
import headers from "./headers";
import method from './method';
import headers from './headers';
export class request<RequestCustomType> {
public readonly method: method;
public readonly url: URL;
public originURL?: URL;
public readonly headers: headers;
public readonly body: any;
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 },
ip: string = "0.0.0.0"
) {
this.method = method;
this.url = url;
this.headers = headers;
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;
return this;
}
public readonly method: method;
public readonly url: URL;
public originURL?: URL;
public readonly headers: headers;
public readonly body: any;
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 },
ip: string = '0.0.0.0',
) {
this.method = method;
this.url = url;
this.headers = headers;
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;
return this;
}
}
export default request;

View File

@ -1,8 +1,8 @@
import { request, response } from "./index";
import { request, response } from './index';
export interface responder<RequestCustomType, ResponseCustomType> {
(
request: request<RequestCustomType>,
reponse?: response<ResponseCustomType>
): Promise<response<ResponseCustomType>> | Promise<void> | void;
(
request: request<RequestCustomType>,
reponse?: response<ResponseCustomType>,
): Promise<response<ResponseCustomType>> | Promise<void> | void;
}
export default responder;

View File

@ -1,35 +1,35 @@
import headers from "./headers";
import packageJSON from "../../package.json";
import { platform, version } from "../lib";
import headers from './headers';
import packageJSON from '../../package.json';
import { platform, version } from '../lib';
export class defaultHeaders extends headers {
constructor(headers: { [key: string]: string } = {}) {
super(headers);
if (!this.has("Content-Type"))
this.set("Content-Type", "text/plain; charset=utf-8");
this.set(
"Server",
`Handlers.js/${packageJSON.version} ${platform}/${version}`
);
}
constructor(headers: { [key: string]: string } = {}) {
super(headers);
if (!this.has('Content-Type'))
this.set('Content-Type', 'text/plain; charset=utf-8');
this.set(
'Server',
`Handlers.js/${packageJSON.version} ${platform}/${version}`,
);
}
}
export class response<ResponseCustomType> {
public status: number;
public headers: headers;
public body: any;
public custom: ResponseCustomType;
public constructor(
body: any,
status: number = 200,
headers: headers = new defaultHeaders()
) {
this.status = status;
this.headers = headers;
this.body = body;
}
public extends(custom: ResponseCustomType): response<ResponseCustomType> {
this.custom = custom;
return this;
}
public status: number;
public headers: headers;
public body: any;
public custom: ResponseCustomType;
public constructor(
body: any,
status: number = 200,
headers: headers = new defaultHeaders(),
) {
this.status = status;
this.headers = headers;
this.body = body;
}
public extends(custom: ResponseCustomType): response<ResponseCustomType> {
this.custom = custom;
return this;
}
}
export default response;

View File

@ -1,34 +1,34 @@
export const firstUpperCase = ([first, ...rest]: string) =>
first?.toUpperCase() + rest.map((e) => e.toLowerCase()).join("");
first?.toUpperCase() + rest.map((e) => e.toLowerCase()).join('');
export const platform = (() => {
if (typeof process != "undefined") {
return "Node.js";
}
if (typeof Deno != "undefined") {
return "Deno";
}
if (typeof Bun != "undefined") {
return "Bun";
}
if (typeof tjs != "undefined") {
return "txiki.js";
}
if (typeof self != "undefined") {
return "Service Worker";
}
return undefined;
if (typeof process != 'undefined') {
return 'Node.js';
}
if (typeof Deno != 'undefined') {
return 'Deno';
}
if (typeof Bun != 'undefined') {
return 'Bun';
}
if (typeof tjs != 'undefined') {
return 'txiki.js';
}
if (typeof self != 'undefined') {
return 'Service Worker';
}
return undefined;
})();
export const version = (() => {
switch (platform) {
case "Node.js" || "Bun":
return process.version;
case "Deno":
return Deno.version.deno;
case "txiki.js":
return tjs.versions.tjs;
case "Service Worker":
return undefined;
default:
return undefined;
}
switch (platform) {
case 'Node.js' || 'Bun':
return process.version;
case 'Deno':
return Deno.version.deno;
case 'txiki.js':
return tjs.versions.tjs;
case 'Service Worker':
return undefined;
default:
return undefined;
}
})();

View File

@ -1,20 +1,20 @@
import { SWPlatformAdapter } from "./serviceworker";
import { platformAdapater } from "./index";
import { SWPlatformAdapter } from './serviceworker';
import { platformAdapater } from './index';
export class BunPlatformAdapter<T = any, K = any>
extends SWPlatformAdapter<T, K>
implements platformAdapater<T, K>
extends SWPlatformAdapter<T, K>
implements platformAdapater<T, K>
{
async listen(port: number): Promise<void> {
Bun.serve({
fetch: async (request: Request): Promise<Response> => {
return await this.handleResponse(
await this.handleRequest(request).then((request) =>
this.router.respond(request)
)
);
},
port,
});
}
async listen(port: number): Promise<void> {
Bun.serve({
fetch: async (request: Request): Promise<Response> => {
return await this.handleResponse(
await this.handleRequest(request).then((request) =>
this.router.respond(request),
),
);
},
port,
});
}
}

View File

@ -1,26 +1,25 @@
import { SWPlatformAdapter } from "./serviceworker";
import { platformAdapater } from "./index";
import { request } from "../interface/request";
import { headers } from "../interface/headers";
import { methodENUM } from "src/interface/method";
import { SWPlatformAdapter } from './serviceworker';
import { platformAdapater } from './index';
import { request } from '../interface/request';
import { headers } from '../interface/headers';
import { methodENUM } from 'src/interface/method';
const DefaultConn: Deno.Conn = {
localAddr: {
transport: "tcp",
hostname: "0.0.0.0",
transport: 'tcp',
hostname: '0.0.0.0',
port: 80,
},
remoteAddr: {
transport: "tcp",
hostname: "0.0.0.0",
transport: 'tcp',
hostname: '0.0.0.0',
port: 80,
},
rid: 0,
closeWrite: async () => undefined,
readable: "",
writable: "",
readable: '',
writable: '',
read: async (p: Uint8Array) => null,
write: async (p: Uint8Array) => 0,
close: () => undefined,
@ -37,14 +36,19 @@ export class DenoPlatformAdapter<T = any, K = any>
const httpConnection = Deno.serveHttp(connection);
for await (const requestEvent of httpConnection) {
requestEvent.respondWith(this.handler(requestEvent, connection));
requestEvent.respondWith(
this.handler(requestEvent, connection),
);
}
}
}
async handleRequest(nativeRequest: Request, connection: Deno.Conn = DefaultConn): Promise<request<T>> {
async handleRequest(
nativeRequest: Request,
connection: Deno.Conn = DefaultConn,
): Promise<request<T>> {
const requestHeaders = new headers(
Object.fromEntries(nativeRequest.headers.entries())
Object.fromEntries(nativeRequest.headers.entries()),
);
const requestMessage: request<T> = new request(
<methodENUM>nativeRequest.method,
@ -52,16 +56,20 @@ export class DenoPlatformAdapter<T = any, K = any>
requestHeaders,
await nativeRequest.text(),
{},
`${connection.remoteAddr.hostname}:${connection.remoteAddr.port}` || ""
`${connection.remoteAddr.hostname}:${connection.remoteAddr.port}` ||
'',
);
return requestMessage;
}
async handler(event: FetchEvent, connection: Deno.Conn = DefaultConn): Promise<Response> {
async handler(
event: FetchEvent,
connection: Deno.Conn = DefaultConn,
): Promise<Response> {
return await this.handleResponse(
await this.handleRequest(event.request, connection).then((request) =>
this.router.respond(request)
)
)
await this.handleRequest(event.request, connection).then(
(request) => this.router.respond(request),
),
);
}
}

View File

@ -1,13 +1,18 @@
import { NodePlatformAdapter } from "./node";
import { SWPlatformAdapter } from "./serviceworker";
import { DenoPlatformAdapter } from "./deno";
import { TxikiPlatformAdapter } from "./txiki";
import { NodePlatformAdapter } from './node';
import { SWPlatformAdapter } from './serviceworker';
import { DenoPlatformAdapter } from './deno';
import { TxikiPlatformAdapter } from './txiki';
export const platformAdapaterMapping = {
"Node.js": NodePlatformAdapter,
"Service Worker": SWPlatformAdapter,
"Deno": DenoPlatformAdapter,
"txiki.js": TxikiPlatformAdapter,
'Node.js': NodePlatformAdapter,
'Service Worker': SWPlatformAdapter,
Deno: DenoPlatformAdapter,
'txiki.js': TxikiPlatformAdapter,
};
export { NodePlatformAdapter, SWPlatformAdapter, DenoPlatformAdapter, TxikiPlatformAdapter };
export {
NodePlatformAdapter,
SWPlatformAdapter,
DenoPlatformAdapter,
TxikiPlatformAdapter,
};

View File

@ -1,20 +1,23 @@
import { request, response } from "../interface/index";
import { router } from "../router";
import { request, response } from '../interface/index';
import { router } from '../router';
export interface platformAdapater<T = any, K = any> {
router: router<T, K>;
listen(port: number): void;
handleRequest(nativeRequest: any): Promise<request<T>>;
handleResponse(response: response<K> | Promise<response<K>>, nativeResponse?: any): any;
router: router<T, K>;
listen(port: number): void;
handleRequest(nativeRequest: any): Promise<request<T>>;
handleResponse(
response: response<K> | Promise<response<K>>,
nativeResponse?: any,
): any;
}
export interface platformAdapaterConstructor<T = any, K = any> {
new (router: router<T, K>): platformAdapater<T, K>;
new (router: router<T, K>): platformAdapater<T, K>;
}
export function createPlatformAdapater(
adapater: platformAdapaterConstructor,
router: router
adapater: platformAdapaterConstructor,
router: router,
): platformAdapater {
return new adapater(router);
return new adapater(router);
}

View File

@ -1,77 +1,81 @@
import { platformAdapater } from "./index";
import { request, response } from "../interface/index";
import { router } from "../router";
import { headers } from "../interface/headers";
import { platformAdapater } from './index';
import { request, response } from '../interface/index';
import { router } from '../router';
import { headers } from '../interface/headers';
import http from "http";
import { methodENUM } from "src/interface/method";
import http from 'http';
import { methodENUM } from 'src/interface/method';
export class NodePlatformAdapter<T = any, K = any> implements platformAdapater {
public router: router<T, K>;
public router: router<T, K>;
constructor(router: router<T, K>) {
this.router = router;
}
constructor(router: router<T, K>) {
this.router = router;
}
async listen(port: number): Promise<void> {
const server = http.createServer();
server.on(
"request",
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;
}
async listen(port: number): Promise<void> {
const server = http.createServer();
server.on(
'request',
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;
}
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");
}
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);
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;
});
if (
!['GET', 'HEAD', 'DELETE', 'OPTIONS'].includes(nativeRequest.method)
) {
nativeRequest.on('data', (data: string) => {
body += data;
});
await new Promise((resolve) =>
nativeRequest.on("end", () => {
resolve(true);
})
);
}
await new Promise((resolve) =>
nativeRequest.on('end', () => {
resolve(true);
}),
);
}
return new request<T>(
<methodENUM>nativeRequest.method,
new URL(
nativeRequest.url,
`http://${requestHeaders.get("host") ?? "localhost"}`
),
requestHeaders,
body,
{},
ip
);
}
return new request<T>(
<methodENUM>nativeRequest.method,
new URL(
nativeRequest.url,
`http://${requestHeaders.get('host') ?? 'localhost'}`,
),
requestHeaders,
body,
{},
ip,
);
}
handleResponse(response: response<K>, nativeResponse: http.ServerResponse) {
nativeResponse.statusCode = response.status;
response.headers.forEach((key, value) => {
nativeResponse.setHeader(key, value);
});
nativeResponse.end(response.body);
}
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

@ -1,53 +1,55 @@
import { platformAdapater } from "./index";
import { request } from "../interface/request";
import { response } from "../interface/response";
import { router } from "../router";
import { headers } from "../interface/headers";
import { platformAdapater } from './index';
import { request } from '../interface/request';
import { response } from '../interface/response';
import { router } from '../router';
import { headers } from '../interface/headers';
import { methodENUM } from "src/interface/method";
import { methodENUM } from 'src/interface/method';
export class SWPlatformAdapter<T = any, K = any> implements platformAdapater {
public router: router<T, K>;
public router: router<T, K>;
constructor(router: router<T, K>) {
this.router = router;
}
constructor(router: router<T, K>) {
this.router = router;
}
async listen(_port?: number): Promise<void> {
self.addEventListener("fetch", (event: FetchEvent) => {
event.respondWith(this.handler(event));
});
}
async listen(_port?: number): Promise<void> {
self.addEventListener('fetch', (event: FetchEvent) => {
event.respondWith(this.handler(event));
});
}
async handleRequest(nativeRequest: Request): Promise<request<T>> {
const requestHeaders = new headers(
Object.fromEntries(nativeRequest.headers.entries())
);
const requestMessage: request<T> = new request(
<methodENUM>nativeRequest.method,
new URL(nativeRequest.url),
requestHeaders,
await nativeRequest.text(),
{},
requestHeaders.get("CF-Connecting-IP") || ""
);
return requestMessage;
}
async handleRequest(nativeRequest: Request): Promise<request<T>> {
const requestHeaders = new headers(
Object.fromEntries(nativeRequest.headers.entries()),
);
const requestMessage: request<T> = new request(
<methodENUM>nativeRequest.method,
new URL(nativeRequest.url),
requestHeaders,
await nativeRequest.text(),
{},
requestHeaders.get('CF-Connecting-IP') || '',
);
return requestMessage;
}
async handleResponse(response: response<K>): Promise<Response> {
if (response.status === 204) { response.body = null; }
const nativResponse = new Response(response.body, {
status: response.status,
headers: response.headers.headers,
});
return nativResponse;
}
async handleResponse(response: response<K>): Promise<Response> {
if (response.status === 204) {
response.body = null;
}
const nativResponse = new Response(response.body, {
status: response.status,
headers: response.headers.headers,
});
return nativResponse;
}
async handler(event: FetchEvent): Promise<Response> {
return await this.handleResponse(
await this.handleRequest(event.request).then((request) =>
this.router.respond(request)
)
)
}
async handler(event: FetchEvent): Promise<Response> {
return await this.handleResponse(
await this.handleRequest(event.request).then((request) =>
this.router.respond(request),
),
);
}
}

View File

@ -1,9 +1,9 @@
import { request } from "../../interface/request";
import { response } from "../../interface";
import { headers } from "../../interface/headers";
import { methodENUM } from "../../interface/method";
import { request } from '../../interface/request';
import { response } from '../../interface';
import { headers } from '../../interface/headers';
import { methodENUM } from '../../interface/method';
import statusCode from "./statusCode.json";
import statusCode from './statusCode.json';
export class HttpConn {
private closed: boolean = false;
@ -16,44 +16,63 @@ export class HttpConn {
}
private readMessage(httpMessage: string): request<any> {
const lines = httpMessage.split("\n");
const lines = httpMessage.split('\n');
const firstLine = lines[0];
const dividingIndex = lines.indexOf("\r") ?? lines.indexOf("");
const dividingIndex = lines.indexOf('\r') ?? lines.indexOf('');
const rawHeaders = lines.slice(1, dividingIndex);
const [method, path, version] = firstLine.split(" ");
const [method, path, version] = firstLine.split(' ');
if (version in ['HTTP/1.1', 'HTTP/1.0', 'HTTP/0.9']) {
this.conn.close();
}
const requestHeaders = new headers({});
for (const header of rawHeaders) {
const [key, value] = header.split(": ");
const [key, value] = header.split(': ');
requestHeaders.set(key, value);
}
const url = new URL(path, `http://${requestHeaders.get("Host")}/` ?? `http://${this.conn.localAddress.ip}:${this.conn.localAddress.port}/`);
const url = new URL(
path,
`http://${requestHeaders.get('Host')}/` ??
`http://${this.conn.localAddress.ip}:${this.conn.localAddress.port}/`,
);
const body = lines.slice(dividingIndex + 1).join("\n");
const body = lines.slice(dividingIndex + 1).join('\n');
const requestMessage = new request<any>(<methodENUM>method, url, requestHeaders, body, {}, this.conn.remoteAddress.ip);
const requestMessage = new request<any>(
<methodENUM>method,
url,
requestHeaders,
body,
{},
this.conn.remoteAddress.ip,
);
return requestMessage;
}
private handleResponse(response: response<any>) {
let responseMessage: string = "";
responseMessage += "HTTP/1.1 " + response.status + " " + statusCode[<"100">response.status.toString()] ?? "";
let responseMessage: string = '';
responseMessage +=
'HTTP/1.1 ' +
response.status +
' ' +
statusCode[<'100'>response.status.toString()] ?? '';
response.headers.forEach((key, value) => {
responseMessage += "\n" + key + ": " + value;
responseMessage += '\n' + key + ': ' + value;
});
responseMessage += "\n\n" + response.body;
responseMessage += '\n\n' + response.body;
this.conn.write(new TextEncoder().encode(responseMessage));
this.conn.shutdown();
this.conn.close();
this.closed = true;
}
private async read(): Promise<request<any> | undefined> {
let message = "";
let message = '';
const { done, value } = await this.reader.read();
if (done || this.closed) {
@ -61,9 +80,15 @@ export class HttpConn {
return undefined;
}
message += String.fromCharCode(...Object.values(<{
[key: string]: number
}>value));
message += String.fromCharCode(
...Object.values(
<
{
[key: string]: number;
}
>value,
),
);
const requestMessage = this.readMessage(message);
return requestMessage;
@ -82,13 +107,13 @@ export class HttpConn {
respondWith: (response: response<any>) => {
httpConn.handleResponse(response);
},
}
}
}
}
},
};
},
};
}
}
export default function serveHttp(Connection: tjs.Connection) {
return new HttpConn(Connection);
}
}

View File

@ -61,4 +61,4 @@
"509": "Bandwidth Limit Exceeded",
"510": "Not Extended",
"511": "Network Authentication Required"
}
}

View File

@ -1,10 +1,12 @@
import { platformAdapater } from "./index";
import { request } from "../interface/request";
import { response } from "../interface/response";
import { router } from "../router";
import serveHttp from "./txiki-js/serveHttp";
import { platformAdapater } from './index';
import { request } from '../interface/request';
import { response } from '../interface/response';
import { router } from '../router';
import serveHttp from './txiki-js/serveHttp';
export class TxikiPlatformAdapter<T = any, K = any> implements platformAdapater {
export class TxikiPlatformAdapter<T = any, K = any>
implements platformAdapater
{
public router: router<T, K>;
constructor(router: router<T, K>) {
@ -12,13 +14,16 @@ export class TxikiPlatformAdapter<T = any, K = any> implements platformAdapater
}
async listen(port?: number): Promise<void> {
const Server = await tjs.listen("tcp", "0.0.0.0", port);
const Server = await tjs.listen('tcp', '0.0.0.0', port);
for await (const conn of Server) {
const httpConn = serveHttp(conn);
for await (const conn of httpConn) {
if (typeof conn == "undefined" || typeof conn.request == "undefined") {
if (
typeof conn == 'undefined' ||
typeof conn.request == 'undefined'
) {
return;
}
conn.respondWith(await this.router.respond(conn.request));
@ -33,4 +38,4 @@ export class TxikiPlatformAdapter<T = any, K = any> implements platformAdapater
async handleResponse(response: response<K>): Promise<response<K>> {
return response;
}
}
}

View File

@ -1,71 +1,70 @@
import { path } from "./interface";
import handler from "./handler";
import { pathToRegexp } from "path-to-regexp";
import { path } from './interface';
import handler from './handler';
import { pathToRegexp } from 'path-to-regexp';
interface matchedStatus {
matched: boolean;
attributes: {
name: string;
value: string | undefined;
}[];
matched: boolean;
attributes: {
name: string;
value: string | undefined;
}[];
}
interface regExpKey {
name: string;
prefix: string;
suffix: string;
pattern: string;
modifier: string;
};
name: string;
prefix: string;
suffix: string;
pattern: string;
modifier: string;
}
export class route {
private paths: path[];
public handlers: handler<any, any>[];
private regExps: { regExp: RegExp, keys: regExpKey[] }[] = [];
private paths: path[];
public handlers: handler<any, any>[];
private regExps: { regExp: RegExp; keys: regExpKey[] }[] = [];
constructor(paths: path[], handlers: handler<any, any>[]) {
this.paths = paths;
this.handlers = handlers;
constructor(paths: path[], handlers: handler<any, any>[]) {
this.paths = paths;
this.handlers = handlers;
this.paths.forEach(path => {
const keys: regExpKey[] = [];
this.regExps.push({ regExp: pathToRegexp(path, keys), keys });
})
}
async exec(path: string): Promise<matchedStatus> {
let Answer = await Promise.all<Promise<matchedStatus>>(
this.regExps.map(async (it) => {
this.paths.forEach((path) => {
const keys: regExpKey[] = [];
this.regExps.push({ regExp: pathToRegexp(path, keys), keys });
});
}
async exec(path: string): Promise<matchedStatus> {
let Answer = await Promise.all<Promise<matchedStatus>>(
this.regExps.map(async (it) => {
const answer = it.regExp.exec(path);
if (answer === null)
return {
matched: false,
attributes: [],
};
const answer = it.regExp.exec(path);
if (answer === null)
return {
matched: false,
attributes: [],
};
let attributes: matchedStatus['attributes'] = [];
let attributes: matchedStatus["attributes"] = [];
it.keys.forEach((key, index) => {
attributes.push({
name: key.name,
value: answer[index + 1],
});
});
it.keys.forEach((key, index) => {
attributes.push({
name: key.name,
value: answer[index + 1],
});
});
return {
matched: true,
attributes: attributes,
};
})
);
Answer = Answer.filter((it) => it.matched);
if (Answer.length === 0)
return {
matched: false,
attributes: [],
};
else return Answer[0];
}
return {
matched: true,
attributes: attributes,
};
}),
);
Answer = Answer.filter((it) => it.matched);
if (Answer.length === 0)
return {
matched: false,
attributes: [],
};
else return Answer[0];
}
}
export default route;

View File

@ -1,225 +1,238 @@
import handler from "./handler";
import handler from './handler';
import {
path,
response,
request,
AllMismatchInterrupted,
responder,
method,
} from "./interface/index";
import { defaultHeaders } from "./interface/response";
import route from "./route";
import { methodENUM } from "./interface/method";
path,
response,
request,
AllMismatchInterrupted,
responder,
method,
} from './interface/index';
import { defaultHeaders } from './interface/response';
import route from './route';
import { methodENUM } from './interface/method';
import {
createPlatformAdapater,
platformAdapaterConstructor,
platformAdapater,
} from "./platform/index";
import { platformAdapaterMapping } from "./platform/export";
import { platform } from "./lib";
createPlatformAdapater,
platformAdapaterConstructor,
platformAdapater,
} from './platform/index';
import { platformAdapaterMapping } from './platform/export';
import { platform } from './lib';
export class router<K = any, V = any> {
public routes: route[];
public routes: route[];
public errorResponder: (
errorCode: number,
errorMessage?: string
) => responder<K, V>;
public errorResponder: (
errorCode: number,
errorMessage?: string,
) => responder<K, V>;
constructor(routes: route[] = []) {
this.routes = routes;
}
constructor(routes: route[] = []) {
this.routes = routes;
}
add(route: route) {
this.routes.push(route);
return this;
}
add(route: route) {
this.routes.push(route);
return this;
}
binding(path: path, handler: handler<K, V>) {
this.add(new route([path], [handler]));
return this;
}
binding(path: path, handler: handler<K, V>) {
this.add(new route([path], [handler]));
return this;
}
create(
method: method,
responder: (
request: request<K>
) =>
| Promise<response<V>>
| Promise<string>
| Promise<object>
| Promise<number>
| Promise<void>
) {
return new handler<K, V>(method, [
async (request: request<K>) => {
const answer = await responder(request);
if (answer instanceof response) {
return answer;
} else if (typeof answer == "string") {
return new response(answer);
} else if (typeof answer == "number") {
return new response(answer.toString());
} else if (typeof answer == "object") {
return new response(
JSON.stringify(answer),
200,
new defaultHeaders({
"Content-Type": "application/json; charset=utf-8",
})
);
} else {
return new response("", 204);
}
},
]);
}
create(
method: method,
responder: (
request: request<K>,
) =>
| Promise<response<V>>
| Promise<string>
| Promise<object>
| Promise<number>
| Promise<void>,
) {
return new handler<K, V>(method, [
async (request: request<K>) => {
const answer = await responder(request);
if (answer instanceof response) {
return answer;
} else if (typeof answer == 'string') {
return new response(answer);
} else if (typeof answer == 'number') {
return new response(answer.toString());
} else if (typeof answer == 'object') {
return new response(
JSON.stringify(answer),
200,
new defaultHeaders({
'Content-Type': 'application/json; charset=utf-8',
}),
);
} else {
return new response('', 204);
}
},
]);
}
use(routers: router[], path: path): void {
routers.forEach((router) => {
this.binding(path, router.toHandler());
});
}
use(routers: router[], path: path): void {
routers.forEach((router) => {
this.binding(path, router.toHandler());
});
}
route(path: path): router {
const Router = new router([]);
this.use([Router], path);
return Router;
}
route(path: path): router {
const Router = new router([]);
this.use([Router], path);
return Router;
}
async _respond(
request: request<K>,
responseMessage: response<V> = new response<V>("")
): Promise<response<V>> {
request.originURL = request.url;
request.url.pathname = request.params["0"]
? "/" + request.params["0"]
: request.originURL.pathname;
async _respond(
request: request<K>,
responseMessage: response<V> = new response<V>(''),
): Promise<response<V>> {
request.originURL = request.url;
request.url.pathname = request.params['0']
? '/' + request.params['0']
: request.originURL.pathname;
let mismatchCount = 0;
let mismatchCount = 0;
for (let route of this.routes) {
const isMatched = await route.exec(request.url.pathname);
for (let route of this.routes) {
const isMatched = await route.exec(request.url.pathname);
if (!isMatched.matched) {
mismatchCount++;
continue;
}
if (!isMatched.matched) {
mismatchCount++;
continue;
}
isMatched.attributes.forEach((e) => {
request.params[e.name] = e.value;
});
isMatched.attributes.forEach((e) => {
request.params[e.name] = e.value;
});
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 instanceof response) {
throw e;
}
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;
}
}
}
}
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 instanceof response) {
throw e;
}
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;
}
if (mismatchCount == this.routes.length) {
throw AllMismatchInterrupted;
}
return responseMessage;
}
return responseMessage;
}
public respond = this._respond;
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(""));
},
]);
}
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;
}
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> {
public adapater: platformAdapater<K, V>;
errorResponder =
(errorCode: number, errorMessage?: string) =>
async (_request: request<K>): Promise<response<V>> =>
new response(errorMessage ?? "", errorCode);
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 {
responseMessage = await this._respond(request, responseMessage);
} catch (e) {
if (e instanceof response) {
return e;
} else if (e === AllMismatchInterrupted) {
responseMessage =
(await this.errorResponder(404, "404 Not Found\n")(request)) ??
new response("404 Not Found\n", 404);
} else {
responseMessage =
(await this.errorResponder(500, e.toString() + "\n")(request)) ??
new response(e.toString(), 500);
console.log(e);
}
}
respond = async (request: request<K>): Promise<response<V>> => {
let responseMessage: response<V> = new response('');
try {
responseMessage = await this._respond(request, responseMessage);
} catch (e) {
if (e instanceof response) {
return e;
} else if (e === AllMismatchInterrupted) {
responseMessage =
(await this.errorResponder(
404,
'404 Not Found\n',
)(request)) ?? new response('404 Not Found\n', 404);
} else {
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;
}
useMappingAdapter(
mapping: { [platform: string]: platformAdapaterConstructor } = platformAdapaterMapping
): this {
if (typeof platform == "undefined") throw new Error("Cannot detect platform");
if (mapping[platform] == undefined) throw new Error("Platform not found in mapping");
else this.useAdapater(mapping[platform]);
return this;
}
listen(port: number): void {
if (this.adapater == null) throw new Error("No platform adapter set");
this.adapater.listen(port);
}
return responseMessage;
};
useAdapater(adapater: platformAdapaterConstructor): this {
this.adapater = createPlatformAdapater(adapater, this);
return this;
}
useMappingAdapter(
mapping: {
[platform: string]: platformAdapaterConstructor;
} = platformAdapaterMapping,
): this {
if (typeof platform == 'undefined')
throw new Error('Cannot detect platform');
if (mapping[platform] == undefined)
throw new Error('Platform not found in mapping');
else this.useAdapater(mapping[platform]);
return this;
}
listen(port: number): void {
if (this.adapater == null) throw new Error('No platform adapter set');
this.adapater.listen(port);
}
}