1 import type { IncomingMessage
, ServerResponse
} from
'node:http'
3 import { StatusCodes
} from
'http-status-codes'
5 import { BaseError
} from
'../../exception/index.js'
7 ApplicationProtocolVersion
,
12 type ProtocolResponse
,
16 type UIServerConfiguration
,
17 } from
'../../types/index.js'
25 } from
'../../utils/index.js'
26 import { AbstractUIServer
} from
'./AbstractUIServer.js'
27 import { isProtocolAndVersionSupported
} from
'./UIServerUtils.js'
29 const moduleName
= 'UIHttpServer'
38 export class UIHttpServer
extends AbstractUIServer
{
39 public constructor (protected override
readonly uiServerConfiguration
: UIServerConfiguration
) {
40 super(uiServerConfiguration
)
43 public logPrefix
= (modName
?: string, methodName
?: string, prefixSuffix
?: string): string => {
44 const logMsgPrefix
= prefixSuffix
!= null ? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server'
46 isNotEmptyString(modName
) && isNotEmptyString(methodName
)
47 ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
48 : ` ${logMsgPrefix} |`
49 return logPrefix(logMsg
)
52 public sendRequest (request
: ProtocolRequest
): void {
53 switch (this.uiServerConfiguration
.version
) {
54 case ApplicationProtocolVersion
.VERSION_20
:
55 this.httpServer
.emit('request', request
)
60 public sendResponse (response
: ProtocolResponse
): void {
61 const [uuid
, payload
] = response
63 if (this.hasResponseHandler(uuid
)) {
64 const res
= this.responseHandlers
.get(uuid
) as ServerResponse
66 .writeHead(this.responseStatusToStatusCode(payload
.status), {
67 'Content-Type': 'application/json',
69 .end(JSONStringify(payload
, undefined, MapStringifyFormat
.object
))
72 `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}`
77 `${this.logPrefix(moduleName, 'sendResponse')} Error at sending response id '${uuid}':`,
81 this.responseHandlers
.delete(uuid
)
85 public start (): void {
86 this.httpServer
.on('request', this.requestListener
.bind(this))
87 this.startHttpServer()
90 private requestListener (req
: IncomingMessage
, res
: ServerResponse
): void {
91 this.authenticate(req
, err
=> {
94 .writeHead(StatusCodes
.UNAUTHORIZED
, {
95 'Content-Type': 'text/plain',
96 'WWW-Authenticate': 'Basic realm=users',
98 .end(`${StatusCodes.UNAUTHORIZED.toString()} Unauthorized`)
103 // Expected request URL pathname: /ui/:version/:procedureName
104 const [protocol
, version
, procedureName
] = req
.url
?.split('/').slice(1) as [
109 const uuid
= generateUUID()
110 this.responseHandlers
.set(uuid
, res
)
112 const fullProtocol
= `${protocol}${version}`
113 if (!isProtocolAndVersionSupported(fullProtocol
)) {
114 throw new BaseError(`Unsupported UI protocol version: '${fullProtocol}'`)
116 this.registerProtocolVersionUIService(version
)
117 req
.on('error', error
=> {
119 `${this.logPrefix(moduleName, 'requestListener.req.onerror')} Error on HTTP request:`,
123 if (req
.method
=== HttpMethods
.POST
) {
124 const bodyBuffer
: Uint8Array
[] = []
126 .on('data', (chunk
: Uint8Array
) => {
127 bodyBuffer
.push(chunk
)
130 let requestPayload
: RequestPayload
| undefined
132 requestPayload
= JSON
.parse(Buffer
.concat(bodyBuffer
).toString()) as RequestPayload
135 this.buildProtocolResponse(uuid
, {
136 errorMessage
: (error
as Error).message
,
137 errorStack
: (error
as Error).stack
,
138 status: ResponseStatus
.FAILURE
,
145 ?.requestHandler(this.buildProtocolRequest(uuid
, procedureName
, requestPayload
))
146 .then((protocolResponse
?: ProtocolResponse
) => {
147 if (protocolResponse
!= null) {
148 this.sendResponse(protocolResponse
)
152 .catch(Constants
.EMPTY_FUNCTION
)
155 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
156 throw new BaseError(`Unsupported HTTP method: '${req.method}'`)
160 `${this.logPrefix(moduleName, 'requestListener')} Handle HTTP request error:`,
163 this.sendResponse(this.buildProtocolResponse(uuid
, { status: ResponseStatus
.FAILURE
}))
167 private responseStatusToStatusCode (status: ResponseStatus
): StatusCodes
{
169 case ResponseStatus
.FAILURE
:
170 return StatusCodes
.BAD_REQUEST
171 case ResponseStatus
.SUCCESS
:
172 return StatusCodes
.OK
174 return StatusCodes
.INTERNAL_SERVER_ERROR