Skip to content

Commit

Permalink
feat: native http server
Browse files Browse the repository at this point in the history
  • Loading branch information
cyperdark authored and KotRikD committed Feb 15, 2024
1 parent fd7f7ce commit 99d210a
Show file tree
Hide file tree
Showing 21 changed files with 659 additions and 801 deletions.
2 changes: 1 addition & 1 deletion packages/common/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as dotenv from 'dotenv';
import fs from 'fs';
import path from 'path';

import { wLogger } from './';
import { wLogger } from './utils/logger';

const configPath = path.join(path.dirname(process.execPath), 'tsosu.env');
if (!fs.existsSync(configPath)) {
Expand Down
3 changes: 2 additions & 1 deletion packages/find-process/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@tosu/find-process",
"version": "0.0.1",
"description": "",
"author": "cyperdark",
"main": "index.js",
"scripts": {
"build-artifact": "cargo-cp-artifact -nc find-process.node -- cargo build --message-format=json-render-diagnostics",
Expand All @@ -17,4 +18,4 @@
"devDependencies": {
"cargo-cp-artifact": "^0.1"
}
}
}
153 changes: 153 additions & 0 deletions packages/server/http/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { wLogger } from '@tosu/common';
import http, { IncomingMessage, ServerResponse } from 'http';

import { getContentType, sendJson } from '../utils/index';

interface ExtendedIncomingMessage extends IncomingMessage {
instanceManager: any;
query: { [key: string]: string };
params: { [key: string]: string };
getContentType: (text: string) => string;
sendJson: (
response: http.ServerResponse,
json: object | any[]
) => http.ServerResponse<http.IncomingMessage>;
}

type RequestHandler = (
req: ExtendedIncomingMessage,
res: http.ServerResponse,
next: () => void
) => void;

type RouteHandler = {
(req: ExtendedIncomingMessage, res: ServerResponse): void;
};

export class HttpServer {
private middlewares: RequestHandler[] = [];
server: http.Server;
private routes: {
[method: string]: [
{
path: string | RegExp;
handler: RouteHandler;
}
];
} = {};

constructor() {
// @ts-ignore
this.server = http.createServer(this.handleRequest.bind(this));
}

use(middleware: RequestHandler) {
this.middlewares.push(middleware);
}

route(
path: string | RegExp,
method:
| 'GET'
| 'POST'
| 'HEAD'
| 'PUT'
| 'DELETE'
| 'CONNECT'
| 'OPTIONS'
| 'TRACE'
| 'PATCH',
handler: RouteHandler
) {
// @ts-ignore
if (this.routes[method] == null) this.routes[method] = [];

// @ts-ignore
const find = this.routes[method].find((r) => r.path == path);
if (!find) this.routes[method].push({ path, handler });
}

private handleRequest(
req: ExtendedIncomingMessage,
res: http.ServerResponse
) {
const startTime = performance.now();
let index = 0;

res.on('finish', () => {
const finishTime = performance.now();
wLogger.debug(
`[${(finishTime - startTime).toFixed(2)}ms] ${req.method} ${
res.statusCode
} ${res.getHeader('content-type')} ${req.url}`
);
});

const next = () => {
if (index < this.middlewares.length) {
const middleware = this.middlewares[index++];
middleware(req, res, next);

return;
}

this.handleNext(req, res);
};

next();
}

private handleNext(req: ExtendedIncomingMessage, res: http.ServerResponse) {
const method = req.method || 'GET';
const hostname = req.headers.host; // Hostname
const url = req.url || '/'; // URL

const parsedURL = new URL(`http://${hostname}${req.url}`);

// parse query parameters
req.query = {};
req.params = {};

// add functions (thats probably shittiest way to do so, but i want to try it)
req.getContentType = getContentType;
req.sendJson = sendJson;

parsedURL.searchParams.forEach(
(value, key) => (req.query[key] = value)
);

const routes = this.routes[method] || [];
for (let i = 0; i < routes.length; i++) {
const route = routes[i];
let routeExists = false;

if (route.path instanceof RegExp && route.path.test(url)) {
routeExists = true;

// turn groups to route params
const array = Object.keys(route.path.exec(url)?.groups || {});
for (let g = 0; g < array.length; g++) {
const key = array[g];
const value = route.path.exec(url)?.groups?.[key];

if (key == null || value == null) continue;
req.params[key] = value;
}
} else if (typeof route.path == 'string')
routeExists = route.path == url;

if (!routeExists) continue;
return route.handler(req, res);
}

res.statusCode = 404;
res.end('Not Found');
return;
}

listen(port: number, hostname: string) {
this.server.listen(port, hostname, () => {
wLogger.info(`Web server started on http://${hostname}:${port}`);
});
}
}
3 changes: 3 additions & 0 deletions packages/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './utils/index';
export * from './http/index';
export * from './socket/index';
19 changes: 19 additions & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@tosu/server",
"version": "0.0.1",
"description": "",
"author": "cyperdark",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"prepare": "npm run build",
"build": "tsc"
},
"dependencies": {
"@tosu/common": "workspace:*",
"ws": "^8.16.0"
},
"devDependencies": {
"@types/ws": "^8.5.10"
}
}
38 changes: 38 additions & 0 deletions packages/server/socket/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { sleep, wLogger } from '@tosu/common';
import { config } from '@tosu/common/dist/config';
import WebSocket from 'ws';

