1 import type { IncomingMessage
, RequestListener
, ServerResponse
} from
'node:http';
3 import { StatusCodes
} from
'http-status-codes';
5 import { AbstractUIServer
} from
'./AbstractUIServer';
6 import { UIServerUtils
} from
'./UIServerUtils';
7 import { BaseError
} from
'../../exception';
9 ApplicationProtocolVersion
,
13 type ProtocolResponse
,
17 type UIServerConfiguration
,
28 const moduleName
= 'UIHttpServer';
37 export class UIHttpServer
extends AbstractUIServer
{
38 public constructor(protected readonly uiServerConfiguration
: UIServerConfiguration
) {
39 super(uiServerConfiguration
);
42 public start(): void {
43 this.httpServer
.on('request', this.requestListener
.bind(this) as RequestListener
);
44 this.startHttpServer();
47 public sendRequest(request
: ProtocolRequest
): void {
48 switch (this.uiServerConfiguration
.version
) {
49 case ApplicationProtocolVersion
.VERSION_20
:
50 this.httpServer
.emit('request', request
);
55 public sendResponse(response
: ProtocolResponse
): void {
56 const [uuid
, payload
] = response
;
58 if (this.hasResponseHandler(uuid
) === true) {
59 const res
= this.responseHandlers
.get(uuid
) as ServerResponse
;
61 .writeHead(this.responseStatusToStatusCode(payload
.status), {
62 'Content-Type': 'application/json',
64 .end(JSON
.stringify(payload
));
67 `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}`,
72 `${this.logPrefix(moduleName, 'sendResponse')} Error at sending response id '${uuid}':`,
76 this.responseHandlers
.delete(uuid
);
80 public logPrefix
= (modName
?: string, methodName
?: string, prefixSuffix
?: string): string => {
81 const logMsgPrefix
= prefixSuffix
? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server';
83 isNotEmptyString(modName
) && isNotEmptyString(methodName
)
84 ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
85 : ` ${logMsgPrefix} |`;
86 return logPrefix(logMsg
);
89 private requestListener(req
: IncomingMessage
, res
: ServerResponse
): void {
90 this.authenticate(req
, (err
) => {
93 .writeHead(StatusCodes
.UNAUTHORIZED
, {
94 'Content-Type': 'text/plain',
95 'WWW-Authenticate': 'Basic realm=users',
97 .end(`${StatusCodes.UNAUTHORIZED} Unauthorized`)
102 // Expected request URL pathname: /ui/:version/:procedureName
103 const [protocol
, version
, procedureName
] = req
.url
?.split('/').slice(1) as [
108 const uuid
= generateUUID();
109 this.responseHandlers
.set(uuid
, res
);
111 const fullProtocol
= `${protocol}${version}`;
112 if (UIServerUtils
.isProtocolAndVersionSupported(fullProtocol
) === false) {
113 throw new BaseError(`Unsupported UI protocol version: '${fullProtocol}'`);
115 this.registerProtocolVersionUIService(version
);
116 req
.on('error', (error
) => {
118 `${this.logPrefix(moduleName, 'requestListener.req.onerror')} Error on HTTP request:`,
122 if (req
.method
=== HttpMethods
.POST
) {
123 const bodyBuffer
: Uint8Array
[] = [];
125 .on('data', (chunk
: Uint8Array
) => {
126 bodyBuffer
.push(chunk
);
129 const body
= JSON
.parse(Buffer
.concat(bodyBuffer
).toString()) as RequestPayload
;
133 this.buildProtocolRequest(
136 body
?? Constants
.EMPTY_FREEZED_OBJECT
,
139 .then((protocolResponse
?: ProtocolResponse
) => {
140 if (!isNullOrUndefined(protocolResponse
)) {
141 this.sendResponse(protocolResponse
!);
144 .catch(Constants
.EMPTY_FUNCTION
);
147 throw new BaseError(`Unsupported HTTP method: '${req.method}'`);
151 `${this.logPrefix(moduleName, 'requestListener')} Handle HTTP request error:`,
154 this.sendResponse(this.buildProtocolResponse(uuid
, { status: ResponseStatus
.FAILURE
}));
158 private responseStatusToStatusCode(status: ResponseStatus
): StatusCodes
{
160 case ResponseStatus
.SUCCESS
:
161 return StatusCodes
.OK
;
162 case ResponseStatus
.FAILURE
:
163 return StatusCodes
.BAD_REQUEST
;
165 return StatusCodes
.INTERNAL_SERVER_ERROR
;