UNPKG

loadtest

Version:

Run load tests for your web application. Mostly ab-compatible interface, with an option to force requests per second. Includes an API for automated load testing.

161 lines (145 loc) 4 kB
const bodySeparator = '\r\n\r\n' const lineSeparator = '\r\n' const packetInfos = new Map() // max size to consider packets duplicated const maxPacketSize = 1000 export class Parser { constructor(method) { this.method = method this.pending = null this.finished = false this.headers = {} this.partialBody = false this.packetInfo = null } addPacket(data) { if (this.pending) { this.pending = Buffer.concat([this.pending, data]) } else { this.pending = data } if (this.partialBody) { this.parseBody(this.pending, this.packetInfo) return } this.parsePacket() } parsePacket() { const division = this.pending.indexOf(bodySeparator) if (division == -1) { // cannot parse yet return } this.packetInfo = new PacketInfo(this.pending.length, division) const messageHeader = this.pending.subarray(0, division) const messageBody = this.pending.subarray(division + 4) this.parseFirstLine(messageHeader) const key = this.packetInfo.getKey() const existing = packetInfos.get(key) if (existing && this.packetInfo.isDuplicated(existing)) { // no need to parse headers or body this.finished = true return } packetInfos.set(key, this.packetInfo) this.parseHeaders(messageHeader) this.parseBody(messageBody) } parseFirstLine(messageHeader) { let firstReturn = messageHeader.indexOf(lineSeparator) if (firstReturn == -1) { // no headers firstReturn = messageHeader.length } const firstLine = messageHeader.toString('utf8', 0, firstReturn) this.packetInfo.parseFirstLine(firstLine) } parseHeaders(messageHeader) { if (this.packetInfo.hasNoBody(this.method)) { // do not parse headers return } let firstReturn = messageHeader.indexOf(lineSeparator) let position = firstReturn + 2 while (position < messageHeader.length) { let nextReturn = messageHeader.indexOf(lineSeparator, position) if (nextReturn == -1) { nextReturn = messageHeader.length } const line = messageHeader.toString('utf8', position, nextReturn) this.parseHeader(line) position = nextReturn + 2 } } parseBody(messageBody) { if (this.packetInfo.hasNoBody(this.method)) { if (messageBody.length != 0) { throw new Error(`Should not have a body`) } // do not parse body this.finished = true return } if (!this.packetInfo.contentLength) { throw new Error(`Missing content length`) } if (this.packetInfo.contentLength != messageBody.length) { this.partialBody = true this.pending = messageBody return } // we do not actually parse the body, have no use for it this.finished = true } parseHeader(line) { const colon = line.indexOf(': ') if (colon == -1) { throw new Error(`Invalid header ${line}`) } const key = line.substring(0, colon).toLowerCase() if (key == 'content-length') { const value = line.substring(colon + 2) this.packetInfo.contentLength = parseInt(value) } // this.headers[key] = value } } class PacketInfo { constructor(length, division) { this.length = length this.division = division this.contentLength = 0 this.statusCode = 0 } getKey() { return `${this.length}-${this.division}` } parseFirstLine(firstLine) { const words = firstLine.split(' ') if (words.length < 2) { throw new Error(`Unexpected response line ${firstLine}`) } if (words[0] != 'HTTP/1.1') { throw new Error(`Unexpected first word ${words[0]}`) } this.statusCode = parseInt(words[1]) if (!this.statusCode || this.statusCode < 100 || this.statusCode >= 600) { throw new Error(`Unexpected status code ${this.statusCode}`) } } hasNoBody(method) { if (method == 'HEAD') { return true } if (this.statusCode < 200 || this.statusCode == 204 || this.statusCode == 304) { return true } return false } isDuplicated(info) { if (this.length > maxPacketSize) { return false } return this.length == info.length && this.division == info.division && this.statusCode == info.statusCode } }