From 976d11ec6d2d38a5a82440de79f059638e28cdbd Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 9 Sep 2022 07:12:20 +0200 Subject: [PATCH] UI Server: Improve error handling MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- .../Insomnia_CSSimulatorUIProtocol.json | 36 ++++++------- .../AutomaticTransactionGenerator.ts | 10 ++-- src/charging-station/Bootstrap.ts | 5 +- .../ui-server/AbstractUIServer.ts | 28 +++++------ .../ui-server/UIHttpServer.ts | 29 ++++++----- .../ui-server/UIServerFactory.ts | 3 +- .../ui-server/UIWebSocketServer.ts | 26 +++++++--- .../ui-services/AbstractUIService.ts | 50 ++++++++++++++++--- .../ui-server/ui-services/UIService001.ts | 38 +------------- src/performance/PerformanceStatistics.ts | 7 +-- 10 files changed, 118 insertions(+), 114 deletions(-) diff --git a/src/assets/ui-protocol/Insomnia_CSSimulatorUIProtocol.json b/src/assets/ui-protocol/Insomnia_CSSimulatorUIProtocol.json index 613aa1da..7cec4676 100644 --- a/src/assets/ui-protocol/Insomnia_CSSimulatorUIProtocol.json +++ b/src/assets/ui-protocol/Insomnia_CSSimulatorUIProtocol.json @@ -1,13 +1,13 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2022-09-08T22:23:58.294Z", + "__export_date": "2022-09-09T05:06:59.623Z", "__export_source": "insomnia.desktop.app:v2022.5.1", "resources": [ { "_id": "req_606dcee139984772877def40fcbb5c76", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675789048, + "modified": 1662699976610, "created": 1661789624987, "url": "{{baseUrl}}/{{protocol}}/{{version}}/listChargingStations", "name": "listChargingStations", @@ -52,7 +52,7 @@ { "_id": "req_7d5f9506e7ac49208a4f960a7740663e", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675781452, + "modified": 1662700001883, "created": 1661789624990, "url": "{{baseUrl}}/{{protocol}}/{{version}}/startSimulator", "name": "startSimulator", @@ -87,7 +87,7 @@ { "_id": "req_59056be11534481c80a0b0da32e2a06a", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675782590, + "modified": 1662700001136, "created": 1661789624994, "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopSimulator", "name": "stopSimulator", @@ -122,7 +122,7 @@ { "_id": "req_aad7fd6db4c64869b60048b915010efc", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675784826, + "modified": 1662699999845, "created": 1661789624998, "url": "{{baseUrl}}/{{protocol}}/{{version}}/startChargingStation", "name": "startChargingStation", @@ -160,7 +160,7 @@ { "_id": "req_d72d91cf3fb044179b8ae9d92a74f99c", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662674004751, + "modified": 1662699974387, "created": 1661789625002, "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopChargingStation", "name": "stopChargingStation", @@ -198,7 +198,7 @@ { "_id": "req_747f458d196f4681b5fe15204b0067aa", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675794900, + "modified": 1662699923383, "created": 1661789625005, "url": "{{baseUrl}}/{{protocol}}/{{version}}/openConnection", "name": "openConnection", @@ -236,7 +236,7 @@ { "_id": "req_401e6a62a33c4b6c90aaa2e019daab6d", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675791687, + "modified": 1662699980072, "created": 1661789625014, "url": "{{baseUrl}}/{{protocol}}/{{version}}/closeConnection", "name": "closeConnection", @@ -274,7 +274,7 @@ { "_id": "req_2f757efe92fb4936ad4fa4b6763f9293", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675797662, + "modified": 1662699966562, "created": 1661789625017, "url": "{{baseUrl}}/{{protocol}}/{{version}}/startTransaction", "name": "startTransaction", @@ -299,7 +299,7 @@ "username": "{{username}}", "password": "{{password}}" }, - "metaSortKey": -999999750, + "metaSortKey": -999999587.5, "isPrivate": false, "settingStoreCookies": true, "settingSendCookies": true, @@ -312,7 +312,7 @@ { "_id": "req_7c285fb6cb6948a08235a6c73cbeb1f9", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675801164, + "modified": 1662699963660, "created": 1661789625020, "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopTransaction", "name": "stopTransaction", @@ -337,7 +337,7 @@ "username": "{{username}}", "password": "{{password}}" }, - "metaSortKey": -999999700, + "metaSortKey": -999999575, "isPrivate": false, "settingStoreCookies": true, "settingSendCookies": true, @@ -350,7 +350,7 @@ { "_id": "req_b33c704fe3464dc5a5d3694abd9320d0", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675804146, + "modified": 1662699972371, "created": 1661803778569, "url": "{{baseUrl}}/{{protocol}}/{{version}}/startAutomaticTransactionGenerator", "name": "startAutomaticTransactionGenerator", @@ -388,7 +388,7 @@ { "_id": "req_24c1c55fe3ba4ddb94702408f21a64df", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675807062, + "modified": 1662699969481, "created": 1661803846882, "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopAutomaticTransactionGenerator", "name": "stopAutomaticTransactionGenerator", @@ -426,7 +426,7 @@ { "_id": "req_6a78267706094fb59d85ed1531e07a55", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675809951, + "modified": 1662699960920, "created": 1662330215407, "url": "{{baseUrl}}/{{protocol}}/{{version}}/statusNotification", "name": "statusNotification", @@ -464,7 +464,7 @@ { "_id": "req_61efafe9f4a14c268b948b9f9c5c4195", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675812274, + "modified": 1662699996869, "created": 1662409405256, "url": "{{baseUrl}}/{{protocol}}/{{version}}/heartbeat", "name": "heartbeat", @@ -502,7 +502,7 @@ { "_id": "req_9633f79d949d491e8b6892eed08bd198", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675815887, + "modified": 1662700003452, "created": 1662648910935, "url": "{{baseUrl}}/{{protocol}}/{{version}}/authorize", "name": "authorize", @@ -540,7 +540,7 @@ { "_id": "req_5874d988a93a4d28b860b7ab65e534e0", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662675817875, + "modified": 1662700004341, "created": 1662673259612, "url": "{{baseUrl}}/{{protocol}}/{{version}}/meterValues", "name": "meterValues", diff --git a/src/charging-station/AutomaticTransactionGenerator.ts b/src/charging-station/AutomaticTransactionGenerator.ts index 54b409ed..706a4b8d 100644 --- a/src/charging-station/AutomaticTransactionGenerator.ts +++ b/src/charging-station/AutomaticTransactionGenerator.ts @@ -8,12 +8,12 @@ import type { import { RequestCommand } from '../types/ocpp/Requests'; import { AuthorizationStatus, - AuthorizeRequest, - AuthorizeResponse, - StartTransactionRequest, - StartTransactionResponse, + type AuthorizeRequest, + type AuthorizeResponse, + type StartTransactionRequest, + type StartTransactionResponse, StopTransactionReason, - StopTransactionResponse, + type StopTransactionResponse, } from '../types/ocpp/Transaction'; import Constants from '../utils/Constants'; import logger from '../utils/Logger'; diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index 8734ed22..6dc6620d 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -54,10 +54,7 @@ export class Bootstrap { ); this.initialize(); Configuration.getUIServer().enabled === true && - (this.uiServer = UIServerFactory.getUIServerImplementation( - Configuration.getUIServer().type, - Configuration.getUIServer() - )); + (this.uiServer = UIServerFactory.getUIServerImplementation(Configuration.getUIServer())); Configuration.getPerformanceStorage().enabled === true && (this.storage = StorageFactory.getStorage( Configuration.getPerformanceStorage().type, diff --git a/src/charging-station/ui-server/AbstractUIServer.ts b/src/charging-station/ui-server/AbstractUIServer.ts index dad1e0cb..d8ef6340 100644 --- a/src/charging-station/ui-server/AbstractUIServer.ts +++ b/src/charging-station/ui-server/AbstractUIServer.ts @@ -18,8 +18,8 @@ import UIServiceFactory from './ui-services/UIServiceFactory'; export abstract class AbstractUIServer { public readonly chargingStations: Map; - protected httpServer: Server; - protected responseHandlers: Map; + protected readonly httpServer: Server; + protected readonly responseHandlers: Map; protected readonly uiServices: Map; public constructor(protected readonly uiServerConfiguration: UIServerConfiguration) { @@ -51,14 +51,24 @@ export abstract class AbstractUIServer { } } - protected isBasicAuthEnabled(): boolean { + protected authenticate(req: IncomingMessage, next: (err?: Error) => void): void { + if (this.isBasicAuthEnabled() === true) { + if (this.isValidBasicAuth(req) === false) { + next(new Error('Unauthorized')); + } + next(); + } + next(); + } + + private isBasicAuthEnabled(): boolean { return ( this.uiServerConfiguration.authentication?.enabled === true && this.uiServerConfiguration.authentication?.type === AuthenticationType.BASIC_AUTH ); } - protected isValidBasicAuth(req: IncomingMessage): boolean { + private isValidBasicAuth(req: IncomingMessage): boolean { const authorizationHeader = req.headers.authorization ?? ''; const authorizationToken = authorizationHeader.split(/\s+/).pop() ?? ''; const authentication = Buffer.from(authorizationToken, 'base64').toString(); @@ -71,16 +81,6 @@ export abstract class AbstractUIServer { ); } - protected authenticate(req: IncomingMessage, next: (err?: Error) => void): void { - if (this.isBasicAuthEnabled() === true) { - if (this.isValidBasicAuth(req) === false) { - next(new Error('Unauthorized')); - } - next(); - } - next(); - } - public abstract start(): void; public abstract sendRequest(request: ProtocolRequest): void; public abstract sendResponse(response: ProtocolResponse): void; diff --git a/src/charging-station/ui-server/UIHttpServer.ts b/src/charging-station/ui-server/UIHttpServer.ts index 6ae03d5d..7dfff0ad 100644 --- a/src/charging-station/ui-server/UIHttpServer.ts +++ b/src/charging-station/ui-server/UIHttpServer.ts @@ -39,16 +39,23 @@ export default class UIHttpServer extends AbstractUIServer { public sendResponse(response: ProtocolResponse): void { const [uuid, payload] = response; - if (this.responseHandlers.has(uuid) === true) { - const res = this.responseHandlers.get(uuid) as ServerResponse; - res.writeHead(this.responseStatusToStatusCode(payload.status), { - 'Content-Type': 'application/json', - }); - res.end(JSON.stringify(payload)); - this.responseHandlers.delete(uuid); - } else { + try { + if (this.responseHandlers.has(uuid) === true) { + const res = this.responseHandlers.get(uuid) as ServerResponse; + res.writeHead(this.responseStatusToStatusCode(payload.status), { + 'Content-Type': 'application/json', + }); + res.end(JSON.stringify(payload)); + this.responseHandlers.delete(uuid); + } else { + logger.error( + `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}` + ); + } + } catch (error) { logger.error( - `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}` + `${this.logPrefix(moduleName, 'sendResponse')} Error at sending response id '${uuid}':`, + error ); } } @@ -102,9 +109,7 @@ export default class UIHttpServer extends AbstractUIServer { .get(version) .requestHandler(this.buildProtocolRequest(uuid, procedureName, body ?? {})) .catch(() => { - this.sendResponse( - this.buildProtocolResponse(uuid, { status: ResponseStatus.FAILURE }) - ); + /* Error caught by AbstractUIService */ }); }); } else { diff --git a/src/charging-station/ui-server/UIServerFactory.ts b/src/charging-station/ui-server/UIServerFactory.ts index 392d1ee0..2e524532 100644 --- a/src/charging-station/ui-server/UIServerFactory.ts +++ b/src/charging-station/ui-server/UIServerFactory.ts @@ -14,7 +14,6 @@ export default class UIServerFactory { } public static getUIServerImplementation( - applicationProtocol: ApplicationProtocol, uiServerConfiguration?: UIServerConfiguration ): AbstractUIServer | null { if (UIServiceUtils.isLoopback(uiServerConfiguration.options?.host) === false) { @@ -24,7 +23,7 @@ export default class UIServerFactory { ) ); } - switch (applicationProtocol) { + switch (uiServerConfiguration?.type ?? Configuration.getUIServer().type) { case ApplicationProtocol.WS: return new UIWebSocketServer(uiServerConfiguration ?? Configuration.getUIServer()); case ApplicationProtocol.HTTP: diff --git a/src/charging-station/ui-server/UIWebSocketServer.ts b/src/charging-station/ui-server/UIWebSocketServer.ts index c067fb36..03cd184d 100644 --- a/src/charging-station/ui-server/UIWebSocketServer.ts +++ b/src/charging-station/ui-server/UIWebSocketServer.ts @@ -3,7 +3,7 @@ import type internal from 'stream'; import { StatusCodes } from 'http-status-codes'; import * as uuid from 'uuid'; -import WebSocket, { RawData, WebSocketServer } from 'ws'; +import WebSocket, { type RawData, WebSocketServer } from 'ws'; import type { UIServerConfiguration } from '../../types/ConfigurationData'; import type { ProtocolRequest, ProtocolResponse } from '../../types/UIProtocol'; @@ -94,18 +94,28 @@ export default class UIWebSocketServer extends AbstractUIServer { public sendResponse(response: ProtocolResponse): void { const responseId = response[0]; - if (this.responseHandlers.has(responseId)) { - const ws = this.responseHandlers.get(responseId) as WebSocket; - if (ws?.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify(response)); + try { + if (this.responseHandlers.has(responseId)) { + const ws = this.responseHandlers.get(responseId) as WebSocket; + if (ws?.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(response)); + } + this.responseHandlers.delete(responseId); + } else { + logger.error( + `${this.logPrefix( + moduleName, + 'sendResponse' + )} Response for unknown request id: ${responseId}` + ); } - this.responseHandlers.delete(responseId); - } else { + } catch (error) { logger.error( `${this.logPrefix( moduleName, 'sendResponse' - )} Response for unknown request id: ${responseId}` + )} Error at sending response id '${responseId}':`, + error ); } } diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index c89601f6..2cfd85fa 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -3,16 +3,16 @@ import type OCPPError from '../../../exception/OCPPError'; import { Bootstrap } from '../../../internal'; import { ProcedureName, - ProtocolRequest, - ProtocolRequestHandler, - ProtocolVersion, - RequestPayload, - ResponsePayload, + type ProtocolRequest, + type ProtocolRequestHandler, + type ProtocolVersion, + type RequestPayload, + type ResponsePayload, ResponseStatus, } from '../../../types/UIProtocol'; -import type { +import { BroadcastChannelProcedureName, - BroadcastChannelRequestPayload, + type BroadcastChannelRequestPayload, } from '../../../types/WorkerBroadcastChannel'; import logger from '../../../utils/Logger'; import Utils from '../../../utils/Utils'; @@ -22,6 +22,26 @@ import type { AbstractUIServer } from '../AbstractUIServer'; const moduleName = 'AbstractUIService'; export default abstract class AbstractUIService { + protected static readonly ProcedureNameToBroadCastChannelProcedureNameMap: Omit< + Record, + 'startSimulator' | 'stopSimulator' | 'listChargingStations' + > = { + [ProcedureName.START_CHARGING_STATION]: BroadcastChannelProcedureName.START_CHARGING_STATION, + [ProcedureName.STOP_CHARGING_STATION]: BroadcastChannelProcedureName.STOP_CHARGING_STATION, + [ProcedureName.CLOSE_CONNECTION]: BroadcastChannelProcedureName.CLOSE_CONNECTION, + [ProcedureName.OPEN_CONNECTION]: BroadcastChannelProcedureName.OPEN_CONNECTION, + [ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR]: + BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, + [ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR]: + BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, + [ProcedureName.START_TRANSACTION]: BroadcastChannelProcedureName.START_TRANSACTION, + [ProcedureName.STOP_TRANSACTION]: BroadcastChannelProcedureName.STOP_TRANSACTION, + [ProcedureName.AUTHORIZE]: BroadcastChannelProcedureName.AUTHORIZE, + [ProcedureName.STATUS_NOTIFICATION]: BroadcastChannelProcedureName.STATUS_NOTIFICATION, + [ProcedureName.HEARTBEAT]: BroadcastChannelProcedureName.HEARTBEAT, + [ProcedureName.METER_VALUES]: BroadcastChannelProcedureName.METER_VALUES, + }; + protected readonly requestHandlers: Map; private readonly version: ProtocolVersion; private readonly uiServer: AbstractUIServer; @@ -29,8 +49,8 @@ export default abstract class AbstractUIService { private readonly broadcastChannelRequests: Map; constructor(uiServer: AbstractUIServer, version: ProtocolVersion) { - this.version = version; this.uiServer = uiServer; + this.version = version; this.requestHandlers = new Map([ [ProcedureName.LIST_CHARGING_STATIONS, this.handleListChargingStations.bind(this)], [ProcedureName.START_SIMULATOR, this.handleStartSimulator.bind(this)], @@ -134,6 +154,20 @@ export default abstract class AbstractUIService { this.broadcastChannelRequests.set(uuid, expectedNumberOfResponses); } + protected handleProtocolRequest( + uuid: string, + procedureName: ProcedureName, + payload: RequestPayload + ): void { + this.sendBroadcastChannelRequest( + uuid, + AbstractUIService.ProcedureNameToBroadCastChannelProcedureNameMap[ + procedureName + ] as BroadcastChannelProcedureName, + payload + ); + } + private handleListChargingStations(): ResponsePayload { return { status: ResponseStatus.SUCCESS, diff --git a/src/charging-station/ui-server/ui-services/UIService001.ts b/src/charging-station/ui-server/ui-services/UIService001.ts index ddbd54e9..a43d96c7 100644 --- a/src/charging-station/ui-server/ui-services/UIService001.ts +++ b/src/charging-station/ui-server/ui-services/UIService001.ts @@ -1,34 +1,12 @@ import { ProcedureName, - ProtocolRequestHandler, + type ProtocolRequestHandler, ProtocolVersion, - RequestPayload, } from '../../../types/UIProtocol'; -import { BroadcastChannelProcedureName } from '../../../types/WorkerBroadcastChannel'; import type { AbstractUIServer } from '../AbstractUIServer'; import AbstractUIService from './AbstractUIService'; export default class UIService001 extends AbstractUIService { - private static readonly ProcedureNameToBroadCastChannelProcedureNameMap: Omit< - Record, - 'startSimulator' | 'stopSimulator' | 'listChargingStations' - > = { - [ProcedureName.START_CHARGING_STATION]: BroadcastChannelProcedureName.START_CHARGING_STATION, - [ProcedureName.STOP_CHARGING_STATION]: BroadcastChannelProcedureName.STOP_CHARGING_STATION, - [ProcedureName.CLOSE_CONNECTION]: BroadcastChannelProcedureName.CLOSE_CONNECTION, - [ProcedureName.OPEN_CONNECTION]: BroadcastChannelProcedureName.OPEN_CONNECTION, - [ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR]: - BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, - [ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR]: - BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, - [ProcedureName.START_TRANSACTION]: BroadcastChannelProcedureName.START_TRANSACTION, - [ProcedureName.STOP_TRANSACTION]: BroadcastChannelProcedureName.STOP_TRANSACTION, - [ProcedureName.AUTHORIZE]: BroadcastChannelProcedureName.AUTHORIZE, - [ProcedureName.STATUS_NOTIFICATION]: BroadcastChannelProcedureName.STATUS_NOTIFICATION, - [ProcedureName.HEARTBEAT]: BroadcastChannelProcedureName.HEARTBEAT, - [ProcedureName.METER_VALUES]: BroadcastChannelProcedureName.METER_VALUES, - }; - constructor(uiServer: AbstractUIServer) { super(uiServer, ProtocolVersion['0.0.1']); this.requestHandlers.set( @@ -80,18 +58,4 @@ export default class UIService001 extends AbstractUIService { this.handleProtocolRequest.bind(this) as ProtocolRequestHandler ); } - - private handleProtocolRequest( - uuid: string, - procedureName: ProcedureName, - payload: RequestPayload - ): void { - this.sendBroadcastChannelRequest( - uuid, - UIService001.ProcedureNameToBroadCastChannelProcedureNameMap[ - procedureName - ] as BroadcastChannelProcedureName, - payload - ); - } } diff --git a/src/performance/PerformanceStatistics.ts b/src/performance/PerformanceStatistics.ts index 6a5b454f..6513bba5 100644 --- a/src/performance/PerformanceStatistics.ts +++ b/src/performance/PerformanceStatistics.ts @@ -222,12 +222,7 @@ export default class PerformanceStatistics { } private addPerformanceEntryToStatistics(entry: PerformanceEntry): void { - let entryName = entry.name; - // Rename entry name - const MAP_NAME: Record = {}; - if (MAP_NAME[entryName]) { - entryName = MAP_NAME[entryName]; - } + const entryName = entry.name; // Initialize command statistics if (!this.statistics.statisticsData.has(entryName)) { this.statistics.statisticsData.set(entryName, {}); -- 2.34.1