-import { IncomingMessage, RequestListener, Server, ServerResponse } from 'http';
+import type { IncomingMessage, RequestListener, ServerResponse } from 'node:http';
import { StatusCodes } from 'http-status-codes';
-import BaseError from '../../exception/BaseError';
-import type { UIServerConfiguration } from '../../types/ConfigurationData';
+import { AbstractUIServer } from './AbstractUIServer';
+import { UIServerUtils } from './UIServerUtils';
+import { BaseError } from '../../exception';
import {
- ProcedureName,
- Protocol,
- ProtocolRequest,
- ProtocolResponse,
- ProtocolVersion,
- RequestPayload,
+ ApplicationProtocolVersion,
+ type ProcedureName,
+ type Protocol,
+ type ProtocolRequest,
+ type ProtocolResponse,
+ type ProtocolVersion,
+ type RequestPayload,
ResponseStatus,
-} from '../../types/UIProtocol';
-import logger from '../../utils/Logger';
-import Utils from '../../utils/Utils';
-import { AbstractUIServer } from './AbstractUIServer';
-import UIServiceFactory from './ui-services/UIServiceFactory';
-import { UIServiceUtils } from './ui-services/UIServiceUtils';
+ type UIServerConfiguration,
+} from '../../types';
+import {
+ Constants,
+ generateUUID,
+ isNotEmptyString,
+ isNullOrUndefined,
+ logPrefix,
+ logger,
+} from '../../utils';
const moduleName = 'UIHttpServer';
-type responseHandler = { procedureName: ProcedureName; res: ServerResponse };
-
-export default class UIHttpServer extends AbstractUIServer {
- private readonly responseHandlers: Map<string, responseHandler>;
+enum HttpMethods {
+ GET = 'GET',
+ PUT = 'PUT',
+ POST = 'POST',
+ PATCH = 'PATCH',
+}
+export class UIHttpServer extends AbstractUIServer {
public constructor(protected readonly uiServerConfiguration: UIServerConfiguration) {
super(uiServerConfiguration);
- this.httpServer = new Server(this.requestListener.bind(this) as RequestListener);
- this.responseHandlers = new Map<string, responseHandler>();
}
public start(): void {
- if (this.httpServer.listening === false) {
- this.httpServer.listen(this.uiServerConfiguration.options);
- }
+ this.httpServer.on('request', this.requestListener.bind(this) as RequestListener);
+ this.startHttpServer();
}
- public stop(): void {
- this.chargingStations.clear();
- }
-
- // 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 {
const [uuid, payload] = response;
- const statusCode = this.responseStatusToStatusCode(payload.status);
- if (this.responseHandlers.has(uuid) === true) {
- const { res } = this.responseHandlers.get(uuid);
- res.writeHead(statusCode, { 'Content-Type': 'application/json' });
- res.write(JSON.stringify(payload));
- res.end();
- this.responseHandlers.delete(uuid);
- } else {
+ try {
+ if (this.hasResponseHandler(uuid) === true) {
+ const res = this.responseHandlers.get(uuid) as ServerResponse;
+ res
+ .writeHead(this.responseStatusToStatusCode(payload.status), {
+ 'Content-Type': 'application/json',
+ })
+ .end(JSON.stringify(payload));
+ } 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,
);
+ } finally {
+ this.responseHandlers.delete(uuid);
}
}
- public logPrefix(modName?: string, methodName?: string, prefixSuffix?: string): string {
+ public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
const logMsgPrefix = prefixSuffix ? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server';
const logMsg =
- modName && methodName ? ` ${logMsgPrefix} | ${modName}.${methodName}:` : ` ${logMsgPrefix} |`;
- return Utils.logPrefix(logMsg);
- }
+ isNotEmptyString(modName) && isNotEmptyString(methodName)
+ ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
+ : ` ${logMsgPrefix} |`;
+ return logPrefix(logMsg);
+ };
private requestListener(req: IncomingMessage, res: ServerResponse): void {
- if (this.isBasicAuthEnabled() === true && this.isValidBasicAuth(req) === false) {
- res.setHeader('Content-Type', 'text/plain');
- res.setHeader('WWW-Authenticate', 'Basic realm=users');
- res.writeHead(StatusCodes.UNAUTHORIZED);
- res.end(`${StatusCodes.UNAUTHORIZED} Unauthorized`);
- return;
- }
+ this.authenticate(req, (err) => {
+ if (err) {
+ res
+ .writeHead(StatusCodes.UNAUTHORIZED, {
+ 'Content-Type': 'text/plain',
+ 'WWW-Authenticate': 'Basic realm=users',
+ })
+ .end(`${StatusCodes.UNAUTHORIZED} Unauthorized`)
+ .destroy();
+ req.destroy();
+ }
+ });
// Expected request URL pathname: /ui/:version/:procedureName
const [protocol, version, procedureName] = req.url?.split('/').slice(1) as [
Protocol,
ProtocolVersion,
- ProcedureName
+ ProcedureName,
];
- const uuid = Utils.generateUUID();
- this.responseHandlers.set(uuid, { procedureName, res });
+ const uuid = generateUUID();
+ this.responseHandlers.set(uuid, res);
try {
- if (UIServiceUtils.isProtocolAndVersionSupported(protocol, version) === false) {
- throw new BaseError(`Unsupported UI protocol version: '/${protocol}/${version}'`);
+ const fullProtocol = `${protocol}${version}`;
+ if (UIServerUtils.isProtocolAndVersionSupported(fullProtocol) === false) {
+ throw new BaseError(`Unsupported UI protocol version: '${fullProtocol}'`);
}
+ this.registerProtocolVersionUIService(version);
req.on('error', (error) => {
logger.error(
`${this.logPrefix(moduleName, 'requestListener.req.onerror')} Error on HTTP request:`,
- error
+ error,
);
});
- if (this.uiServices.has(version) === false) {
- this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this));
- }
- if (req.method === 'POST') {
- const bodyBuffer = [];
+ if (req.method === HttpMethods.POST) {
+ const bodyBuffer: Uint8Array[] = [];
req
- .on('data', (chunk) => {
+ .on('data', (chunk: Uint8Array) => {
bodyBuffer.push(chunk);
})
.on('end', () => {
const body = JSON.parse(Buffer.concat(bodyBuffer).toString()) as RequestPayload;
this.uiServices
.get(version)
- .requestHandler(this.buildProtocolRequest(uuid, procedureName, body ?? {}))
- .catch(() => {
- this.sendResponse(
- this.buildProtocolResponse(uuid, { status: ResponseStatus.FAILURE })
- );
- });
+ ?.requestHandler(
+ this.buildProtocolRequest(
+ uuid,
+ procedureName,
+ body ?? Constants.EMPTY_FREEZED_OBJECT,
+ ),
+ )
+ .then((protocolResponse?: ProtocolResponse) => {
+ if (!isNullOrUndefined(protocolResponse)) {
+ this.sendResponse(protocolResponse!);
+ }
+ })
+ .catch(Constants.EMPTY_FUNCTION);
});
} else {
throw new BaseError(`Unsupported HTTP method: '${req.method}'`);
} catch (error) {
logger.error(
`${this.logPrefix(moduleName, 'requestListener')} Handle HTTP request error:`,
- error
+ error,
);
this.sendResponse(this.buildProtocolResponse(uuid, { status: ResponseStatus.FAILURE }));
}