mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-21 07:46:24 +00:00
TSK-728: Server reconnect support (#2689)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
a089cb9198
commit
63d0483ab9
@ -24,6 +24,7 @@ import core, {
|
|||||||
Domain,
|
Domain,
|
||||||
FindOptions,
|
FindOptions,
|
||||||
FindResult,
|
FindResult,
|
||||||
|
generateId,
|
||||||
Ref,
|
Ref,
|
||||||
Tx,
|
Tx,
|
||||||
TxHandler,
|
TxHandler,
|
||||||
@ -48,6 +49,7 @@ class Connection implements ClientConnection {
|
|||||||
private readonly requests = new Map<ReqId, DeferredPromise>()
|
private readonly requests = new Map<ReqId, DeferredPromise>()
|
||||||
private lastId = 0
|
private lastId = 0
|
||||||
private readonly interval: number
|
private readonly interval: number
|
||||||
|
private readonly sessionId = generateId() as string
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly url: string,
|
private readonly url: string,
|
||||||
@ -106,7 +108,7 @@ class Connection implements ClientConnection {
|
|||||||
const clientSocketFactory =
|
const clientSocketFactory =
|
||||||
getMetadata(client.metadata.ClientSocketFactory) ?? ((url: string) => new WebSocket(url) as ClientSocket)
|
getMetadata(client.metadata.ClientSocketFactory) ?? ((url: string) => new WebSocket(url) as ClientSocket)
|
||||||
|
|
||||||
const websocket = clientSocketFactory(this.url)
|
const websocket = clientSocketFactory(this.url + `?sessionId=${this.sessionId}`)
|
||||||
const socketId = this.sockets++
|
const socketId = this.sockets++
|
||||||
websocket.onmessage = (event: MessageEvent) => {
|
websocket.onmessage = (event: MessageEvent) => {
|
||||||
const resp = readResponse(event.data)
|
const resp = readResponse(event.data)
|
||||||
|
@ -58,7 +58,8 @@ class SessionManager {
|
|||||||
ws: WebSocket,
|
ws: WebSocket,
|
||||||
token: Token,
|
token: Token,
|
||||||
pipelineFactory: PipelineFactory,
|
pipelineFactory: PipelineFactory,
|
||||||
productId: string
|
productId: string,
|
||||||
|
sessionId?: string
|
||||||
): Promise<Session> {
|
): Promise<Session> {
|
||||||
const wsString = toWorkspaceString(token.workspace, '@')
|
const wsString = toWorkspaceString(token.workspace, '@')
|
||||||
|
|
||||||
@ -98,7 +99,22 @@ class SessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pipeline = await workspace.pipeline
|
const pipeline = await workspace.pipeline
|
||||||
|
|
||||||
|
if (sessionId !== undefined) {
|
||||||
|
// try restore session
|
||||||
|
const existingSession = workspace.sessions.find((it) => it[0].sessionId === sessionId)
|
||||||
|
if (existingSession !== undefined) {
|
||||||
|
if (LOGGING_ENABLED) console.log('found existing session', existingSession)
|
||||||
|
// Update websocket
|
||||||
|
clearTimeout(existingSession[0].closeTimeout)
|
||||||
|
existingSession[0].closeTimeout = undefined
|
||||||
|
existingSession[1] = ws
|
||||||
|
return existingSession[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const session = this.createSession(token, pipeline)
|
const session = this.createSession(token, pipeline)
|
||||||
|
session.sessionId = sessionId
|
||||||
workspace.sessions.push([session, ws])
|
workspace.sessions.push([session, ws])
|
||||||
await this.setStatus(ctx, session, true)
|
await this.setStatus(ctx, session, true)
|
||||||
return session
|
return session
|
||||||
@ -210,6 +226,7 @@ class SessionManager {
|
|||||||
workspace.sessions = []
|
workspace.sessions = []
|
||||||
|
|
||||||
const closeS = async (s: Session, webSocket: WebSocket): Promise<void> => {
|
const closeS = async (s: Session, webSocket: WebSocket): Promise<void> => {
|
||||||
|
clearTimeout(s.closeTimeout)
|
||||||
// await for message to go to client.
|
// await for message to go to client.
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
// Override message handler, to wait for upgrading response from clients.
|
// Override message handler, to wait for upgrading response from clients.
|
||||||
@ -283,6 +300,12 @@ async function handleRequest<S extends Session> (
|
|||||||
const request = readRequest(msg)
|
const request = readRequest(msg)
|
||||||
if (request.id === -1 && request.method === 'hello') {
|
if (request.id === -1 && request.method === 'hello') {
|
||||||
ws.send(serialize({ id: -1, result: 'hello' }))
|
ws.send(serialize({ id: -1, result: 'hello' }))
|
||||||
|
|
||||||
|
// Push result buffer messages to client.
|
||||||
|
for (const r of service.resultBuffer ?? []) {
|
||||||
|
ws.send(serialize(r))
|
||||||
|
}
|
||||||
|
service.resultBuffer = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (request.id === -1 && request.method === '#upgrade') {
|
if (request.id === -1 && request.method === '#upgrade') {
|
||||||
@ -294,7 +317,12 @@ async function handleRequest<S extends Session> (
|
|||||||
const params = [ctx, ...request.params]
|
const params = [ctx, ...request.params]
|
||||||
const result = await f.apply(service, params)
|
const result = await f.apply(service, params)
|
||||||
const resp: Response<any> = { id: request.id, result }
|
const resp: Response<any> = { id: request.id, result }
|
||||||
ws.send(serialize(resp))
|
ws.send(serialize(resp), (err) => {
|
||||||
|
if (err !== undefined) {
|
||||||
|
// It seems we failed to send to client.
|
||||||
|
service.resultBuffer = [...(service.resultBuffer ?? []), resp]
|
||||||
|
}
|
||||||
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const resp: Response<any> = {
|
const resp: Response<any> = {
|
||||||
id: request.id,
|
id: request.id,
|
||||||
@ -337,18 +365,21 @@ export function start (
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
wss.on('connection', async (ws: WebSocket, request: any, token: Token) => {
|
wss.on('connection', async (ws: WebSocket, request: any, token: Token, sessionId?: string) => {
|
||||||
let buffer: string[] | undefined = []
|
let buffer: string[] | undefined = []
|
||||||
|
|
||||||
ws.on('message', (msg: string) => {
|
ws.on('message', (msg: string) => {
|
||||||
buffer?.push(msg)
|
buffer?.push(msg)
|
||||||
})
|
})
|
||||||
const session = await sessions.addSession(ctx, ws, token, pipelineFactory, productId)
|
const session = await sessions.addSession(ctx, ws, token, pipelineFactory, productId, sessionId)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
ws.on('message', async (msg: string) => await handleRequest(ctx, session, ws, msg))
|
ws.on('message', async (msg: string) => await handleRequest(ctx, session, ws, msg))
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
ws.on('close', (code: number, reason: string) => {
|
ws.on('close', (code: number, reason: string) => {
|
||||||
void sessions.close(ctx, ws, token.workspace, code, reason)
|
// remove session after 1seconds, give a time to reconnect.
|
||||||
|
session.closeTimeout = setTimeout(() => {
|
||||||
|
void sessions.close(ctx, ws, token.workspace, code, reason)
|
||||||
|
}, 1000)
|
||||||
})
|
})
|
||||||
const b = buffer
|
const b = buffer
|
||||||
buffer = undefined
|
buffer = undefined
|
||||||
@ -359,16 +390,19 @@ export function start (
|
|||||||
|
|
||||||
const server = createServer()
|
const server = createServer()
|
||||||
server.on('upgrade', (request: IncomingMessage, socket: any, head: Buffer) => {
|
server.on('upgrade', (request: IncomingMessage, socket: any, head: Buffer) => {
|
||||||
const token = request.url?.substring(1) // remove leading '/'
|
const url = new URL('http://localhost' + (request.url ?? ''))
|
||||||
|
const token = url.pathname.substring(1)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = decodeToken(token ?? '')
|
const payload = decodeToken(token ?? '')
|
||||||
console.log('client connected with payload', payload)
|
console.log('client connected with payload', payload)
|
||||||
|
const sessionId = url.searchParams.get('sessionId')
|
||||||
|
|
||||||
if (payload.workspace.productId !== productId) {
|
if (payload.workspace.productId !== productId) {
|
||||||
throw new Error('Invalid workspace product')
|
throw new Error('Invalid workspace product')
|
||||||
}
|
}
|
||||||
|
|
||||||
wss.handleUpgrade(request, socket, head, (ws) => wss.emit('connection', ws, request, payload))
|
wss.handleUpgrade(request, socket, head, (ws) => wss.emit('connection', ws, request, payload, sessionId))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('invalid token', err)
|
console.error('invalid token', err)
|
||||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||||
|
@ -27,6 +27,11 @@ export interface Session {
|
|||||||
options?: FindOptions<T>
|
options?: FindOptions<T>
|
||||||
) => Promise<FindResult<T>>
|
) => Promise<FindResult<T>>
|
||||||
tx: (ctx: MeasureContext, tx: Tx) => Promise<TxResult>
|
tx: (ctx: MeasureContext, tx: Tx) => Promise<TxResult>
|
||||||
|
|
||||||
|
// Session restore information
|
||||||
|
sessionId?: string
|
||||||
|
resultBuffer?: TxResult[]
|
||||||
|
closeTimeout?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user