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 readonly uiServerConfiguration
: UIServerConfiguration
) {
40 super(uiServerConfiguration
)
43 public start (): void {
44 this.httpServer
.on('request', this.requestListener
.bind(this))
45 this.startHttpServer()
48 public sendRequest (request
: ProtocolRequest
): void {
49 switch (this.uiServerConfiguration
.version
) {
50 case ApplicationProtocolVersion
.VERSION_20
:
51 this.httpServer
.emit('request', request
)
56 public sendResponse (response
: ProtocolResponse
): void {
57 const [uuid
, payload
] = response
59 if (this.hasResponseHandler(uuid
)) {
60 const res
= this.responseHandlers
.get(uuid
) as ServerResponse
62 .writeHead(this.responseStatusToStatusCode(payload
.status), {
63 'Content-Type': 'application/json'
65 .end(JSONStringify(payload
, undefined, MapStringifyFormat
.object
))
68 `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}`
73 `${this.logPrefix(moduleName, 'sendResponse')} Error at sending response id '${uuid}':`,
77 this.responseHandlers
.delete(uuid
)
81 public logPrefix
= (modName
?: string, methodName
?: string, prefixSuffix
?: string): string => {
82 const logMsgPrefix
= prefixSuffix
!= null ? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server'
84 isNotEmptyString(modName
) && isNotEmptyString(methodName
)
85 ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
86 : ` ${logMsgPrefix} |`
87 return logPrefix(logMsg
)
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} 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 status: ResponseStatus
.FAILURE
,
137 errorMessage
: (error
as Error).message
,
138 errorStack
: (error
as Error).stack
145 ?.requestHandler(this.buildProtocolRequest(uuid
, procedureName
, requestPayload
))
146 .then((protocolResponse
?: ProtocolResponse
) => {
147 if (protocolResponse
!= null) {
148 this.sendResponse(protocolResponse
)
151 .catch(Constants
.EMPTY_FUNCTION
)
154 throw new BaseError(`Unsupported HTTP method: '${req.method}'`)
158 `${this.logPrefix(moduleName, 'requestListener')} Handle HTTP request error:`,
161 this.sendResponse(this.buildProtocolResponse(uuid
, { status: ResponseStatus
.FAILURE
}))
165 private responseStatusToStatusCode (status: ResponseStatus
): StatusCodes
{
167 case ResponseStatus
.SUCCESS
:
168 return StatusCodes
.OK
169 case ResponseStatus
.FAILURE
:
170 return StatusCodes
.BAD_REQUEST
172 return StatusCodes
.INTERNAL_SERVER_ERROR