export const WebSocketV1 = (instancesManager: any) => {
const wss = new WebSocket.Server({ noServer: true });
wss.on('connection', async (ws) => {
wLogger.debug('>>> ws: CONNECTED');
let isSocketConnected = true;

ws.on('close', function (reasonCode, description) {
isSocketConnected = false;
wLogger.debug('>>> ws: CLOSED');
});

ws.on('error', function (reasonCode, description) {
isSocketConnected = false;
wLogger.debug(`>>> ws: error: ${reasonCode} [${description}]`);
});

while (isSocketConnected) {
const osuInstances: any = Object.values(
instancesManager.osuInstances || {}
);
if (osuInstances.length < 1) {
await sleep(500);
continue;
}

ws.send(JSON.stringify(osuInstances[0].getState(instancesManager)));
await sleep(config.wsSendInterval);
}
});

return wss;
};

export { WebSocket };
27 changes: 27 additions & 0 deletions packages/server/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"lib": [
"ES2020"
],
"module": "commonjs",
"moduleResolution": "Node",
"allowJs": true,
"esModuleInterop": true,
"outDir": "dist",
"rootDir": "./",
"sourceMap": false,
"declaration": false,
"strict": true,
"noImplicitAny": false,
"target": "ES2020",
"strictPropertyInitialization": false,
"baseUrl": ".",
},
"exclude": [
"node_modules",
"dist",
],
"include": [
"**/*"
],
}
37 changes: 37 additions & 0 deletions packages/server/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import http from 'http';
import path from 'path';

export function getContentType(text: string) {
const extension = path.extname(text);

const contentType =
{
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.wav': 'audio/wav',
'.mp4': 'video/mp4',
'.woff': 'application/font-woff',
'.ttf': 'application/font-ttf',
'.eot': 'application/vnd.ms-fontobject',
'.otf': 'application/font-otf',
'.wasm': 'application/wasm'
}[extension] || 'application/octet-stream';

return contentType;
}

export function sendJson(response: http.ServerResponse, json: object | any[]) {
response.setHeader('Content-Type', 'application/json');

try {
return response.end(JSON.stringify(json));
} catch (error) {
return response.end(JSON.stringify({ error: 'Json parsing error' }));
}
}
19 changes: 8 additions & 11 deletions packages/tosu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,26 @@
"compile": "pnpm run genver && pnpm run ts:compile && pkg -t node18-win-x64 --output dist/tosu.exe --debug --config pkg.json --compress brotli dist/index.js && pnpm run ts:run src/postBuild.ts"
},
"dependencies": {
"@fastify/send": "^2.1.0",
"@fastify/static": "^6.12.0",
"@fastify/websocket": "^8.3.0",
"@kotrikd/rosu-pp": "^0.10.0",
"@tosu/common": "workspace:*",
"@tosu/find-process": "workspace:*",
"@tosu/server": "workspace:*",
"@tosu/updater": "workspace:*",
"@types/ws": "^8.5.10",
"@vercel/ncc": "^0.38.1",
"fastify": "^4.25.1",
"game-overlay": "workspace:*",
"genversion": "^3.1.1",
"osu-catch-stable": "^4.0.0",
"osu-classes": "^3.0.0",
"osu-mania-stable": "^5.0.0",
"osu-parsers": "^4.1.0",
"osu-standard-stable": "^5.0.0",
"osu-taiko-stable": "^5.0.0",
"pkg": "^5.8.0",
"resedit": "^2.0.0",
"semver": "^7.5.4",
"ts-node": "^10.9.1",
"tsprocess": "workspace:*",
"winston": "^3.8.2"
"tsprocess": "workspace:*"
},
"devDependencies": {
"@vercel/ncc": "^0.38.1",
"genversion": "^3.1.1",
"pkg": "^5.8.0",
"ts-node": "^10.9.1"
}
}
7 changes: 0 additions & 7 deletions packages/tosu/src/@types/fastify.d.ts

This file was deleted.

Loading

0 comments on commit 99d210a

Please sign in to comment.