1 import type { IncomingMessage
, RequestListener
, ServerResponse
} from
'node:http'
3 import { StatusCodes
} from
'http-status-codes'
5 import { AbstractUIServer
} from
'./AbstractUIServer.js'
6 import { UIServerUtils
} from
'./UIServerUtils.js'
7 import { BaseError
} from
'../../exception/index.js'
9 ApplicationProtocolVersion
,
13 type ProtocolResponse
,
17 type UIServerConfiguration
18 } from
'../../types/index.js'
19 import { Constants
, generateUUID
, isNotEmptyString
, logPrefix
, logger
} from
'../../utils/index.js'
21 const moduleName
= 'UIHttpServer'
30 export class UIHttpServer
extends AbstractUIServer
{
31 public constructor (protected readonly uiServerConfiguration
: UIServerConfiguration
) {
32 super(uiServerConfiguration
)
35 public start (): void {
36 this.httpServer
.on('request', this.requestListener
.bind(this) as RequestListener
)
37 this.startHttpServer()
40 public sendRequest (request
: ProtocolRequest
): void {
41 switch (this.uiServerConfiguration
.version
) {
42 case ApplicationProtocolVersion
.VERSION_20
:
43 this.httpServer
.emit('request', request
)
48 public sendResponse (response
: ProtocolResponse
): void {
49 const [uuid
, payload
] = response
51 if (this.hasResponseHandler(uuid
)) {
52 const res
= this.responseHandlers
.get(uuid
) as ServerResponse
54 .writeHead(this.responseStatusToStatusCode(payload
.status), {
55 'Content-Type': 'application/json'
57 .end(JSON
.stringify(payload
))
60 `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}`
65 `${this.logPrefix(moduleName, 'sendResponse')} Error at sending response id '${uuid}':`,
69 this.responseHandlers
.delete(uuid
)
73 public logPrefix
= (modName
?: string, methodName
?: string, prefixSuffix
?: string): string => {
74 const logMsgPrefix
= prefixSuffix
!= null ? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server'
76 isNotEmptyString(modName
) && isNotEmptyString(methodName
)
77 ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
78 : ` ${logMsgPrefix} |`
79 return logPrefix(logMsg
)
82 private requestListener (req
: IncomingMessage
, res
: ServerResponse
): void {
83 this.authenticate(req
, (err
) => {
86 .writeHead(StatusCodes
.UNAUTHORIZED
, {
87 'Content-Type': 'text/plain',
88 'WWW-Authenticate': 'Basic realm=users'
90 .end(`${StatusCodes.UNAUTHORIZED} Unauthorized`)
95 // Expected request URL pathname: /ui/:version/:procedureName
96 const [protocol
, version
, procedureName
] = req
.url
?.split('/').slice(1) as [
101 const uuid
= generateUUID()
102 this.responseHandlers
.set(uuid
, res
)
104 const fullProtocol
= `${protocol}${version}`
105 if (!UIServerUtils
.isProtocolAndVersionSupported(fullProtocol
)) {
106 throw new BaseError(`Unsupported UI protocol version: '${fullProtocol}'`)
108 this.registerProtocolVersionUIService(version
)
109 req
.on('error', (error
) => {
111 `${this.logPrefix(moduleName, 'requestListener.req.onerror')} Error on HTTP request:`,
115 if (req
.method
=== HttpMethods
.POST
) {
116 const bodyBuffer
: Uint8Array
[] = []
118 .on('data', (chunk
: Uint8Array
) => {
119 bodyBuffer
.push(chunk
)
122 const body
= JSON
.parse(Buffer
.concat(bodyBuffer
).toString()) as RequestPayload
125 ?.requestHandler(this.buildProtocolRequest(uuid
, procedureName
, body
))
126 .then((protocolResponse
?: ProtocolResponse
) => {
127 if (protocolResponse
!= null) {
128 this.sendResponse(protocolResponse
)
131 .catch(Constants
.EMPTY_FUNCTION
)
134 throw new BaseError(`Unsupported HTTP method: '${req.method}'`)
138 `${this.logPrefix(moduleName, 'requestListener')} Handle HTTP request error:`,
141 this.sendResponse(this.buildProtocolResponse(uuid
, { status: ResponseStatus
.FAILURE
}))
145 private responseStatusToStatusCode (status: ResponseStatus
): StatusCodes
{
147 case ResponseStatus
.SUCCESS
:
148 return StatusCodes
.OK
149 case ResponseStatus
.FAILURE
:
150 return StatusCodes
.BAD_REQUEST
152 return StatusCodes
.INTERNAL_SERVER_ERROR