支持 llrt 运行时

This commit is contained in:
zixu 2024-08-28 19:57:20 +08:00
parent 2477caf183
commit ad6d3b4649
6 changed files with 116 additions and 64 deletions

View File

@ -3,8 +3,15 @@ import { mimeTypes } from "./server/mime_type";
import type { cMiddleware } from "./server/req_res"; import type { cMiddleware } from "./server/req_res";
import { SimpleHttpServer } from "./server/server"; import { SimpleHttpServer } from "./server/server";
import { path_join, readFile, stat } from "./interface"; import { path_join, readFile, stat } from "./interface";
if (global.tjs) { let release_name = global.tjs ? "tjs" : globalThis?.process?.release?.name;
if (release_name === "tjs") {
import("./server/tjs"); import("./server/tjs");
} else if (release_name === "node" || release_name === "llrt") {
import("./server/node");
}
if (release_name === "llrt") {
import("./server/llrt");
} }
const ROOT_DIR = "dist"; // 静态文件目录 const ROOT_DIR = "dist"; // 静态文件目录
@ -15,12 +22,12 @@ const logMiddleware: cMiddleware = async (req, res, next) => {
console.log(`[${t2 - t1}ms] ${req.url.split("?")[0]}`); console.log(`[${t2 - t1}ms] ${req.url.split("?")[0]}`);
return r; return r;
}; };
const reqs: Promise<unknown>[] = [];
const staticFileMiddleware: cMiddleware = async function (req, res, next) { const staticFileMiddleware: cMiddleware = async function (req, res, next) {
let newRes: Response; let newRes: Response;
if (req.method === "GET") { if (req.method === "GET") {
const filePath = path_join(ROOT_DIR, req.url === "/" ? "index.html" : req.url); const url = new URL(req.url);
const filePath = path_join(ROOT_DIR, url.pathname === "/" ? "index.html" : url.pathname);
try { try {
const stats = await stat(filePath); const stats = await stat(filePath);
@ -90,9 +97,9 @@ const corsMiddleware: cMiddleware = async (req, res, next) => {
} }
}; };
const fontApiMiddleware: cMiddleware = async (req, res, next) => { const fontApiMiddleware: cMiddleware = async (req, res, next) => {
if (!req.url.startsWith("/api")) return next(req, res);
// 创建一个新的 URL 对象(需要一个完整的 URL必须包含协议和主机 // 创建一个新的 URL 对象(需要一个完整的 URL必须包含协议和主机
const url = new URL(req.url, "http://test.com"); const url = new URL(req.url, "http://test.com");
if (!url.pathname.startsWith("/api")) return next(req, res);
const params = new URLSearchParams(url.search); const params = new URLSearchParams(url.search);
const font = params.get("font") || ""; const font = params.get("font") || "";
const text = params.get("text") || ""; const text = params.get("text") || "";

11
backend/server/node.ts Normal file
View File

@ -0,0 +1,11 @@
import { implInterface } from "../interface";
import { stat, readFile } from "fs/promises";
implInterface({
async stat(path) {
const r = await stat(path);
return r;
},
readFile(path) {
return readFile(path);
},
});

View File

@ -1,5 +1,4 @@
import { cMiddleware, cRequest, cResponse, type cNext } from "./req_res"; import { cMiddleware, cRequest, cResponse, type cNext } from "./req_res";
// import { createTcpServer } from "./tcp_server";
// 配置 // 配置
// 路由器类 // 路由器类
export class cRouter { export class cRouter {
@ -28,18 +27,22 @@ export class SimpleHttpServer {
private router: cRouter = new cRouter(); private router: cRouter = new cRouter();
constructor(options: { port: number; hostname?: string }) { constructor(options: { port: number; hostname?: string }) {
console.log(`Server is listening on port ${options.port}`); let release_name = global.tjs ? "tjs" : globalThis?.process?.release?.name;
console.log("[release.name]", release_name);
if (global.tjs) { if (global.tjs) {
this.tjsServer(options); this.tjsServer(options);
return this; return this;
} }
// const server = createTcpServer((req, res) => { if (release_name === "llrt" || release_name == "node") {
// const r = this.router.handle(req, res); import("./tcp_server").then((m) => {
// return r; const server = m.createTcpServer((socket) => {
// }); connectionHandle(socket, (req, res) => this.router.handle(req, res));
// server.listen(options.port, options.hostname, () => { });
// console.log(`Server is listening on port ${options.port}`); server.listen(options.port, options.hostname, () => {
// }); console.log(`Server is listening on port ${options.port}`);
});
});
}
} }
private async tjsServer(options: { port: number; hostname?: string }) { private async tjsServer(options: { port: number; hostname?: string }) {
const listener = (await global.tjs.listen( const listener = (await global.tjs.listen(
@ -48,7 +51,7 @@ export class SimpleHttpServer {
options.port, options.port,
{}, {},
)) as tjs.Listener; )) as tjs.Listener;
// listener.localAddress; console.log(`Server is listening on port ${options.port}`);
for await (const connection of listener) { for await (const connection of listener) {
connectionHandle(connection, this.router.handle.bind(this.router)); connectionHandle(connection, this.router.handle.bind(this.router));
} }
@ -62,14 +65,21 @@ const decoder = new TextDecoder("utf-8");
const encoder = new TextEncoder(); const encoder = new TextEncoder();
// 请求头终止符 // 请求头终止符
const target = encoder.encode("\r\n\r\n"); const target = encoder.encode("\r\n\r\n");
async function connectionHandle(connection: tjs.Connection, handle: cNext) { async function connectionHandle(
connection: {
readable: ReadableStream<Uint8Array>;
writable: WritableStream<Uint8Array>;
close: () => void;
},
handle: cNext,
) {
const { header, body } = await createStreamAfterTarget(connection.readable, target); const { header, body } = await createStreamAfterTarget(connection.readable, target);
if (!header) { if (!header) {
return; return;
} }
const httpHeaderText = decoder.decode(header); const httpHeaderText = decoder.decode(header);
const httpHeader = parseHttpRequest(httpHeaderText); const httpHeader = parseHttpRequest(httpHeaderText);
const rawReq = new Request(httpHeader.url, { const rawReq = new Request("http://" + httpHeader.headers["Host"] + httpHeader.url, {
method: httpHeader.method, method: httpHeader.method,
body: httpHeader.method === "GET" || httpHeader.method === "HEAD" ? undefined : body, body: httpHeader.method === "GET" || httpHeader.method === "HEAD" ? undefined : body,
headers: httpHeader.headers, headers: httpHeader.headers,
@ -77,7 +87,6 @@ async function connectionHandle(connection: tjs.Connection, handle: cNext) {
const rawRes = new Response(); const rawRes = new Response();
const { req, res } = await handle(rawReq, rawRes); const { req, res } = await handle(rawReq, rawRes);
const resWriter = connection.writable.getWriter(); const resWriter = connection.writable.getWriter();
let headerText: string[] = []; let headerText: string[] = [];
res.headers.forEach((value, key) => { res.headers.forEach((value, key) => {
@ -86,12 +95,23 @@ async function connectionHandle(connection: tjs.Connection, handle: cNext) {
const resHeaertText = `HTTP/1.1 ${res.status} OK\r\n${headerText.join("\r\n")}\r\n\r\n`; const resHeaertText = `HTTP/1.1 ${res.status} OK\r\n${headerText.join("\r\n")}\r\n\r\n`;
await resWriter.write(encoder.encode(resHeaertText)); await resWriter.write(encoder.encode(resHeaertText));
if (res.body) { if (res.body) {
// node 运行时
// 释放写入器的锁定
resWriter.releaseLock();
console.log("[connection.writable.locked]", connection.writable.locked);
// https://github.com/saghul/txiki.js/issues/646 // https://github.com/saghul/txiki.js/issues/646
await res.body?.pipeTo(connection.writable); await res.body?.pipeTo(connection.writable);
} else { } else {
// @ts-expect-error
if (res._bodyInit) {
// tjs 运行时
// @ts-expect-error // @ts-expect-error
await resWriter.write(res._bodyInit); await resWriter.write(res._bodyInit);
// await resWriter.write(encoder.encode(r)); } else {
// llrt 运行时
const buffer = new Uint8Array(await (await res.blob()).arrayBuffer());
await resWriter.write(buffer);
}
} }
if (!resWriter.closed) { if (!resWriter.closed) {
await resWriter.close(); await resWriter.close();

View File

@ -1,54 +1,58 @@
import { createServer } from "net"; import { createServer } from "net";
import { cNext, cResponse, parseRequest } from "./req_res";
// 状态码文本映射
const statusCodes: Record<number, string> = {
200: "OK",
404: "Not Found",
405: "Method Not Allowed",
500: "Internal Server Error",
};
// 创建 TCP 服务器
export function createTcpServer(handle: cNext) {
const server = createServer((socket) => {
let requestBuffer = "";
socket.on("data", async (data) => {
requestBuffer += data.toString();
// 如果收到请求头的结束标志,则处理请求 // 创建 TCP 服务器
if (requestBuffer.includes("\r\n\r\n")) { export function createTcpServer(
const req = parseRequest(requestBuffer, socket); onSocket: (socket: {
const res: cResponse = { statusCode: 200, headers: {}, body: "" }; readable: ReadableStream<Uint8Array>;
try { writable: WritableStream<Uint8Array>;
// 处理请求 close: () => void;
const { res: newRes } = await handle(req, res); }) => void,
// 发送响应 ) {
socket.write(`HTTP/1.1 ${newRes.statusCode} ${statusCodes[newRes.statusCode]}\r\n`); const server = createServer((socket) => {
Object.entries(newRes.headers).forEach(([key, value]) => { const readable = new ReadableStream<Uint8Array>({
socket.write(`${key}: ${value}\r\n`); start(controller) {
socket.on("data", (chunk) => {
controller.enqueue(new Uint8Array(chunk));
}); });
socket.write("\r\n");
if (typeof newRes.body === "string") { socket.on("end", () => {
socket.write(newRes.body); controller.close();
} else {
socket.write(newRes.body);
}
socket.end();
} catch (error) {
console.error("[err]", error);
socket.write("HTTP/1.1 500 Internal Server Error\r\n");
socket.write("Content-Type: text/plain\r\n");
socket.write("\r\n");
socket.write("500 Internal Server Error");
socket.end();
} finally {
requestBuffer = ""; // 重置请求缓冲区
}
}
}); });
socket.on("error", (err) => { socket.on("error", (err) => {
console.error("Socket error:", err); controller.error(err);
}); });
},
cancel() {
socket.destroy();
},
});
// 创建 WritableStream
const writable = new WritableStream<Uint8Array>({
write(chunk) {
return new Promise((resolve, reject) => {
socket.write(chunk, (err) => {
if (err) reject(err);
else resolve();
});
});
},
close() {
socket.end();
},
abort(reason) {
socket.destroy(reason);
},
});
// 实现 close 方法
function close() {
socket.end();
socket.destroy();
}
onSocket({ readable, writable, close });
}); });
return server; return server;
} }

View File

@ -13,7 +13,8 @@
}, },
"dependencies": { "dependencies": {
"fonteditor-core": "^2.4.1", "fonteditor-core": "^2.4.1",
"solid-js": "^1.8.20" "solid-js": "^1.8.20",
"web-streams-polyfill": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@txikijs/types": "^24.6.0", "@txikijs/types": "^24.6.0",

9
pnpm-lock.yaml generated
View File

@ -14,6 +14,9 @@ importers:
solid-js: solid-js:
specifier: ^1.8.20 specifier: ^1.8.20
version: 1.8.21 version: 1.8.21
web-streams-polyfill:
specifier: ^4.0.0
version: 4.0.0
devDependencies: devDependencies:
'@txikijs/types': '@txikijs/types':
specifier: ^24.6.0 specifier: ^24.6.0
@ -1162,6 +1165,10 @@ packages:
vite: vite:
optional: true optional: true
web-streams-polyfill@4.0.0:
resolution: {integrity: sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw==}
engines: {node: '>= 8'}
webidl-conversions@4.0.2: webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
@ -2157,6 +2164,8 @@ snapshots:
optionalDependencies: optionalDependencies:
vite: 5.4.1(@types/node@22.4.0) vite: 5.4.1(@types/node@22.4.0)
web-streams-polyfill@4.0.0: {}
webidl-conversions@4.0.2: {} webidl-conversions@4.0.2: {}
whatwg-url@7.1.0: whatwg-url@7.1.0: