Update WebSocket server with favicon assets and additional dependencies
* Added favicon assets including various sizes and a manifest file for improved branding * Updated package.json to include new type definitions and dependencies for body-parser, cors, lru-cache, moment, multer, on-headers, response-time, and serve-favicon * Enhanced HttpServer class to utilize the favicon and improved middleware configuration for handling requests
This commit is contained in:
6
Web/WebSocket/websocket/server/assets/favicon/about.txt
Normal file
6
Web/WebSocket/websocket/server/assets/favicon/about.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
This favicon was generated using the following graphics from Twitter Twemoji:
|
||||||
|
|
||||||
|
- Graphics Title: 1f916.svg
|
||||||
|
- Graphics Author: Copyright 2020 Twitter, Inc and other contributors (https://github.com/twitter/twemoji)
|
||||||
|
- Graphics Source: https://github.com/twitter/twemoji/blob/master/assets/svg/1f916.svg
|
||||||
|
- Graphics License: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
Web/WebSocket/websocket/server/assets/favicon/favicon-16x16.png
Normal file
BIN
Web/WebSocket/websocket/server/assets/favicon/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 680 B |
BIN
Web/WebSocket/websocket/server/assets/favicon/favicon-32x32.png
Normal file
BIN
Web/WebSocket/websocket/server/assets/favicon/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Web/WebSocket/websocket/server/assets/favicon/favicon.ico
Normal file
BIN
Web/WebSocket/websocket/server/assets/favicon/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1 @@
|
|||||||
|
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||||
@@ -20,8 +20,15 @@
|
|||||||
"@eslint/js": "9.36.0",
|
"@eslint/js": "9.36.0",
|
||||||
"@eslint/json": "0.13.2",
|
"@eslint/json": "0.13.2",
|
||||||
"@eslint/markdown": "7.3.0",
|
"@eslint/markdown": "7.3.0",
|
||||||
|
"@types/body-parser": "1.19.6",
|
||||||
|
"@types/cors": "2.8.19",
|
||||||
|
"@types/lru-cache": "7.10.9",
|
||||||
"@types/morgan": "1.9.10",
|
"@types/morgan": "1.9.10",
|
||||||
|
"@types/multer": "2.0.0",
|
||||||
"@types/node": "24.5.2",
|
"@types/node": "24.5.2",
|
||||||
|
"@types/on-headers": "1.0.4",
|
||||||
|
"@types/response-time": "2.3.9",
|
||||||
|
"@types/serve-favicon": "2.5.7",
|
||||||
"eslint": "9.36.0",
|
"eslint": "9.36.0",
|
||||||
"globals": "16.4.0",
|
"globals": "16.4.0",
|
||||||
"jiti": "2.6.0",
|
"jiti": "2.6.0",
|
||||||
@@ -34,6 +41,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@techniker-me/logger": "0.0.15",
|
"@techniker-me/logger": "0.0.15",
|
||||||
"@techniker-me/tools": "2025.0.16",
|
"@techniker-me/tools": "2025.0.16",
|
||||||
"morgan": "1.10.1"
|
"body-parser": "2.2.0",
|
||||||
|
"cors": "2.8.5",
|
||||||
|
"lru-cache": "11.2.2",
|
||||||
|
"moment": "2.30.1",
|
||||||
|
"morgan": "1.10.1",
|
||||||
|
"multer": "2.0.2",
|
||||||
|
"on-headers": "1.1.0",
|
||||||
|
"response-time": "2.3.4",
|
||||||
|
"serve-favicon": "2.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
import HttpServer from './net/http/HttpServer';
|
|
||||||
import {LoggerFactory} from '@techniker-me/logger';
|
|
||||||
|
|
||||||
|
import {LoggerFactory} from '@techniker-me/logger';
|
||||||
|
import HttpServer from './net/http/HttpServer';
|
||||||
|
import path from 'node:path';
|
||||||
const logger = LoggerFactory.getLogger('Server');
|
const logger = LoggerFactory.getLogger('Server');
|
||||||
|
|
||||||
const httpServer = new HttpServer('http', 3000, {
|
const httpServer = new HttpServer(
|
||||||
|
'http',
|
||||||
|
3000,
|
||||||
|
{
|
||||||
getGETRoutes: () => ({}),
|
getGETRoutes: () => ({}),
|
||||||
getPOSTRoutes: () => ({}),
|
getPOSTRoutes: () => ({}),
|
||||||
getPUTRoutes: () => ({}),
|
getPUTRoutes: () => ({}),
|
||||||
getPATCHRoutes: () => ({}),
|
getPATCHRoutes: () => ({}),
|
||||||
getDELETERoutes: () => ({}),
|
getDELETERoutes: () => ({})
|
||||||
}, {}, '', [], '', {});
|
},
|
||||||
|
{},
|
||||||
|
'',
|
||||||
|
[],
|
||||||
|
path.resolve(process.cwd(), 'assets', 'favicon', 'favicon.ico'),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
httpServer.on('error', () => {
|
httpServer.on('error', () => {
|
||||||
logger.error('[HttpServer] Error');
|
logger.error('[HttpServer] Error');
|
||||||
|
|||||||
@@ -73,27 +73,40 @@ export default class Assert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isFunction(name: string, value: unknown): asserts value is Function {
|
public static isFunction(name: string, value: unknown): asserts value is (...args: unknown[]) => unknown {
|
||||||
if (typeof value !== 'function') {
|
if (typeof value !== 'function') {
|
||||||
throw new Error(`[${name}] must be a function, instead received [${typeof value}]`);
|
throw new Error(`[${name}] must be a function, instead received [${typeof value}]`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static satisfiesInterface<T>(name: string, obj: unknown, requiredProps: (keyof T)[]): asserts obj is T {
|
public static satisfiesInterface<T>(name: string, obj: unknown, requiredProps: (keyof T)[]): asserts obj is T {
|
||||||
Assert.isObject(name, obj);
|
Assert.isObject(name, obj);
|
||||||
|
|
||||||
for (const prop of requiredProps) {
|
for (const prop of requiredProps) {
|
||||||
if (!(prop in (obj as any))) {
|
if (!(prop in (obj as Record<string, unknown>))) {
|
||||||
throw new Error(`[${name}] missing required property: ${String(prop)}`);
|
throw new Error(`[${name}] missing required property: ${String(prop)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isArray<T>(name: string, value: unknown): asserts value is T[] {
|
public static isArray(name: string, value: unknown): asserts value is unknown[] {
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
throw new Error(`[${name}] must be an array, instead received [${typeof value}]`);
|
throw new Error(`[${name}] must be an array, instead received [${typeof value}]`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static isArrayOf<T>(name: string, arrayValueType: T, value: unknown): asserts value is T[] {
|
||||||
|
Assert.isArray(name, value);
|
||||||
|
|
||||||
|
for (const item of value) {
|
||||||
|
const itemTypeof = typeof item;
|
||||||
|
|
||||||
|
if (itemTypeof !== arrayValueType) {
|
||||||
|
throw new Error(`[${name}] must be an array of [${arrayValueType}] received [${itemTypeof}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static isStringArray(name: string, value: unknown): asserts value is string[] {
|
public static isStringArray(name: string, value: unknown): asserts value is string[] {
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
throw new Error(`[${name}] must be an array, instead received [${typeof value}]`);
|
throw new Error(`[${name}] must be an array, instead received [${typeof value}]`);
|
||||||
@@ -116,6 +129,7 @@ export default class Assert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
public static isInstance<T>(name: string, parentClass: new (...args: any[]) => T, object: unknown): asserts object is T {
|
public static isInstance<T>(name: string, parentClass: new (...args: any[]) => T, object: unknown): asserts object is T {
|
||||||
if (object === null || object === undefined || typeof object !== 'object') {
|
if (object === null || object === undefined || typeof object !== 'object') {
|
||||||
throw new Error(`[${name}] must be an instance of [${parentClass.constructor.name}], instead received [${typeof object}]`);
|
throw new Error(`[${name}] must be an instance of [${parentClass.constructor.name}], instead received [${typeof object}]`);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {Nullable} from '../../types/optional';
|
import {Nullable} from '../../types/optional';
|
||||||
import Assert from '../../lang/Assert';
|
import Assert from '../../lang/Assert';
|
||||||
|
import moment from 'moment';
|
||||||
import type {Server} from 'node:http';
|
import type {Server} from 'node:http';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import {EventEmitter} from 'node:events';
|
import {EventEmitter} from 'node:events';
|
||||||
@@ -9,30 +10,62 @@ import IRoutes from './IRoutes';
|
|||||||
import {Subject} from '@techniker-me/tools';
|
import {Subject} from '@techniker-me/tools';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import morgan from 'morgan';
|
import morgan from 'morgan';
|
||||||
|
import favicon from 'serve-favicon';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import multer from 'multer';
|
||||||
|
import {Kilobytes} from '../../types/Units';
|
||||||
|
import responseTime from 'response-time';
|
||||||
|
import onHeaders from 'on-headers';
|
||||||
|
import {LRUCache} from 'lru-cache';
|
||||||
|
|
||||||
|
const requestSizeLimit: Kilobytes = 10240;
|
||||||
|
const defaultTcpSocketTimeout = moment.duration(720, 'seconds'); // Google HTTPS load balancer expects at least 600 seconds
|
||||||
|
const defaultKeepAliveTimeout = moment.duration(660, 'seconds'); // Google HTTPS load balancer expects at least 600 seconds
|
||||||
|
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age#:~:text=The%20Access%2DControl%2DMax%2D,Headers%20headers)%20can%20be%20cached.
|
||||||
|
const corsAccessControlMaxAge = moment.duration(24, 'hours');
|
||||||
|
const shortTermCaching = 'public, max-age=20, s-maxage=20';
|
||||||
|
const tlsSessionTimeout = moment.duration(5, 'minutes');
|
||||||
|
const maxCachedTlsSessions = 1000;
|
||||||
|
|
||||||
export default class HttpServer {
|
export default class HttpServer {
|
||||||
private readonly _logger: ILogger = LoggerFactory.getLogger('HttpServer');
|
private readonly _logger: ILogger = LoggerFactory.getLogger('HttpServer');
|
||||||
|
private readonly _eventEmitter: EventEmitter;
|
||||||
private readonly _protocol: 'http' | 'https';
|
private readonly _protocol: 'http' | 'https';
|
||||||
private readonly _port: number;
|
private readonly _port: number;
|
||||||
|
// @ts-expect-error - unused parameter for future functionality
|
||||||
private readonly _routes: IRoutes;
|
private readonly _routes: IRoutes;
|
||||||
|
// @ts-expect-error - unused parameter for future functionality
|
||||||
private readonly _viewsPath: object;
|
private readonly _viewsPath: object;
|
||||||
|
// @ts-expect-error - unused parameter for future functionality
|
||||||
private readonly _viewParameters: string;
|
private readonly _viewParameters: string;
|
||||||
private readonly _resourcesPaths: string[];
|
private readonly _resourcesPaths: string[];
|
||||||
private readonly _favicon: string;
|
private readonly _favicon: string;
|
||||||
private readonly _cors: object;
|
private readonly _cors: object;
|
||||||
private readonly _app: Subject<Nullable<express.Application>>;
|
private readonly _app: Subject<Nullable<express.Application>>;
|
||||||
private readonly _eventEmitter: EventEmitter;
|
|
||||||
private readonly _server: Subject<Nullable<Server>>;
|
private readonly _server: Subject<Nullable<Server>>;
|
||||||
|
private readonly _tlsSessionCache = new LRUCache({
|
||||||
|
ttl: tlsSessionTimeout.asMilliseconds(),
|
||||||
|
max: maxCachedTlsSessions
|
||||||
|
});
|
||||||
|
|
||||||
constructor(protocol: 'https' | 'http', port: number, routes: IRoutes, viewsPath: object, viewParameters: string, resourcesPaths: string[], favicon: string, cors: object) {
|
constructor(
|
||||||
|
protocol: 'https' | 'http',
|
||||||
|
port: number,
|
||||||
|
routes: IRoutes,
|
||||||
|
viewsPath: object,
|
||||||
|
viewParameters: string,
|
||||||
|
resourcesPaths: string[],
|
||||||
|
favicon: string,
|
||||||
|
cors: object
|
||||||
|
) {
|
||||||
Assert.isString('protocol', protocol);
|
Assert.isString('protocol', protocol);
|
||||||
Assert.isNumber('port', port);
|
Assert.isNumber('port', port);
|
||||||
Assert.satisfiesInterface('routes', routes, ['getGETRoutes', 'getPOSTRoutes', 'getPUTRoutes', 'getPATCHRoutes', 'getDELETERoutes']);
|
Assert.satisfiesInterface('routes', routes, ['getGETRoutes', 'getPOSTRoutes', 'getPUTRoutes', 'getPATCHRoutes', 'getDELETERoutes']);
|
||||||
Assert.isObject('viewsPath', viewsPath);
|
// Assert.isObjectOf<string>('viewsPath', viewsPath);
|
||||||
Assert.isString('viewParameters', viewParameters);
|
Assert.isString('viewParameters', viewParameters);
|
||||||
Assert.isStringArray('resourcesPaths', resourcesPaths);
|
Assert.isArrayOf<string>('resourcesPaths', 'string', resourcesPaths);
|
||||||
Assert.isString('favicon', favicon);
|
Assert.isString('favicon', favicon);
|
||||||
Assert.isObject('cors', cors);
|
// Assert.isObjectOf<string>('cors', cors, 'string');
|
||||||
|
|
||||||
this._protocol = protocol;
|
this._protocol = protocol;
|
||||||
this._port = port;
|
this._port = port;
|
||||||
@@ -47,22 +80,281 @@ export default class HttpServer {
|
|||||||
this._server = new Subject<Nullable<Server>>(null);
|
this._server = new Subject<Nullable<Server>>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start() {
|
public start() {
|
||||||
const app = this._app.value = express();
|
return new Promise((resolve, reject) => {
|
||||||
|
const app = (this._app.value = express());
|
||||||
|
|
||||||
app.use(morgan('common'));
|
this.configureListener();
|
||||||
this._server.value = http.createServer(app);
|
this.configureMiddleware();
|
||||||
|
this.configureResources();
|
||||||
|
this.configureRoutes();
|
||||||
|
|
||||||
this._server.value.listen(this._port, () => {
|
app.set('x-powered-by', false);
|
||||||
this._logger.info(`Server is running on port ${this._port}`);
|
|
||||||
|
const server = (this._server.value = http.createServer(app));
|
||||||
|
|
||||||
|
const onListen = () => {
|
||||||
|
this._logger.info('HTTP Server listening on %s://*:%s', this._protocol, this._port);
|
||||||
|
|
||||||
|
server.removeListener('error', onError);
|
||||||
|
|
||||||
|
resolve(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onError = (err: unknown) => {
|
||||||
|
server.removeListener('listening', onListen);
|
||||||
|
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
server.keepAliveTimeout = defaultKeepAliveTimeout.milliseconds();
|
||||||
|
server.timeout = defaultTcpSocketTimeout.asMilliseconds();
|
||||||
|
server.setTimeout(defaultTcpSocketTimeout.asMilliseconds());
|
||||||
|
server.once('error', onError);
|
||||||
|
server.once('listening', onListen);
|
||||||
|
|
||||||
|
server.on('newSession', (sessionId, sessionData, callback) => {
|
||||||
|
const cacheId = sessionId.toString('hex');
|
||||||
|
|
||||||
|
this._tlsSessionCache.set(cacheId, sessionData);
|
||||||
|
this._logger.debug('Created new TLS session [%s]', cacheId);
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('resumeSession', (sessionId, callback) => {
|
||||||
|
const cacheId = sessionId.toString('hex');
|
||||||
|
const sessionData = this._tlsSessionCache.get(cacheId);
|
||||||
|
|
||||||
|
callback(null, sessionData);
|
||||||
|
|
||||||
|
if (sessionData) {
|
||||||
|
this._logger.debug('Resumed TLS session [%s]', cacheId);
|
||||||
|
} else {
|
||||||
|
this._logger.debug('TLS session [%s] not found', cacheId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen({
|
||||||
|
port: this._port,
|
||||||
|
backlog: 16 * 1024
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public on(event: string, handler: () => void): void {
|
public on(event: string, handler: () => void): void {
|
||||||
Assert.isNonEmptyString('event', event);
|
Assert.isNonEmptyString('event', event);
|
||||||
Assert.isFunction('handler', handler);
|
Assert.isFunction('handler', handler);
|
||||||
|
|
||||||
this._eventEmitter.on(event, handler);
|
this._eventEmitter.on(event, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private configureListener() {
|
||||||
|
if (!this._app.value) {
|
||||||
|
throw new Error('Unable to configure listener, no app instance found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = this._app.value;
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
req.on('finish', () => {
|
||||||
|
this._eventEmitter.emit('request', req.method, req.url, res.statusCode, req.headers);
|
||||||
|
});
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private configureMiddleware() {
|
||||||
|
if (!this._app.value) {
|
||||||
|
throw new Error('Unable to configure middleware, no app instance found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = this._app.value;
|
||||||
|
const logger = this._logger;
|
||||||
|
|
||||||
|
app.use(morgan('common'));
|
||||||
|
app.enable('trust proxy');
|
||||||
|
app.set('env', 'development'); // setting env to test prevents logging to the console
|
||||||
|
app.use(favicon(this._favicon));
|
||||||
|
app.use(
|
||||||
|
morgan(
|
||||||
|
':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" :response-time',
|
||||||
|
{
|
||||||
|
stream: {
|
||||||
|
write(line) {
|
||||||
|
logger.info(line.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
bodyParser.text({
|
||||||
|
type: 'application/sdp',
|
||||||
|
limit: requestSizeLimit
|
||||||
|
})
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
bodyParser.text({
|
||||||
|
type: 'application/trickle-ice-sdpfrag',
|
||||||
|
limit: requestSizeLimit
|
||||||
|
})
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
bodyParser.urlencoded({
|
||||||
|
extended: true,
|
||||||
|
limit: requestSizeLimit
|
||||||
|
})
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
multer({
|
||||||
|
limits: {
|
||||||
|
fields: 1,
|
||||||
|
fieldNameSize: 100,
|
||||||
|
fieldSize: requestSizeLimit,
|
||||||
|
files: 0,
|
||||||
|
parts: 1,
|
||||||
|
headerPairs: 1
|
||||||
|
}
|
||||||
|
}).none()
|
||||||
|
);
|
||||||
|
app.use((req, _res, next) => {
|
||||||
|
const contentType = req?.headers?.['content-type'] || '';
|
||||||
|
|
||||||
|
if (contentType.startsWith('multipart/form-data;')) {
|
||||||
|
if (req?.body?.jsonBody) {
|
||||||
|
req.body = JSON.parse(req.body.jsonBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
app.use(responseTime());
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
res.set('x-origination', 'Platform');
|
||||||
|
|
||||||
|
if (req.secure) {
|
||||||
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this._cors) {
|
||||||
|
this._logger.info('Enable CORS on %s://*:%s', this._protocol, this._port);
|
||||||
|
|
||||||
|
const cachedCorsAllowOrigins: Record<string, string> = {};
|
||||||
|
const getCorsAllowOrigin = (url: string) => {
|
||||||
|
let corsAllowOrigin = cachedCorsAllowOrigins[url];
|
||||||
|
|
||||||
|
if (!Object.hasOwn(cachedCorsAllowOrigins, url)) {
|
||||||
|
Object.entries(this._cors).forEach(([key, value]) => {
|
||||||
|
if (url.startsWith(key)) {
|
||||||
|
corsAllowOrigin = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cachedCorsAllowOrigins[url] = corsAllowOrigin ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return corsAllowOrigin;
|
||||||
|
};
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
const corsAllowOrigin = getCorsAllowOrigin(req.url);
|
||||||
|
|
||||||
|
if (corsAllowOrigin) {
|
||||||
|
res.header('Access-Control-Allow-Origin', corsAllowOrigin);
|
||||||
|
res.header(
|
||||||
|
'Access-Control-Allow-Headers',
|
||||||
|
'Authorization, Origin, Range, X-Requested-With, If-Modified-Since, Accept, Keep-Alive, Cache-Control, Content-Type, DNT'
|
||||||
|
);
|
||||||
|
res.header('Access-Control-Allow-Methods', 'POST, GET, HEAD, OPTIONS, PUT, PATCH, DELETE');
|
||||||
|
res.header('Access-Control-Expose-Headers', 'Server, Range, Date, Content-Disposition, X-Timer, ETag, Link, Location');
|
||||||
|
res.header('Access-Control-Max-Age', corsAccessControlMaxAge.asSeconds().toString());
|
||||||
|
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
res.header('Cache-Control', shortTermCaching);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
app.use((_req, res, next) => {
|
||||||
|
const startTimeSeconds = Date.now() / 1000;
|
||||||
|
const startTimeNanoseconds = process.hrtime.bigint();
|
||||||
|
|
||||||
|
onHeaders(res, () => {
|
||||||
|
const durationNanoseconds = process.hrtime.bigint() - startTimeNanoseconds;
|
||||||
|
const durationMilliseconds = durationNanoseconds / 1000000n;
|
||||||
|
|
||||||
|
// https://developer.fastly.com/reference/http/http-headers/X-Timer/
|
||||||
|
// S{unixStartTimeSeconds},VS0,VE{durationMilliseconds}
|
||||||
|
res.setHeader('X-Timer', `S${startTimeSeconds},VS0,VE${durationMilliseconds}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private configureResources() {
|
||||||
|
if (!this._app.value) {
|
||||||
|
throw new Error('Unable to configure resources, no app instance found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = this._app.value;
|
||||||
|
|
||||||
|
for (const resourcePath of this._resourcesPaths) {
|
||||||
|
app.use(express.static(resourcePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(this.errorHandler);
|
||||||
|
app.use(this.genericNotFoundHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
private errorHandler(err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) {
|
||||||
|
this._logger.error(err.message);
|
||||||
|
|
||||||
|
res.status(500).send({status: 'error'});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private genericNotFoundHandler(_req: express.Request, res: express.Response) {
|
||||||
|
res.status(404).send({status: 'not-found'});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private configureRoutes() {
|
||||||
|
if (!this._app.value) {
|
||||||
|
throw new Error('Unable to configure routes, no app instance found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement routes
|
||||||
|
// const app = this._app.value;
|
||||||
|
|
||||||
|
// let catchAllHandler = null;
|
||||||
|
|
||||||
|
// for (const route of this._routes.getGETRoutes().entries()) {
|
||||||
|
// const [handler, name] = route;
|
||||||
|
|
||||||
|
// if (name === '*') {
|
||||||
|
// if (catchAllHandler) {
|
||||||
|
// throw new Error(`Only one catch-all handler can ber registered per server, ignoring conflicting catch-all`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// catchAllHandler = handler;
|
||||||
|
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// this._logger.debug(`Registering [GET] route [${name}]`);
|
||||||
|
// // app.get(name, this._json);
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
export default interface IRoutes {
|
export default interface IRoutes {
|
||||||
getGETRoutes(): Record<string, () => (Promise<void> | void);
|
getGETRoutes(): Record<string, () => void | Promise<void>>;
|
||||||
getPOSTRoutes(): Record<string, () => (Promise<void> | void);
|
getPOSTRoutes(): Record<string, () => void | Promise<void>>;
|
||||||
getPUTRoutes(): Record<string, () => (Promise<void> | void);
|
getPUTRoutes(): Record<string, () => void | Promise<void>>;
|
||||||
getPATCHRoutes(): Record<string, () => (Promise<void> | void);
|
getPATCHRoutes(): Record<string, () => void | Promise<void>>;
|
||||||
getDELETERoutes(): Record<string, () => (Promise<void> | void);
|
getDELETERoutes(): Record<string, () => void | Promise<void>>;
|
||||||
}
|
}
|
||||||
|
|||||||
18
Web/WebSocket/websocket/server/src/types/Units.ts
Normal file
18
Web/WebSocket/websocket/server/src/types/Units.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export type Bytes = number;
|
||||||
|
export type Kilobytes = number;
|
||||||
|
export type Megabytes = number;
|
||||||
|
export type Gigabytes = number;
|
||||||
|
export type Terabytes = number;
|
||||||
|
export type Petabytes = number;
|
||||||
|
export type Exabytes = number;
|
||||||
|
export type Zettabytes = number;
|
||||||
|
export type Yottabytes = number;
|
||||||
|
|
||||||
|
export type Milliseconds = number;
|
||||||
|
export type Seconds = number;
|
||||||
|
export type Minutes = number;
|
||||||
|
export type Hours = number;
|
||||||
|
export type Days = number;
|
||||||
|
export type Weeks = number;
|
||||||
|
export type Months = number;
|
||||||
|
export type Years = number;
|
||||||
Reference in New Issue
Block a user