1 import type { IncomingMessage
, ServerResponse
} from
'node:http'
3 import { StatusCodes
} from
'http-status-codes'
5 import { BaseError
} from
'../../exception/index.js'
7 ApplicationProtocolVersion
,
11 type ProtocolResponse
,
15 type UIServerConfiguration
16 } from
'../../types/index.js'
21 JSONStringifyWithMapSupport
,
24 } from
'../../utils/index.js'
25 import { AbstractUIServer
} from
'./AbstractUIServer.js'
26 import { isProtocolAndVersionSupported
} from
'./UIServerUtils.js'
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))
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
)) {
59 const res
= this.responseHandlers
.get(uuid
) as ServerResponse
61 .writeHead(this.responseStatusToStatusCode(payload
.status), {
62 'Content-Type': 'application/json'
64 .end(JSONStringifyWithMapSupport(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
!= null ? `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 (!isProtocolAndVersionSupported(fullProtocol
)) {
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 let requestPayload
: RequestPayload
| undefined
131 requestPayload
= JSON
.parse(Buffer
.concat(bodyBuffer
).toString()) as RequestPayload
134 this.buildProtocolResponse(uuid
, {
135 status: ResponseStatus
.FAILURE
,
136 errorMessage
: (error
as Error).message
,
137 errorStack
: (error
as Error).stack
144 ?.requestHandler(this.buildProtocolRequest(uuid
, procedureName
, requestPayload
))
145 .then((protocolResponse
?: ProtocolResponse
) => {
146 if (protocolResponse
!= null) {
147 this.sendResponse(protocolResponse
)
150 .catch(Constants
.EMPTY_FUNCTION
)
153 throw new BaseError(`Unsupported HTTP method: '${req.method}'`)
157 `${this.logPrefix(moduleName, 'requestListener')} Handle HTTP request error:`,
160 this.sendResponse(this.buildProtocolResponse(uuid
, { status: ResponseStatus
.FAILURE
}))
164 private responseStatusToStatusCode (status: ResponseStatus
): StatusCodes
{
166 case ResponseStatus
.SUCCESS
:
167 return StatusCodes
.OK
168 case ResponseStatus
.FAILURE
:
169 return StatusCodes
.BAD_REQUEST
171 return StatusCodes
.INTERNAL_SERVER_ERROR