UI Server: Improve error handling
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 9 Sep 2022 05:12:20 +0000 (07:12 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 9 Sep 2022 05:12:20 +0000 (07:12 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/assets/ui-protocol/Insomnia_CSSimulatorUIProtocol.json
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/Bootstrap.ts
src/charging-station/ui-server/AbstractUIServer.ts
src/charging-station/ui-server/UIHttpServer.ts
src/charging-station/ui-server/UIServerFactory.ts
src/charging-station/ui-server/UIWebSocketServer.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/charging-station/ui-server/ui-services/UIService001.ts
src/performance/PerformanceStatistics.ts

index 613aa1da408d138c411cfc9ded4a9f74197ab34a..7cec46760c47b61a919322b5ae750c935e190429 100644 (file)
@@ -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",
     {
       "_id": "req_aad7fd6db4c64869b60048b915010efc",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675784826,
+      "modified": 1662699999845,
       "created": 1661789624998,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/startChargingStation",
       "name": "startChargingStation",
     {
       "_id": "req_d72d91cf3fb044179b8ae9d92a74f99c",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662674004751,
+      "modified": 1662699974387,
       "created": 1661789625002,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopChargingStation",
       "name": "stopChargingStation",
     {
       "_id": "req_747f458d196f4681b5fe15204b0067aa",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675794900,
+      "modified": 1662699923383,
       "created": 1661789625005,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/openConnection",
       "name": "openConnection",
     {
       "_id": "req_401e6a62a33c4b6c90aaa2e019daab6d",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675791687,
+      "modified": 1662699980072,
       "created": 1661789625014,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/closeConnection",
       "name": "closeConnection",
     {
       "_id": "req_2f757efe92fb4936ad4fa4b6763f9293",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675797662,
+      "modified": 1662699966562,
       "created": 1661789625017,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/startTransaction",
       "name": "startTransaction",
         "username": "{{username}}",
         "password": "{{password}}"
       },
-      "metaSortKey": -999999750,
+      "metaSortKey": -999999587.5,
       "isPrivate": false,
       "settingStoreCookies": true,
       "settingSendCookies": true,
     {
       "_id": "req_7c285fb6cb6948a08235a6c73cbeb1f9",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675801164,
+      "modified": 1662699963660,
       "created": 1661789625020,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopTransaction",
       "name": "stopTransaction",
         "username": "{{username}}",
         "password": "{{password}}"
       },
-      "metaSortKey": -999999700,
+      "metaSortKey": -999999575,
       "isPrivate": false,
       "settingStoreCookies": true,
       "settingSendCookies": true,
     {
       "_id": "req_b33c704fe3464dc5a5d3694abd9320d0",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675804146,
+      "modified": 1662699972371,
       "created": 1661803778569,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/startAutomaticTransactionGenerator",
       "name": "startAutomaticTransactionGenerator",
     {
       "_id": "req_24c1c55fe3ba4ddb94702408f21a64df",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675807062,
+      "modified": 1662699969481,
       "created": 1661803846882,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopAutomaticTransactionGenerator",
       "name": "stopAutomaticTransactionGenerator",
     {
       "_id": "req_6a78267706094fb59d85ed1531e07a55",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675809951,
+      "modified": 1662699960920,
       "created": 1662330215407,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/statusNotification",
       "name": "statusNotification",
     {
       "_id": "req_61efafe9f4a14c268b948b9f9c5c4195",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675812274,
+      "modified": 1662699996869,
       "created": 1662409405256,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/heartbeat",
       "name": "heartbeat",
     {
       "_id": "req_9633f79d949d491e8b6892eed08bd198",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675815887,
+      "modified": 1662700003452,
       "created": 1662648910935,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/authorize",
       "name": "authorize",
     {
       "_id": "req_5874d988a93a4d28b860b7ab65e534e0",
       "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205",
-      "modified": 1662675817875,
+      "modified": 1662700004341,
       "created": 1662673259612,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/meterValues",
       "name": "meterValues",
index 54b409ed39b7837cc7d988de8719e18512ee4add..706a4b8d900ccaa327d5811dc91d8587b9a640ce 100644 (file)
@@ -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';
index 8734ed2284b38c5c0e8706cbb86e8afa013c1d0c..6dc6620d71dd4fed3f834dca67b53ebba6e68e18 100644 (file)
@@ -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,
index dad1e0cb9353fae919b08cc18d6006bfd6c262da..d8ef6340296b5b2ddefa4dbd471529e197a72f40 100644 (file)
@@ -18,8 +18,8 @@ import UIServiceFactory from './ui-services/UIServiceFactory';
 
 export abstract class AbstractUIServer {
   public readonly chargingStations: Map<string, ChargingStationData>;
-  protected httpServer: Server;
-  protected responseHandlers: Map<string, ServerResponse | WebSocket>;
+  protected readonly httpServer: Server;
+  protected readonly responseHandlers: Map<string, ServerResponse | WebSocket>;
   protected readonly uiServices: Map<ProtocolVersion, AbstractUIService>;
 
   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;
index 6ae03d5d90ce2ace0a903db63ce24f8b101d6842..7dfff0adf5c587284d2c32663fcd4d24ddafc423 100644 (file)
@@ -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 {
index 392d1ee010f6ab1447cea880fdd311f8d19c21f7..2e5245329b9b70485a8796f92bd2b661124c602d 100644 (file)
@@ -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:
index c067fb362a8c8b717312bcff35a049135aac2a23..03cd184ddc81a106fb5a72c17ec35aabc7290014 100644 (file)
@@ -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
       );
     }
   }
index c89601f6dd4c2cff0b83a2c9c5e353fd6c34cb5b..2cfd85fa97081eafa45116a2d5f886c1ef4f97a2 100644 (file)
@@ -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<ProcedureName, BroadcastChannelProcedureName>,
+    '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<ProcedureName, ProtocolRequestHandler>;
   private readonly version: ProtocolVersion;
   private readonly uiServer: AbstractUIServer;
@@ -29,8 +49,8 @@ export default abstract class AbstractUIService {
   private readonly broadcastChannelRequests: Map<string, number>;
 
   constructor(uiServer: AbstractUIServer, version: ProtocolVersion) {
-    this.version = version;
     this.uiServer = uiServer;
+    this.version = version;
     this.requestHandlers = new Map<ProcedureName, ProtocolRequestHandler>([
       [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,
index ddbd54e9250ba9d4d4d3d27c0bdffddaa3692ab6..a43d96c7ab2ad0211115bbaaabffa7d529d14932 100644 (file)
@@ -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<ProcedureName, BroadcastChannelProcedureName>,
-    '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
-    );
-  }
 }
index 6a5b454f9c5d868f74ad3bc62c606324adfebe9e..6513bba5e98ade3a08b00f8e3fb41680c5a66296 100644 (file)
@@ -222,12 +222,7 @@ export default class PerformanceStatistics {
   }
 
   private addPerformanceEntryToStatistics(entry: PerformanceEntry): void {
-    let entryName = entry.name;
-    // Rename entry name
-    const MAP_NAME: Record<string, string> = {};
-    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, {});