Add Txiki.js Adapter.

This commit is contained in:
186526 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

@ -24,6 +24,7 @@
"@types/node": "^18.0.0",
"@webpack-cli/generators": "^2.5.0",
"axios": "^0.27.2",
"bluebird": "^3.7.2",
"jest": "^28.1.2",
"prettier": "^2.7.1",
"ts-jest": "^28.0.5",
@ -35,11 +36,12 @@
},
"sideEffects": false,
"scripts": {
"build": "yarn clean && yarn build:node && yarn build:serviceworker && yarn build:cfworker && yarn build:deno",
"build": "yarn clean && yarn build:node && yarn build:serviceworker && yarn build:cfworker && yarn build:deno && yarn build:txiki",
"build:node": "BUILD_TARGET=node webpack",
"build:serviceworker": "BUILD_TARGET=serviceworker webpack",
"build:cfworker": "BUILD_TARGET=cfworker webpack",
"build:deno": "BUILD_TARGET=deno webpack",
"build:txiki": "BUILD_TARGET=txiki webpack",
"watch": "webpack --watch",
"clean": "rm -rf ./dist",
"demo": "env NODE_ENV=development yarn build:node && node ./dist/main.node.js",
@ -51,4 +53,4 @@
"node": ">=14.0.0"
},
"type": "module"
}
}

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;
}
}

View File

@ -29,6 +29,6 @@
"declaration": true
},
"exclude": ["node_modules"],
"include": ["index.ts", "src/**/*.ts", "demo/**/*.ts", "types/deno.d.ts"],
"include": ["index.ts", "src/**/*.ts", "demo/**/*.ts", "types/*.d.ts"],
// "esm": true
}

53
types/txiki.d.ts vendored Normal file
View File

@ -0,0 +1,53 @@
type Transport = "tcp" | "udp" | "pipe";
interface ListenOptions {
backlog?: number
// Disables dual stack mode.
ipv6Only?: boolean;
// Used on UDP only. Enable address reusing (when binding). What that means is that multiple threads or processes can bind to the same address without error (provided they all set the flag) but only the last one to bind will receive any traffic, in effect "stealing" the port from the previous listener.
reuseAddr?: boolean;
}
declare namespace tjs {
const versions: { curl: string; quickjs: string; tjs: string; uv: string; wasm3: string };
export interface Address {
readonly family: number;
readonly flowinfo?: number;
readonly ip: string;
readonly port: number;
readonly scopeId?: number;
}
export interface Connection {
readonly localAddress: Address;
readonly readable: ReadableStream<Uint8Array>;
readonly remoteAddress: Address;
readonly writeable: WritableStream<Uint8Array>
close(): void
read(buf: Uint8Array): Promise<number>
write(buf: Uint8Array): Promise<number>
setKeepAlive(enable?: boolean): void
setNoDelay(enable?: boolean): void
shutdown(): void
}
export interface Listener extends AsyncIterable<Connection> {
readonly localAddress: Address;
accept(): Promise<Connection>;
close(): void;
[Symbol.asyncIterator](): AsyncIterableIterator<Connection>;
}
export function listen(
transport: Transport,
host: string,
port?: string | number,
options?: ListenOptions
): Promise<Listener>
}

View File

@ -8,6 +8,8 @@ const __dirname = path.dirname(__filename);
const isProduction = process.env.NODE_ENV == "production";
import webpack from "webpack";
const config = {
output: {
path: path.resolve(__dirname, "dist"),
@ -35,7 +37,8 @@ const config = {
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", "..."],
fallback: {
"http": false
"http": false,
"async_hooks": false,
}
},
experiments: {
@ -69,6 +72,15 @@ export default () => {
config.target = "webworker";
config.output.filename = "main.cfworker.js";
break;
case "txiki":
config.mode = "production";
config.target = "es2021";
config.output.filename = "main.txiki.js";
config.plugins.push(
new webpack.ProvidePlugin({
'Promise': 'bluebird'
})
)
default:
config.target = "es6";
}

View File

@ -1480,6 +1480,11 @@ bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"