From ad6d3b4649b85e69ddcb95be07f1e887222c08e0 Mon Sep 17 00:00:00 2001 From: zixu Date: Wed, 28 Aug 2024 19:57:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=20llrt=20=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app.ts | 15 ++++-- backend/server/node.ts | 11 +++++ backend/server/server.ts | 50 ++++++++++++++------ backend/server/tcp_server.ts | 92 +++++++++++++++++++----------------- package.json | 3 +- pnpm-lock.yaml | 9 ++++ 6 files changed, 116 insertions(+), 64 deletions(-) create mode 100644 backend/server/node.ts diff --git a/backend/app.ts b/backend/app.ts index 565258f..73de1c6 100644 --- a/backend/app.ts +++ b/backend/app.ts @@ -3,8 +3,15 @@ import { mimeTypes } from "./server/mime_type"; import type { cMiddleware } from "./server/req_res"; import { SimpleHttpServer } from "./server/server"; 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"); +} else if (release_name === "node" || release_name === "llrt") { + import("./server/node"); +} +if (release_name === "llrt") { + import("./server/llrt"); } const ROOT_DIR = "dist"; // 静态文件目录 @@ -15,12 +22,12 @@ const logMiddleware: cMiddleware = async (req, res, next) => { console.log(`[${t2 - t1}ms] ${req.url.split("?")[0]}`); return r; }; -const reqs: Promise[] = []; const staticFileMiddleware: cMiddleware = async function (req, res, next) { let newRes: Response; 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 { const stats = await stat(filePath); @@ -90,9 +97,9 @@ const corsMiddleware: cMiddleware = async (req, res, next) => { } }; const fontApiMiddleware: cMiddleware = async (req, res, next) => { - if (!req.url.startsWith("/api")) return next(req, res); // 创建一个新的 URL 对象(需要一个完整的 URL,必须包含协议和主机) 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 font = params.get("font") || ""; const text = params.get("text") || ""; diff --git a/backend/server/node.ts b/backend/server/node.ts new file mode 100644 index 0000000..ee70731 --- /dev/null +++ b/backend/server/node.ts @@ -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); + }, +}); diff --git a/backend/server/server.ts b/backend/server/server.ts index 8b14ae5..0f52e56 100644 --- a/backend/server/server.ts +++ b/backend/server/server.ts @@ -1,5 +1,4 @@ import { cMiddleware, cRequest, cResponse, type cNext } from "./req_res"; -// import { createTcpServer } from "./tcp_server"; // 配置 // 路由器类 export class cRouter { @@ -28,18 +27,22 @@ export class SimpleHttpServer { private router: cRouter = new cRouter(); 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) { this.tjsServer(options); return this; } - // const server = createTcpServer((req, res) => { - // const r = this.router.handle(req, res); - // return r; - // }); - // server.listen(options.port, options.hostname, () => { - // console.log(`Server is listening on port ${options.port}`); - // }); + if (release_name === "llrt" || release_name == "node") { + import("./tcp_server").then((m) => { + 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}`); + }); + }); + } } private async tjsServer(options: { port: number; hostname?: string }) { const listener = (await global.tjs.listen( @@ -48,7 +51,7 @@ export class SimpleHttpServer { options.port, {}, )) as tjs.Listener; - // listener.localAddress; + console.log(`Server is listening on port ${options.port}`); for await (const connection of listener) { connectionHandle(connection, this.router.handle.bind(this.router)); } @@ -62,14 +65,21 @@ const decoder = new TextDecoder("utf-8"); const encoder = new TextEncoder(); // 请求头终止符 const target = encoder.encode("\r\n\r\n"); -async function connectionHandle(connection: tjs.Connection, handle: cNext) { +async function connectionHandle( + connection: { + readable: ReadableStream; + writable: WritableStream; + close: () => void; + }, + handle: cNext, +) { const { header, body } = await createStreamAfterTarget(connection.readable, target); if (!header) { return; } const httpHeaderText = decoder.decode(header); const httpHeader = parseHttpRequest(httpHeaderText); - const rawReq = new Request(httpHeader.url, { + const rawReq = new Request("http://" + httpHeader.headers["Host"] + httpHeader.url, { method: httpHeader.method, body: httpHeader.method === "GET" || httpHeader.method === "HEAD" ? undefined : body, headers: httpHeader.headers, @@ -77,7 +87,6 @@ async function connectionHandle(connection: tjs.Connection, handle: cNext) { const rawRes = new Response(); const { req, res } = await handle(rawReq, rawRes); - const resWriter = connection.writable.getWriter(); let headerText: string[] = []; 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`; await resWriter.write(encoder.encode(resHeaertText)); if (res.body) { + // node 运行时 + // 释放写入器的锁定 + resWriter.releaseLock(); + console.log("[connection.writable.locked]", connection.writable.locked); // https://github.com/saghul/txiki.js/issues/646 await res.body?.pipeTo(connection.writable); } else { // @ts-expect-error - await resWriter.write(res._bodyInit); - // await resWriter.write(encoder.encode(r)); + if (res._bodyInit) { + // tjs 运行时 + // @ts-expect-error + await resWriter.write(res._bodyInit); + } else { + // llrt 运行时 + const buffer = new Uint8Array(await (await res.blob()).arrayBuffer()); + await resWriter.write(buffer); + } } if (!resWriter.closed) { await resWriter.close(); diff --git a/backend/server/tcp_server.ts b/backend/server/tcp_server.ts index dcb317d..edc62f5 100644 --- a/backend/server/tcp_server.ts +++ b/backend/server/tcp_server.ts @@ -1,54 +1,58 @@ import { createServer } from "net"; -import { cNext, cResponse, parseRequest } from "./req_res"; -// 状态码文本映射 -const statusCodes: Record = { - 200: "OK", - 404: "Not Found", - 405: "Method Not Allowed", - 500: "Internal Server Error", -}; + // 创建 TCP 服务器 -export function createTcpServer(handle: cNext) { +export function createTcpServer( + onSocket: (socket: { + readable: ReadableStream; + writable: WritableStream; + close: () => void; + }) => void, +) { const server = createServer((socket) => { - let requestBuffer = ""; - socket.on("data", async (data) => { - requestBuffer += data.toString(); + const readable = new ReadableStream({ + start(controller) { + socket.on("data", (chunk) => { + controller.enqueue(new Uint8Array(chunk)); + }); - // 如果收到请求头的结束标志,则处理请求 - if (requestBuffer.includes("\r\n\r\n")) { - const req = parseRequest(requestBuffer, socket); - const res: cResponse = { statusCode: 200, headers: {}, body: "" }; - try { - // 处理请求 - const { res: newRes } = await handle(req, res); - // 发送响应 - socket.write(`HTTP/1.1 ${newRes.statusCode} ${statusCodes[newRes.statusCode]}\r\n`); - Object.entries(newRes.headers).forEach(([key, value]) => { - socket.write(`${key}: ${value}\r\n`); + socket.on("end", () => { + controller.close(); + }); + + socket.on("error", (err) => { + controller.error(err); + }); + }, + cancel() { + socket.destroy(); + }, + }); + + // 创建 WritableStream + const writable = new WritableStream({ + write(chunk) { + return new Promise((resolve, reject) => { + socket.write(chunk, (err) => { + if (err) reject(err); + else resolve(); }); - socket.write("\r\n"); - if (typeof newRes.body === "string") { - socket.write(newRes.body); - } 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 = ""; // 重置请求缓冲区 - } - } + }); + }, + close() { + socket.end(); + }, + abort(reason) { + socket.destroy(reason); + }, }); - socket.on("error", (err) => { - console.error("Socket error:", err); - }); + // 实现 close 方法 + function close() { + socket.end(); + socket.destroy(); + } + + onSocket({ readable, writable, close }); }); return server; } diff --git a/package.json b/package.json index a53a540..0007d63 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ }, "dependencies": { "fonteditor-core": "^2.4.1", - "solid-js": "^1.8.20" + "solid-js": "^1.8.20", + "web-streams-polyfill": "^4.0.0" }, "devDependencies": { "@txikijs/types": "^24.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4776d9c..474811a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: solid-js: specifier: ^1.8.20 version: 1.8.21 + web-streams-polyfill: + specifier: ^4.0.0 + version: 4.0.0 devDependencies: '@txikijs/types': specifier: ^24.6.0 @@ -1162,6 +1165,10 @@ packages: vite: optional: true + web-streams-polyfill@4.0.0: + resolution: {integrity: sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw==} + engines: {node: '>= 8'} + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -2157,6 +2164,8 @@ snapshots: optionalDependencies: vite: 5.4.1(@types/node@22.4.0) + web-streams-polyfill@4.0.0: {} + webidl-conversions@4.0.2: {} whatwg-url@7.1.0: