UNPKG

jspurefix

Version:
171 lines (156 loc) 6.16 kB
import { MsgTransport } from '../factory' import { FixAcceptor } from '../fix-acceptor' import { IJsFixConfig, IJsFixLogger } from '../../config' import { IFixmlRequest } from '../fixml' import { FixDuplex, StringDuplex, StringDuplexTraits } from '../duplex' import * as express from 'express' import * as bodyParser from 'body-parser' import * as http from 'http' import { v4 as uuidv4 } from 'uuid' import { inject, injectable } from 'tsyringe' import { DITokens } from '../../runtime/di-tokens' @injectable() export class HttpAcceptor extends FixAcceptor { private readonly app: express.Express = express() private server: http.Server private readonly logger: IJsFixLogger private readonly router: express.Router private nextId: number = 0 private readonly keys: Map<string, MsgTransport> = new Map<string, MsgTransport>() constructor (@inject(DITokens.IJsFixConfig) public readonly config: IJsFixConfig) { super(config?.description?.application ?? null) this.logger = config.logFactory.logger(`${config?.description?.application?.name}:HttpAcceptor`) this.logger.info('creating http server') this.router = express.Router() this.router.use(bodyParser.json()) this.subscribe() this.app.use('/', this.router) } public listen (): void { const app = this.config.description.application const port = app?.http?.port ?? 0 const logger = this.logger logger.info(`start to listen ${port}`) this.server = this.app.listen(port, () => { logger.info(`app listening at http://localhost:${port}${app?.http?.uri}`) }) this.server.on('error', (err: Error) => { logger.error(err) this.emit('error', err) }) } public close (callback?: (err?: Error) => void): void { const app = this.config.description.application const port = app?.http?.port this.logger.info(`close listener on port ${port}`) this.server.close(callback) } private saveTransport (tid: number, transport: MsgTransport): string { this.transports[tid] = transport const keys: string[] = Object.keys(this.transports) const a = uuidv4() this.keys.set(a, transport) this.logger.info(`new transport id = ${tid} token = ${a} created total transports = ${keys.length}`) this.emit('transport', transport) return a } private harvestTransport (token: string, tid: number): void { delete this.transports[tid] this.keys.delete(token) const keys: string[] = Object.keys(this.transports) this.logger.info(`transport ${tid} ends total transports = ${keys.length}`) } private async respond (duplex: FixDuplex, response: express.Response, token: string | null = null): Promise<any> { return new Promise<any>((resolve, reject) => { response.setHeader('Content-Type', 'application/json') const timer = setTimeout(() => { const businessReject = '<FIXML><BizMsgRej BizRejRsn="4" Txt="no response from application"/></FIXML>' const b = Buffer.from(businessReject, 'utf-8') duplex.writable.removeListener('data', transmit) response.send(b) reject(new Error('no response')) }, 5000) const transmit = (d: Buffer): void => { this.logger.info('responding to request') clearTimeout(timer) if (token) { response.setHeader('authorization', token) } duplex.writable.removeListener('data', transmit) response.send(d) resolve(true) } duplex.writable.on('data', transmit) }) } private async logon (req: express.Request, res: express.Response): Promise<void> { const body: IFixmlRequest = req.body const id = this.nextId++ this.logger.info(JSON.stringify(body, null, 4)) // check hand back session key const d = new StringDuplex('', StringDuplexTraits.None) const transport = new MsgTransport(id, this.config, d) const token = this.saveTransport(id, transport) this.respond(d, res, token).then(() => { this.logger.info('responded to logon') }).catch((e: Error) => { this.logger.error(e) }) d.readable.push(body.fixml) } private async logout (req: express.Request, res: express.Response): Promise<void> { const headers = req.headers const body: IFixmlRequest = req.body const t: MsgTransport | undefined = this.keys.get(headers?.authorization ?? '') if (t) { const token = req.headers.authorization if (token) { this.harvestTransport(token, t.id) const d = t.duplex this.respond(d, res, token).then(() => { this.logger.info('responded to logout') t.end() }).catch((e: Error) => { this.logger.error(e) }) d.readable.push(body.fixml) } } } private subscribe (): void { const router = this.router const app = this.config.description.application const root = app?.http?.uri const authorise = `${root}authorise` const query = `${root}query` this.logger.info(`uri: authorise ${authorise}, query ${query}`) router.post(authorise, async (req: express.Request, res: express.Response) => { if (!req.headers.authorization) { this.logger.info('logon') await this.logon(req, res) } else { this.logger.info('logout') await this.logout(req, res) } }) router.get(query, async (req: express.Request, res: express.Response) => { const headers = req.headers const body: IFixmlRequest = req.body const t: MsgTransport | null = this.keys.get(headers.authorization ?? '') ?? null if (!t) { this.logger.info('received request with no token') res.send({ error: 'no key with query' }) } else { const d = t.duplex this.respond(d, res).then(() => { this.logger.info(`responded to ${req.url}`) }).catch(e => { res.send(e) }) d.readable.push(body.fixml) } }) } }