tardis-machine
Version:
Locally runnable server with built-in data caching, providing both tick-level historical and consolidated real-time cryptocurrency market data via HTTP and WebSocket APIs
96 lines (78 loc) • 3.03 kB
text/typescript
import { once } from 'events'
import { IncomingMessage, OutgoingMessage, ServerResponse } from 'http'
import { replay, ReplayOptions } from 'tardis-dev'
import url from 'url'
import { debug } from '../debug'
export const replayHttp = async (req: IncomingMessage, res: ServerResponse) => {
try {
const startTimestamp = new Date().getTime()
const parsedQuery = url.parse(req.url!, true).query
const optionsString = parsedQuery['options'] as string
const replayOptions = JSON.parse(optionsString) as ReplayOptions<any, any, any>
debug('GET /replay request started, options: %o', replayOptions)
const streamedMessagesCount = await writeMessagesToResponse(res, replayOptions)
const endTimestamp = new Date().getTime()
debug(
'GET /replay request finished, options: %o, time: %d seconds, total messages count:%d',
replayOptions,
(endTimestamp - startTimestamp) / 1000,
streamedMessagesCount
)
} catch (e: any) {
const errorInfo = {
responseText: e.responseText,
message: e.message,
url: e.url
}
debug('GET /replay request error: %o', e)
console.error('GET /replay request error:', e)
if (!res.finished) {
res.statusCode = e.status || 500
res.end(JSON.stringify(errorInfo))
}
}
}
async function writeMessagesToResponse(res: OutgoingMessage, replayOptions: ReplayOptions<any, any, any>) {
const responsePrefixBuffer = Buffer.from('{"localTimestamp":"')
const responseMiddleBuffer = Buffer.from('","message":')
const responseSuffixBuffer = Buffer.from('}\n')
const newLineBuffer = Buffer.from('\n')
const BATCH_SIZE = 32
// not 100% sure that's necessary since we're returning ndjson in fact, not json
res.setHeader('Content-Type', 'application/x-json-stream')
let buffers: Buffer[] = []
let totalMessagesCount = 0
const messages = replay({ ...replayOptions, skipDecoding: true })
for await (let messageWithTimestamp of messages) {
totalMessagesCount++
if (messageWithTimestamp === undefined) {
// if received message is undefined (disconnect)
// return it as new line
buffers.push(newLineBuffer)
} else {
// instead of writing each message directly to response,
// let's batch them and send in BATCH_SIZE batches (each message is 5 buffers: prefix etc)
// also instead of converting messages to string or parsing them let's manually stich together desired json response using buffers which is faster
buffers.push(
responsePrefixBuffer,
messageWithTimestamp.localTimestamp,
responseMiddleBuffer,
messageWithTimestamp.message,
responseSuffixBuffer
)
if (buffers.length >= BATCH_SIZE * 5) {
const ok = res.write(Buffer.concat(buffers))
buffers = []
if (!ok) {
await once(res, 'drain')
}
}
}
}
if (buffers.length > 0) {
res.write(Buffer.concat(buffers))
buffers = []
}
res.end('')
return totalMessagesCount
}