diff --git a/lerna.json b/lerna.json index 6942539e..8e2bb6cb 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "useWorkspaces": true, - "version": "0.7.0" + "$schema": "node_modules/lerna/schemas/lerna-schema.json", + "useWorkspaces": true, + "version": "0.7.0" } diff --git a/package-lock.json b/package-lock.json index ccff8e9e..c15db662 100644 --- a/package-lock.json +++ b/package-lock.json @@ -694,6 +694,10 @@ "resolved": "packages/tslua-common", "link": true }, + "node_modules/@flying-dice/tslua-dcs-testapp": { + "resolved": "packages/tslua-dcs-testapp", + "link": true + }, "node_modules/@flying-dice/tslua-dcs-types": { "resolved": "packages/tslua-dcs-types", "link": true @@ -8811,6 +8815,21 @@ "node": ">=4" } }, + "node_modules/prettier": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", + "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.3.tgz", @@ -11760,6 +11779,190 @@ "typescript-to-lua": "^1.22.0" } }, + "packages/tslua-dcs-testapp": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@flying-dice/tslua-base64": "^0.7.0", + "@flying-dice/tslua-dcs-types": "^0.7.0", + "@flying-dice/tslua-http": "^0.7.0", + "@flying-dice/tslua-http-api": "^0.7.0" + }, + "devDependencies": { + "@biomejs/biome": "^1.4.0", + "lua-types": "^2.13.1", + "prettier": "^3.1.0", + "rimraf": "^5.0.5", + "typescript": "^5.2.2", + "typescript-to-lua": "^1.22.0", + "vitest": "^0.34.6" + } + }, + "packages/tslua-dcs-testapp/node_modules/@biomejs/biome": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.4.0.tgz", + "integrity": "sha512-/rDlao6ra38nhxo4IYCqWCzfTJcpMk4YHjSVBI9yN/ifdhnzSwirL25xDVH7G9hZdNhpF9g78FaPJhFa9DX0Cw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.4.0", + "@biomejs/cli-darwin-x64": "1.4.0", + "@biomejs/cli-linux-arm64": "1.4.0", + "@biomejs/cli-linux-x64": "1.4.0", + "@biomejs/cli-win32-arm64": "1.4.0", + "@biomejs/cli-win32-x64": "1.4.0" + } + }, + "packages/tslua-dcs-testapp/node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.4.0.tgz", + "integrity": "sha512-nBrtVRwr4IlTtxLOHwBwLv1sWvggf9/DnT5/ALIANJZOpoING6u8jHWipods69wK8kGa8Ld7iwHm3W5BrJJFFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.*" + } + }, + "packages/tslua-dcs-testapp/node_modules/@biomejs/cli-darwin-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.4.0.tgz", + "integrity": "sha512-nny0VgOj3ksUGzU5GblgtQEvrAZFgFe1IJBoYOP978OQdDrg7BpS+GX5udfof87Dl4ZlHPRBU951ceHOxF7BTg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.*" + } + }, + "packages/tslua-dcs-testapp/node_modules/@biomejs/cli-linux-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.4.0.tgz", + "integrity": "sha512-gyLkT/Yh9xfW1T9yjQs/2txkCeG0e+LRs0adLugMwN0ptcNTRyusBvUoiHnpB+9rS6hWu9ZCedGMNmKQ8v2GSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.*" + } + }, + "packages/tslua-dcs-testapp/node_modules/@biomejs/cli-linux-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.4.0.tgz", + "integrity": "sha512-LIxTuU2zSbIHM9XDYjQphJ5UU8h2eS7yR8uIvGYSba7Qt9AKqfbenyVJTsVnoj1CXxxgKNVSc/wVmlOlGz5DBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.*" + } + }, + "packages/tslua-dcs-testapp/node_modules/@biomejs/cli-win32-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.4.0.tgz", + "integrity": "sha512-U2jT1/0wZLJIRqnU8qHAfi/A/+yUwlL3sYJgqs+wO0BbR22WGQZlj03u5FdpEoyLXdsLv1pbeIcjNp+V0NYXWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.*" + } + }, + "packages/tslua-dcs-testapp/node_modules/@biomejs/cli-win32-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.4.0.tgz", + "integrity": "sha512-gN6DgyyBxIwoCovAUFJHFWVallb0cLosayDRtNyxU3MDv/atZxSXOWQezfVKBIbgmFPxYWJObd+awvbPYXwwww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.*" + } + }, + "packages/tslua-dcs-testapp/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "packages/tslua-dcs-testapp/node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "packages/tslua-dcs-testapp/node_modules/typescript-to-lua": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/typescript-to-lua/-/typescript-to-lua-1.22.0.tgz", + "integrity": "sha512-6rMbWj/NsNQ/phNFVtMZj5LdHucUPdrmKP4VFsynM7SGtm2zR2RMLnZcJM3unrDb1VCAR6FnL7GJoKp77yM5qw==", + "dev": true, + "dependencies": { + "@typescript-to-lua/language-extensions": "1.19.0", + "enhanced-resolve": "^5.8.2", + "picomatch": "^2.3.1", + "resolve": "^1.15.1", + "source-map": "^0.7.3" + }, + "bin": { + "tstl": "dist/tstl.js" + }, + "engines": { + "node": ">=16.10.0" + }, + "peerDependencies": { + "typescript": "5.2.2" + } + }, "packages/tslua-dcs-types": { "name": "@flying-dice/tslua-dcs-types", "version": "0.7.0", diff --git a/packages/tslua-dcs-testapp/package.json b/packages/tslua-dcs-testapp/package.json new file mode 100644 index 00000000..03450042 --- /dev/null +++ b/packages/tslua-dcs-testapp/package.json @@ -0,0 +1,27 @@ +{ + "name": "@flying-dice/tslua-dcs-testapp", + "version": "1.0.0", + "description": "Test app for DCS World and tslua-http", + "private": true, + "scripts": { + "build": "rimraf dist && tstl -p tsconfig.tstl.json" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@flying-dice/tslua-base64": "^0.7.0", + "@flying-dice/tslua-dcs-types": "^0.7.0", + "@flying-dice/tslua-http-api": "^0.7.0", + "@flying-dice/tslua-http": "^0.7.0" + }, + "devDependencies": { + "@biomejs/biome": "^1.4.0", + "lua-types": "^2.13.1", + "prettier": "^3.1.0", + "rimraf": "^5.0.5", + "typescript": "^5.2.2", + "typescript-to-lua": "^1.22.0", + "vitest": "^0.34.6" + } +} diff --git a/packages/tslua-dcs-testapp/src/index.ts b/packages/tslua-dcs-testapp/src/index.ts new file mode 100644 index 00000000..7bd552cf --- /dev/null +++ b/packages/tslua-dcs-testapp/src/index.ts @@ -0,0 +1,44 @@ +_G.print = env.info; + +import { HttpServer } from "@flying-dice/tslua-http"; + +declare global { + // biome-ignore lint/style/useConst: It is re-assigned :/ + let functionId: number; + // biome-ignore lint/style/useConst: It is re-assigned :/ + let app: HttpServer; +} + +if (app) { + env.info("Closing existing app"); + app.close(); +} + +app = new HttpServer("127.0.0.1", 1631, (req, res) => { + res.body = "Hello World!"; + res.status = 200; + return res; +}); + +env.info("Starting app"); + +if (functionId) { + env.info(`Removing existing function ${functionId}`); + timer.removeFunction(functionId); +} + +functionId = timer.scheduleFunction( + () => { + try { + app.acceptNextClient(); + } catch (e) { + env.error(`Error accepting client: ${e}`); + } + + return (timer.getTime() as number) + 0.1; + }, + [], + (timer.getTime() as number) + 0.1, +); + +env.info(`Started server loop with functionId ${functionId}`); diff --git a/packages/tslua-dcs-testapp/tsconfig.json b/packages/tslua-dcs-testapp/tsconfig.json new file mode 100644 index 00000000..128892f4 --- /dev/null +++ b/packages/tslua-dcs-testapp/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES6", + "lib": ["ES6"], + "moduleResolution": "Node", + "types": ["lua-types/5.1", "@flying-dice/tslua-dcs-types"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist", + "resolveJsonModule": true + } +} diff --git a/packages/tslua-dcs-testapp/tsconfig.tstl.json b/packages/tslua-dcs-testapp/tsconfig.tstl.json new file mode 100644 index 00000000..935353da --- /dev/null +++ b/packages/tslua-dcs-testapp/tsconfig.tstl.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://raw.githubusercontent.com/TypeScriptToLua/TypeScriptToLua/master/tsconfig-schema.json", + "extends": "./tsconfig.json", + "tstl": { + "sourceMapTraceback": true, + "luaBundleEntry": "src/index.ts", + "luaBundle": "tslua-testapp.lua", + "noResolvePaths": ["socket"] + } +} diff --git a/packages/tslua-http/src/server.ts b/packages/tslua-http/src/server.ts index eac5eda1..ffc37e81 100644 --- a/packages/tslua-http/src/server.ts +++ b/packages/tslua-http/src/server.ts @@ -32,6 +32,8 @@ export class HttpServer { protected logger: Logger; + private currentClient: TCP | undefined; + /** * Creates an instance of a HTTP server. * @param {string} bindAddress - The IP address or hostname the server will bind to. @@ -49,6 +51,14 @@ export class HttpServer { this.server.settimeout(0); } + close() { + if (this.currentClient) { + this.currentClient.close(); + this.currentClient = undefined; + } + this.server.close(); + } + /** * Accepts the next client connection. * When a client connects, it handles the client's request. @@ -58,46 +68,73 @@ export class HttpServer { * httpServer.acceptNextClient(); */ acceptNextClient() { - const client = this.server.accept(); - if (client) { - this.handleClient(client); + if (this.currentClient) return; + this.currentClient = this.server.accept(); + if (this.currentClient) { + this.logger.debug("Accepted client"); + this.handleClient(); } } /** * Handles the client's request. * Reads the request from the client, processes it, and sends back a response. - * @param {TCP} client - The client connection to handle. * @private * * @example * // Internally used to handle a client's request - * this.handleClient(client); + * this.handleClient(); */ - private handleClient(client: TCP): void { - const requestHeadLines: string[] = []; - let lastReceived; - do { - const received = client.receive("*l"); - if (typeof received === "string") { - requestHeadLines.push(received); + private handleClient(): void { + try { + if (!this.currentClient) return; + const requestHeadLines: string[] = []; + let lastReceived; + this.logger.debug("Handling client"); + this.currentClient.settimeout(0); + + do { + const received = this.currentClient.receive("*l"); + if (typeof received === "string") { + requestHeadLines.push(received); + } + lastReceived = received; + if (received === undefined) { + throw new Error("Client returned unexpected value, terminating"); + } + } while (lastReceived !== ""); + + this.currentClient.settimeout(0); + this.logger.debug("Received request head"); + const request = readRequestHead(requestHeadLines.join(CRLF)); + + const contentLength = request.headers["Content-Length"]; + const contentLengthNum = tonumber(contentLength); + if (contentLengthNum && contentLengthNum > 0) { + this.logger.debug( + `Fetching request body ${request.headers["Content-Length"]}`, + ); + + request.body = this.currentClient.receive(contentLengthNum) as string; } - lastReceived = received; - } while (lastReceived !== ""); - - const request = readRequestHead(requestHeadLines.join(CRLF)); - if (request.headers["Content-Length"]) { - client.settimeout(5); - request.body = client.receive( - +request.headers["Content-Length"], - ) as string; + + this.logger.debug("Handling request"); + const response: HttpResponse = this.handler(request, { + status: 404, + headers: {}, + }); + + this.logger.debug("Assembling response"); + const responseString = assembleResponseString(response); + + this.logger.debug("Sending response"); + this.currentClient.send(responseString); + } catch (e) { + this.logger.error(`Error handling client: ${e}`); + } finally { + this.logger.debug("Closing client"); + this.currentClient?.close(); + this.currentClient = undefined; } - const response: HttpResponse = this.handler(request, { - status: 404, - headers: {}, - }); - const responseString = assembleResponseString(response); - client.send(responseString); - client.close(); } }