Add Txiki.js Adapter.

This commit is contained in:
2022-07-03 11:00:26 +00:00
committed by GitHub
parent 057c9b9cb5
commit 6559a3f134
10 changed files with 278 additions and 5 deletions

View File

@ -7,6 +7,9 @@ export const platform = (() => {
if (typeof Deno != "undefined") {
return "Deno";
}
if (typeof tjs != "undefined") {
return "txiki.js";
}
if (typeof self != "undefined") {
return "Service Worker";
}
@ -18,6 +21,8 @@ export const version = (() => {
return process.version;
case "Deno":
return Deno.version.deno;
case "txiki.js":
return tjs.versions.tjs;
case "Service Worker":
return undefined;
default:

View File

@ -1,11 +1,13 @@
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,
};
export { NodePlatformAdapter, SWPlatformAdapter, DenoPlatformAdapter };
export { NodePlatformAdapter, SWPlatformAdapter, DenoPlatformAdapter, TxikiPlatformAdapter };

View File

@ -0,0 +1,94 @@
import { request } from "../../interface/request";
import { response } from "../../interface";
import { headers } from "../../interface/headers";
import { methodENUM } from "../../interface/method";
import statusCode from "./statusCode.json";
export class HttpConn {
private closed: boolean = false;
private conn: tjs.Connection;
private reader: ReadableStreamDefaultReader;
constructor(Connection: tjs.Connection) {
this.conn = Connection;
this.reader = this.reader ?? this.conn.readable.getReader();
}
private readMessage(httpMessage: string): request<any> {
const lines = httpMessage.split("\n");
const firstLine = lines[0];
const dividingIndex = lines.indexOf("\r") ?? lines.indexOf("");
const rawHeaders = lines.slice(1, dividingIndex);
const [method, path, version] = firstLine.split(" ");
const requestHeaders = new headers({});
for (const header of rawHeaders) {
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 body = lines.slice(dividingIndex + 1).join("\n");
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()] ?? "";
response.headers.forEach((key, value) => {
responseMessage += "\n" + key + ": " + value;
});
responseMessage += "\n\n" + response.body;
this.conn.write(new TextEncoder().encode(responseMessage));
this.conn.shutdown();
this.closed = true;
}
private async read(): Promise<request<any> | undefined> {
let message = "";
const { done, value } = await this.reader.read();
if (done || this.closed) {
this.closed = true;
return undefined;
}
message += String.fromCharCode(...Object.values(<{
[key: string]: number
}>value));
const requestMessage = this.readMessage(message);
return requestMessage;
}
[Symbol.asyncIterator]() {
const httpConn = this;
return {
async next() {
if (httpConn.closed) return { done: true, value: undefined };
return {
done: false,
value: {
request: await httpConn.read(),
respondWith: (response: response<any>) => {
httpConn.handleResponse(response);
},
}
}
}
}
}
}
export default function serveHttp(Connection: tjs.Connection) {
return new HttpConn(Connection);
}

View File

@ -0,0 +1,64 @@
{
"100": "Continue",
"101": "Switching Protocols",
"102": "Processing",
"103": "Checkpoint",
"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",
"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 Long",
"415": "Unsupported Media Type",
"416": "Requested Range Not Satisfiable",
"417": "Expectation Failed",
"418": "I'm a teapot",
"421": "Unprocessable Entity",
"422": "Misdirected Request",
"423": "Locked",
"424": "Failed Dependency",
"426": "Upgrade Required",
"428": "Precondition Required",
"429": "Too Many Requests",
"431": "Request Header Fileds Too Large",
"451": "Unavailable For Legal Reasons",
"500": "Internal Server Error",
"501": "Not Implemented",
"502": "Bad Gateway",
"503": "Service Unavailable",
"504": "Gateway Timeout",
"505": "HTTP Version Not Supported",
"506": "Variant Also Negotiates",
"507": "Insufficient Storage",
"508": "Loop Detected",
"509": "Bandwidth Limit Exceeded",
"510": "Not Extended",
"511": "Network Authentication Required"
}

36
src/platform/txiki.ts Normal file
View File

@ -0,0 +1,36 @@
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 {
public router: router<T, K>;
constructor(router: router<T, K>) {
this.router = router;
}
async listen(port?: number): Promise<void> {
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") {
return;
}
conn.respondWith(await this.router.respond(conn.request));
}
}
}
async handleRequest(nativeRequest: request<any>): Promise<request<T>> {
return nativeRequest;
}
async handleResponse(response: response<K>): Promise<response<K>> {
return response;
}
}