refactor: revert internal exports
[e-mobility-charging-stations-simulator.git] / src / charging-station / ui-server / UIHttpServer.ts
1 import type { IncomingMessage, RequestListener, ServerResponse } from 'node:http';
2
3 import { StatusCodes } from 'http-status-codes';
4
5 import { AbstractUIServer } from './AbstractUIServer';
6 import { UIServerUtils } from './UIServerUtils';
7 import { BaseError } from '../../exception';
8 import {
9 type ProcedureName,
10 type Protocol,
11 type ProtocolRequest,
12 type ProtocolResponse,
13 type ProtocolVersion,
14 type RequestPayload,
15 ResponseStatus,
16 type UIServerConfiguration,
17 } from '../../types';
18 import { Constants, Utils, logger } from '../../utils';
19
20 const moduleName = 'UIHttpServer';
21
22 enum HttpMethods {
23 GET = 'GET',
24 PUT = 'PUT',
25 POST = 'POST',
26 PATCH = 'PATCH',
27 }
28
29 export class UIHttpServer extends AbstractUIServer {
30 public constructor(protected readonly uiServerConfiguration: UIServerConfiguration) {
31 super(uiServerConfiguration);
32 }
33
34 public start(): void {
35 this.httpServer.on('request', this.requestListener.bind(this) as RequestListener);
36 this.startHttpServer();
37 }
38
39 // eslint-disable-next-line @typescript-eslint/no-unused-vars
40 public sendRequest(request: ProtocolRequest): void {
41 // This is intentionally left blank
42 }
43
44 public sendResponse(response: ProtocolResponse): void {
45 const [uuid, payload] = response;
46 try {
47 if (this.responseHandlers.has(uuid) === true) {
48 const res = this.responseHandlers.get(uuid) as ServerResponse;
49 res
50 .writeHead(this.responseStatusToStatusCode(payload.status), {
51 'Content-Type': 'application/json',
52 })
53 .end(JSON.stringify(payload));
54 } else {
55 logger.error(
56 `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}`
57 );
58 }
59 } catch (error) {
60 logger.error(
61 `${this.logPrefix(moduleName, 'sendResponse')} Error at sending response id '${uuid}':`,
62 error
63 );
64 } finally {
65 this.responseHandlers.delete(uuid);
66 }
67 }
68
69 public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
70 const logMsgPrefix = prefixSuffix ? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server';
71 const logMsg =
72 Utils.isNotEmptyString(modName) && Utils.isNotEmptyString(methodName)
73 ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
74 : ` ${logMsgPrefix} |`;
75 return Utils.logPrefix(logMsg);
76 };
77
78 private requestListener(req: IncomingMessage, res: ServerResponse): void {
79 this.authenticate(req, (err) => {
80 if (err) {
81 res
82 .writeHead(StatusCodes.UNAUTHORIZED, {
83 'Content-Type': 'text/plain',
84 'WWW-Authenticate': 'Basic realm=users',
85 })
86 .end(`${StatusCodes.UNAUTHORIZED} Unauthorized`)
87 .destroy();
88 req.destroy();
89 }
90 });
91 // Expected request URL pathname: /ui/:version/:procedureName
92 const [protocol, version, procedureName] = req.url?.split('/').slice(1) as [
93 Protocol,
94 ProtocolVersion,
95 ProcedureName
96 ];
97 const uuid = Utils.generateUUID();
98 this.responseHandlers.set(uuid, res);
99 try {
100 const fullProtocol = `${protocol}${version}`;
101 if (UIServerUtils.isProtocolAndVersionSupported(fullProtocol) === false) {
102 throw new BaseError(`Unsupported UI protocol version: '${fullProtocol}'`);
103 }
104 this.registerProtocolVersionUIService(version);
105 req.on('error', (error) => {
106 logger.error(
107 `${this.logPrefix(moduleName, 'requestListener.req.onerror')} Error on HTTP request:`,
108 error
109 );
110 });
111 if (req.method === HttpMethods.POST) {
112 const bodyBuffer = [];
113 req
114 .on('data', (chunk) => {
115 bodyBuffer.push(chunk);
116 })
117 .on('end', () => {
118 const body = JSON.parse(Buffer.concat(bodyBuffer).toString()) as RequestPayload;
119 this.uiServices
120 .get(version)
121 ?.requestHandler(
122 this.buildProtocolRequest(
123 uuid,
124 procedureName,
125 body ?? Constants.EMPTY_FREEZED_OBJECT
126 )
127 )
128 .catch(Constants.EMPTY_FUNCTION);
129 });
130 } else {
131 throw new BaseError(`Unsupported HTTP method: '${req.method}'`);
132 }
133 } catch (error) {
134 logger.error(
135 `${this.logPrefix(moduleName, 'requestListener')} Handle HTTP request error:`,
136 error
137 );
138 this.sendResponse(this.buildProtocolResponse(uuid, { status: ResponseStatus.FAILURE }));
139 }
140 }
141
142 private responseStatusToStatusCode(status: ResponseStatus): StatusCodes {
143 switch (status) {
144 case ResponseStatus.SUCCESS:
145 return StatusCodes.OK;
146 case ResponseStatus.FAILURE:
147 return StatusCodes.BAD_REQUEST;
148 default:
149 return StatusCodes.INTERNAL_SERVER_ERROR;
150 }
151 }
152 }