From a60809049f58eb001d868717a1dda9ac129f8a53 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 30 Jul 2023 15:38:25 +0200 Subject: [PATCH] feat: add initial HTTP/2 support to ui server (mutually exclusive for now) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- README.md | 18 +++++++++--------- src/charging-station/Bootstrap.ts | 4 ++-- src/charging-station/ChargingStationWorker.ts | 3 ++- .../ui-server/AbstractUIServer.ts | 17 +++++++++++++++-- src/charging-station/ui-server/UIHttpServer.ts | 8 ++++++-- .../ui-server/UIServerFactory.ts | 14 +++++++++++++- src/types/ConfigurationData.ts | 6 ++++++ src/types/index.ts | 1 + 8 files changed, 54 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 69d46ccc..ae8eb0e7 100644 --- a/README.md +++ b/README.md @@ -100,15 +100,15 @@ But the modifications to test have to be done to the files in the build target d **src/assets/config.json**: -| Key | Value(s) | Default Value | Value type | Description | -| -------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| supervisionUrls | | [] | string \| string[] | string or strings array containing global connection URIs to OCPP-J servers | -| supervisionUrlDistribution | round-robin/random/charging-station-affinity | charging-station-affinity | string | supervision urls distribution policy to simulated charging stations | -| log | | {
"enabled": true,
"file": "logs/combined.log",
"errorFile": "logs/error.log",
"statisticsInterval": 60,
"level": "info",
"console": false,
"format": "simple",
"rotate": true
} | {
enabled: boolean;
file: string;
errorFile: string;
statisticsInterval: number;
level: string;
console: boolean;
format: string;
rotate: boolean;
maxFiles: string \| number;
maxSize: string \| number;
} | Log configuration section:
- _enabled_: enable logging
- _file_: log file relative path
- _errorFile_: error log file relative path
- _statisticsInterval_: seconds between charging stations statistics output in the logs
- _level_: emerg/alert/crit/error/warning/notice/info/debug [winston](https://github.com/winstonjs/winston) logging level
- _console_: output logs on the console
- _format_: [winston](https://github.com/winstonjs/winston) log format
- _rotate_: enable daily log files rotation
- _maxFiles_: maximum number of log files: https://github.com/winstonjs/winston-daily-rotate-file#options
- _maxSize_: maximum size of log files in bytes, or units of kb, mb, and gb: https://github.com/winstonjs/winston-daily-rotate-file#options | -| worker | | {
"processType": "workerSet",
"startDelay": 500,
"elementStartDelay": 0,
"elementsPerWorker": 'auto',
"poolMinSize": 4,
"poolMaxSize": 16
} | {
processType: WorkerProcessType;
startDelay: number;
elementStartDelay: number;
elementsPerWorker: number \| 'auto';
poolMinSize: number;
poolMaxSize: number;
} | Worker configuration section:
- _processType_: worker threads process type (`workerSet`/`staticPool`/`dynamicPool`)
- _startDelay_: milliseconds to wait at worker threads startup (only for `workerSet` worker threads process type)
- _elementStartDelay_: milliseconds to wait at charging station startup
- _elementsPerWorker_: number of charging stations per worker threads for the `workerSet` process type (`auto` means (number of stations) / (number of CPUs) \* 1.5 if (number of stations) > (number of CPUs), otherwise 1)
- _poolMinSize_: worker threads pool minimum number of threads
- _poolMaxSize_: worker threads pool maximum number of threads | -| uiServer | | {
"enabled": false,
"type": "ws",
"options": {
"host": "localhost",
"port": 8080
}
} | {
enabled: boolean;
type: ApplicationProtocol;
options: ServerOptions;
authentication: {
enabled: boolean;
type: AuthenticationType;
username: string;
password: string;
}
} | UI server configuration section | -| performanceStorage | | {
"enabled": false,
"type": "jsonfile",
"file:///performanceRecords.json"
} | {
enabled: boolean;
type: string;
URI: string;
}
where type can be 'jsonfile' or 'mongodb' | performance storage configuration section | -| stationTemplateUrls | | {}[] | {
file: string;
numberOfStations: number;
}[] | array of charging station configuration templates URIs configuration section (charging station configuration template file name and number of stations) | +| Key | Value(s) | Default Value | Value type | Description | +| -------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| supervisionUrls | | [] | string \| string[] | string or strings array containing global connection URIs to OCPP-J servers | +| supervisionUrlDistribution | round-robin/random/charging-station-affinity | charging-station-affinity | string | supervision urls distribution policy to simulated charging stations | +| log | | {
"enabled": true,
"file": "logs/combined.log",
"errorFile": "logs/error.log",
"statisticsInterval": 60,
"level": "info",
"console": false,
"format": "simple",
"rotate": true
} | {
enabled: boolean;
file: string;
errorFile: string;
statisticsInterval: number;
level: string;
console: boolean;
format: string;
rotate: boolean;
maxFiles: string \| number;
maxSize: string \| number;
} | Log configuration section:
- _enabled_: enable logging
- _file_: log file relative path
- _errorFile_: error log file relative path
- _statisticsInterval_: seconds between charging stations statistics output in the logs
- _level_: emerg/alert/crit/error/warning/notice/info/debug [winston](https://github.com/winstonjs/winston) logging level
- _console_: output logs on the console
- _format_: [winston](https://github.com/winstonjs/winston) log format
- _rotate_: enable daily log files rotation
- _maxFiles_: maximum number of log files: https://github.com/winstonjs/winston-daily-rotate-file#options
- _maxSize_: maximum size of log files in bytes, or units of kb, mb, and gb: https://github.com/winstonjs/winston-daily-rotate-file#options | +| worker | | {
"processType": "workerSet",
"startDelay": 500,
"elementStartDelay": 0,
"elementsPerWorker": 'auto',
"poolMinSize": 4,
"poolMaxSize": 16
} | {
processType: WorkerProcessType;
startDelay: number;
elementStartDelay: number;
elementsPerWorker: number \| 'auto';
poolMinSize: number;
poolMaxSize: number;
} | Worker configuration section:
- _processType_: worker threads process type (`workerSet`/`staticPool`/`dynamicPool`)
- _startDelay_: milliseconds to wait at worker threads startup (only for `workerSet` worker threads process type)
- _elementStartDelay_: milliseconds to wait at charging station startup
- _elementsPerWorker_: number of charging stations per worker threads for the `workerSet` process type (`auto` means (number of stations) / (number of CPUs) \* 1.5 if (number of stations) > (number of CPUs), otherwise 1)
- _poolMinSize_: worker threads pool minimum number of threads
- _poolMaxSize_: worker threads pool maximum number of threads | +| uiServer | | {
"enabled": false,
"type": "ws",
"options": {
"host": "localhost",
"port": 8080
}
} | {
enabled: boolean;
type: ApplicationProtocol;
version: ApplicationProtocolVersion;
options: ServerOptions;
authentication: {
enabled: boolean;
type: AuthenticationType;
username: string;
password: string;
}
} | UI server configuration sectio9n:
- _enabled_: enable UI server
- _type_: 'http' or 'ws'
- _version_: '1.1' or '2.0'
_options_: node.js net module [listen options](https://nodejs.org/api/net.html#serverlistenoptions-callback) | +| performanceStorage | | {
"enabled": false,
"type": "jsonfile",
"file:///performanceRecords.json"
} | {
enabled: boolean;
type: string;
URI: string;
}
where type can be 'jsonfile' or 'mongodb' | performance storage configuration section | +| stationTemplateUrls | | {}[] | {
file: string;
numberOfStations: number;
}[] | array of charging station configuration templates URIs configuration section (charging station configuration template file name and number of stations) | #### Worker process model diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index 76b2c299..7e23949c 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -109,7 +109,7 @@ export class Bootstrap extends EventEmitter { public async start(): Promise { if (!isMainThread) { - throw new Error('Cannot start charging stations simulator from worker thread'); + throw new BaseError('Cannot start charging stations simulator from worker thread'); } if (this.started === false) { if (this.starting === false) { @@ -176,7 +176,7 @@ export class Bootstrap extends EventEmitter { public async stop(): Promise { if (!isMainThread) { - throw new Error('Cannot stop charging stations simulator from worker thread'); + throw new BaseError('Cannot stop charging stations simulator from worker thread'); } if (this.started === true) { if (this.stopping === false) { diff --git a/src/charging-station/ChargingStationWorker.ts b/src/charging-station/ChargingStationWorker.ts index ff9dbe62..7c7a5235 100644 --- a/src/charging-station/ChargingStationWorker.ts +++ b/src/charging-station/ChargingStationWorker.ts @@ -6,6 +6,7 @@ import { parentPort } from 'node:worker_threads'; import { ThreadWorker } from 'poolifier'; import { ChargingStation } from './ChargingStation'; +import { BaseError } from '../exception'; import type { ChargingStationWorkerData } from '../types'; import { Configuration } from '../utils'; import { POOL_MAX_INACTIVE_TIME, type WorkerMessage, WorkerMessageEvents } from '../worker'; @@ -48,7 +49,7 @@ class ChargingStationWorker extends AsyncResource { } break; default: - throw new Error( + throw new BaseError( `Unknown worker event: '${message.event}' received with data: '${JSON.stringify( message.data, null, diff --git a/src/charging-station/ui-server/AbstractUIServer.ts b/src/charging-station/ui-server/AbstractUIServer.ts index 5dbe5b20..133389d6 100644 --- a/src/charging-station/ui-server/AbstractUIServer.ts +++ b/src/charging-station/ui-server/AbstractUIServer.ts @@ -1,4 +1,5 @@ import { type IncomingMessage, Server, type ServerResponse } from 'node:http'; +import { type Http2Server, createServer } from 'node:http2'; import type { WebSocket } from 'ws'; @@ -6,6 +7,7 @@ import type { AbstractUIService } from './ui-services/AbstractUIService'; import { UIServiceFactory } from './ui-services/UIServiceFactory'; import { BaseError } from '../../exception'; import { + ApplicationProtocolVersion, AuthenticationType, type ChargingStationData, type ProcedureName, @@ -19,13 +21,24 @@ import { export abstract class AbstractUIServer { public readonly chargingStations: Map; - protected readonly httpServer: Server; + protected readonly httpServer: Server | Http2Server; protected readonly responseHandlers: Map; protected readonly uiServices: Map; public constructor(protected readonly uiServerConfiguration: UIServerConfiguration) { this.chargingStations = new Map(); - this.httpServer = new Server(); + switch (this.uiServerConfiguration.version) { + case ApplicationProtocolVersion.VERSION_11: + this.httpServer = new Server(); + break; + case ApplicationProtocolVersion.VERSION_20: + this.httpServer = createServer(); + break; + default: + throw new BaseError( + `Unsupported application protocol version ${this.uiServerConfiguration.version}`, + ); + } this.responseHandlers = new Map(); this.uiServices = new Map(); } diff --git a/src/charging-station/ui-server/UIHttpServer.ts b/src/charging-station/ui-server/UIHttpServer.ts index ee12b3f4..e11fd4bd 100644 --- a/src/charging-station/ui-server/UIHttpServer.ts +++ b/src/charging-station/ui-server/UIHttpServer.ts @@ -6,6 +6,7 @@ import { AbstractUIServer } from './AbstractUIServer'; import { UIServerUtils } from './UIServerUtils'; import { BaseError } from '../../exception'; import { + ApplicationProtocolVersion, type ProcedureName, type Protocol, type ProtocolRequest, @@ -43,9 +44,12 @@ export class UIHttpServer extends AbstractUIServer { this.startHttpServer(); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars public sendRequest(request: ProtocolRequest): void { - // This is intentionally left blank + switch (this.uiServerConfiguration.version) { + case ApplicationProtocolVersion.VERSION_20: + this.httpServer.emit('request', request); + break; + } } public sendResponse(response: ProtocolResponse): void { diff --git a/src/charging-station/ui-server/UIServerFactory.ts b/src/charging-station/ui-server/UIServerFactory.ts index c4006b03..49f08837 100644 --- a/src/charging-station/ui-server/UIServerFactory.ts +++ b/src/charging-station/ui-server/UIServerFactory.ts @@ -4,7 +4,12 @@ import type { AbstractUIServer } from './AbstractUIServer'; import { UIHttpServer } from './UIHttpServer'; import { UIServerUtils } from './UIServerUtils'; import { UIWebSocketServer } from './UIWebSocketServer'; -import { ApplicationProtocol, type UIServerConfiguration } from '../../types'; +import { + ApplicationProtocol, + ApplicationProtocolVersion, + type UIServerConfiguration, +} from '../../types'; +import { isUndefined } from '../../utils'; export class UIServerFactory { private constructor() { @@ -21,6 +26,13 @@ export class UIServerFactory { ), ); } + uiServerConfiguration = { + ...(uiServerConfiguration.type === ApplicationProtocol.HTTP && + isUndefined(uiServerConfiguration.version) && { + version: ApplicationProtocolVersion.VERSION_11, + }), + ...uiServerConfiguration, + }; switch (uiServerConfiguration.type) { case ApplicationProtocol.WS: return new UIWebSocketServer(uiServerConfiguration); diff --git a/src/types/ConfigurationData.ts b/src/types/ConfigurationData.ts index ebec3a0d..95d30bdc 100644 --- a/src/types/ConfigurationData.ts +++ b/src/types/ConfigurationData.ts @@ -39,9 +39,15 @@ export interface LogConfiguration { maxSize?: string | number; } +export enum ApplicationProtocolVersion { + VERSION_11 = 1.1, + VERSION_20 = 2.0, +} + export interface UIServerConfiguration { enabled?: boolean; type?: ApplicationProtocol; + version?: ApplicationProtocolVersion; options?: ServerOptions; authentication?: { enabled: boolean; diff --git a/src/types/index.ts b/src/types/index.ts index 04608aab..11f1138c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -164,6 +164,7 @@ export { type WsOptions, } from './ChargingStationTemplate'; export { + ApplicationProtocolVersion, type ConfigurationData, ConfigurationSection, type LogConfiguration, -- 2.34.1