123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- 'use strict'
- var StringDecoder = require('string_decoder').StringDecoder
- var decoder = new StringDecoder()
- var ReplyError = require('./replyError')
- var ParserError = require('./parserError')
- var bufferPool = bufferAlloc(32 * 1024)
- var bufferOffset = 0
- var interval = null
- var counter = 0
- var notDecreased = 0
- var isModern = typeof Buffer.allocUnsafe === 'function'
- function bufferAlloc (len) {
- return isModern ? Buffer.allocUnsafe(len) : new Buffer(len)
- }
- function parseSimpleNumbers (parser) {
- var offset = parser.offset
- var length = parser.buffer.length - 1
- var number = 0
- var sign = 1
- if (parser.buffer[offset] === 45) {
- sign = -1
- offset++
- }
- while (offset < length) {
- var c1 = parser.buffer[offset++]
- if (c1 === 13) {
- parser.offset = offset + 1
- return sign * number
- }
- number = (number * 10) + (c1 - 48)
- }
- }
- function parseStringNumbers (parser) {
- var offset = parser.offset
- var length = parser.buffer.length - 1
- var number = 0
- var res = ''
- if (parser.buffer[offset] === 45) {
- res += '-'
- offset++
- }
- while (offset < length) {
- var c1 = parser.buffer[offset++]
- if (c1 === 13) {
- parser.offset = offset + 1
- if (number !== 0) {
- res += number
- }
- return res
- } else if (number > 429496728) {
- res += (number * 10) + (c1 - 48)
- number = 0
- } else if (c1 === 48 && number === 0) {
- res += 0
- } else {
- number = (number * 10) + (c1 - 48)
- }
- }
- }
- function convertBufferRange (parser, start, end) {
- parser.offset = end + 2
- if (parser.optionReturnBuffers === true) {
- return parser.buffer.slice(start, end)
- }
- return parser.buffer.toString('utf-8', start, end)
- }
- function parseSimpleString (parser) {
- var start = parser.offset
- var offset = start
- var buffer = parser.buffer
- var length = buffer.length - 1
- while (offset < length) {
- if (buffer[offset++] === 13) {
- return convertBufferRange(parser, start, offset - 1)
- }
- }
- }
- function parseLength (parser) {
- var string = parseSimpleNumbers(parser)
- if (string !== undefined) {
- return string
- }
- }
- function parseInteger (parser) {
- if (parser.optionStringNumbers) {
- return parseStringNumbers(parser)
- }
- return parseSimpleNumbers(parser)
- }
- function parseBulkString (parser) {
- var length = parseLength(parser)
- if (length === undefined) {
- return
- }
- if (length === -1) {
- return null
- }
- var offsetEnd = parser.offset + length
- if (offsetEnd + 2 > parser.buffer.length) {
- parser.bigStrSize = offsetEnd + 2
- parser.bigOffset = parser.offset
- parser.totalChunkSize = parser.buffer.length
- parser.bufferCache.push(parser.buffer)
- return
- }
- return convertBufferRange(parser, parser.offset, offsetEnd)
- }
- function parseError (parser) {
- var string = parseSimpleString(parser)
- if (string !== undefined) {
- if (parser.optionReturnBuffers === true) {
- string = string.toString()
- }
- return new ReplyError(string)
- }
- }
- function handleError (parser, error) {
- parser.buffer = null
- parser.returnFatalError(error)
- }
- function parseArray (parser) {
- var length = parseLength(parser)
- if (length === undefined) {
- return
- }
- if (length === -1) {
- return null
- }
- var responses = new Array(length)
- return parseArrayElements(parser, responses, 0)
- }
- function pushArrayCache (parser, elem, pos) {
- parser.arrayCache.push(elem)
- parser.arrayPos.push(pos)
- }
- function parseArrayChunks (parser) {
- var tmp = parser.arrayCache.pop()
- var pos = parser.arrayPos.pop()
- if (parser.arrayCache.length) {
- var res = parseArrayChunks(parser)
- if (!res) {
- pushArrayCache(parser, tmp, pos)
- return
- }
- tmp[pos++] = res
- }
- return parseArrayElements(parser, tmp, pos)
- }
- function parseArrayElements (parser, responses, i) {
- var bufferLength = parser.buffer.length
- while (i < responses.length) {
- var offset = parser.offset
- if (parser.offset >= bufferLength) {
- pushArrayCache(parser, responses, i)
- return
- }
- var response = parseType(parser, parser.buffer[parser.offset++])
- if (response === undefined) {
- if (!parser.arrayCache.length) {
- parser.offset = offset
- }
- pushArrayCache(parser, responses, i)
- return
- }
- responses[i] = response
- i++
- }
- return responses
- }
- function parseType (parser, type) {
- switch (type) {
- case 36:
- return parseBulkString(parser)
- case 58:
- return parseInteger(parser)
- case 43:
- return parseSimpleString(parser)
- case 42:
- return parseArray(parser)
- case 45:
- return parseError(parser)
- default:
- return handleError(parser, new ParserError(
- 'Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte',
- JSON.stringify(parser.buffer),
- parser.offset
- ))
- }
- }
- var optionTypes = {
- returnError: 'function',
- returnFatalError: 'function',
- returnReply: 'function',
- returnBuffers: 'boolean',
- stringNumbers: 'boolean',
- name: 'string'
- }
- function JavascriptRedisParser (options) {
- if (!(this instanceof JavascriptRedisParser)) {
- return new JavascriptRedisParser(options)
- }
- if (!options || !options.returnError || !options.returnReply) {
- throw new TypeError('Please provide all return functions while initiating the parser')
- }
- for (var key in options) {
-
- if (optionTypes.hasOwnProperty(key) && typeof options[key] !== optionTypes[key]) {
- throw new TypeError('The options argument contains the property "' + key + '" that is either unknown or of a wrong type')
- }
- }
- if (options.name === 'hiredis') {
-
- try {
- var Hiredis = require('./hiredis')
- console.error(new TypeError('Using hiredis is discouraged. Please use the faster JS parser by removing the name option.').stack.replace('Error', 'Warning'))
- return new Hiredis(options)
- } catch (e) {
- console.error(new TypeError('Hiredis is not installed. Please remove the `name` option. The (faster) JS parser is used instead.').stack.replace('Error', 'Warning'))
- }
- }
- this.optionReturnBuffers = !!options.returnBuffers
- this.optionStringNumbers = !!options.stringNumbers
- this.returnError = options.returnError
- this.returnFatalError = options.returnFatalError || options.returnError
- this.returnReply = options.returnReply
- this.name = 'javascript'
- this.reset()
- }
- JavascriptRedisParser.prototype.reset = function () {
- this.offset = 0
- this.buffer = null
- this.bigStrSize = 0
- this.bigOffset = 0
- this.totalChunkSize = 0
- this.bufferCache = []
- this.arrayCache = []
- this.arrayPos = []
- }
- JavascriptRedisParser.prototype.setReturnBuffers = function (returnBuffers) {
- if (typeof returnBuffers !== 'boolean') {
- throw new TypeError('The returnBuffers argument has to be a boolean')
- }
- this.optionReturnBuffers = returnBuffers
- }
- JavascriptRedisParser.prototype.setStringNumbers = function (stringNumbers) {
- if (typeof stringNumbers !== 'boolean') {
- throw new TypeError('The stringNumbers argument has to be a boolean')
- }
- this.optionStringNumbers = stringNumbers
- }
- function decreaseBufferPool () {
- if (bufferPool.length > 50 * 1024) {
-
- if (counter === 1 || notDecreased > counter * 2) {
-
- var sliceLength = Math.floor(bufferPool.length / 10)
- if (bufferOffset <= sliceLength) {
- bufferOffset = 0
- } else {
- bufferOffset -= sliceLength
- }
- bufferPool = bufferPool.slice(sliceLength, bufferPool.length)
- } else {
- notDecreased++
- counter--
- }
- } else {
- clearInterval(interval)
- counter = 0
- notDecreased = 0
- interval = null
- }
- }
- function resizeBuffer (length) {
- if (bufferPool.length < length + bufferOffset) {
- var multiplier = length > 1024 * 1024 * 75 ? 2 : 3
- if (bufferOffset > 1024 * 1024 * 111) {
- bufferOffset = 1024 * 1024 * 50
- }
- bufferPool = bufferAlloc(length * multiplier + bufferOffset)
- bufferOffset = 0
- counter++
- if (interval === null) {
- interval = setInterval(decreaseBufferPool, 50)
- }
- }
- }
- function concatBulkString (parser) {
- var list = parser.bufferCache
- var chunks = list.length
- var offset = parser.bigStrSize - parser.totalChunkSize
- parser.offset = offset
- if (offset <= 2) {
- if (chunks === 2) {
- return list[0].toString('utf8', parser.bigOffset, list[0].length + offset - 2)
- }
- chunks--
- offset = list[list.length - 2].length + offset
- }
- var res = decoder.write(list[0].slice(parser.bigOffset))
- for (var i = 1; i < chunks - 1; i++) {
- res += decoder.write(list[i])
- }
- res += decoder.end(list[i].slice(0, offset - 2))
- return res
- }
- function concatBulkBuffer (parser) {
- var list = parser.bufferCache
- var chunks = list.length
- var length = parser.bigStrSize - parser.bigOffset - 2
- var offset = parser.bigStrSize - parser.totalChunkSize
- parser.offset = offset
- if (offset <= 2) {
- if (chunks === 2) {
- return list[0].slice(parser.bigOffset, list[0].length + offset - 2)
- }
- chunks--
- offset = list[list.length - 2].length + offset
- }
- resizeBuffer(length)
- var start = bufferOffset
- list[0].copy(bufferPool, start, parser.bigOffset, list[0].length)
- bufferOffset += list[0].length - parser.bigOffset
- for (var i = 1; i < chunks - 1; i++) {
- list[i].copy(bufferPool, bufferOffset)
- bufferOffset += list[i].length
- }
- list[i].copy(bufferPool, bufferOffset, 0, offset - 2)
- bufferOffset += offset - 2
- return bufferPool.slice(start, bufferOffset)
- }
- JavascriptRedisParser.prototype.execute = function execute (buffer) {
- if (this.buffer === null) {
- this.buffer = buffer
- this.offset = 0
- } else if (this.bigStrSize === 0) {
- var oldLength = this.buffer.length
- var remainingLength = oldLength - this.offset
- var newBuffer = bufferAlloc(remainingLength + buffer.length)
- this.buffer.copy(newBuffer, 0, this.offset, oldLength)
- buffer.copy(newBuffer, remainingLength, 0, buffer.length)
- this.buffer = newBuffer
- this.offset = 0
- if (this.arrayCache.length) {
- var arr = parseArrayChunks(this)
- if (!arr) {
- return
- }
- this.returnReply(arr)
- }
- } else if (this.totalChunkSize + buffer.length >= this.bigStrSize) {
- this.bufferCache.push(buffer)
- var tmp = this.optionReturnBuffers ? concatBulkBuffer(this) : concatBulkString(this)
- this.bigStrSize = 0
- this.bufferCache = []
- this.buffer = buffer
- if (this.arrayCache.length) {
- this.arrayCache[0][this.arrayPos[0]++] = tmp
- tmp = parseArrayChunks(this)
- if (!tmp) {
- return
- }
- }
- this.returnReply(tmp)
- } else {
- this.bufferCache.push(buffer)
- this.totalChunkSize += buffer.length
- return
- }
- while (this.offset < this.buffer.length) {
- var offset = this.offset
- var type = this.buffer[this.offset++]
- var response = parseType(this, type)
- if (response === undefined) {
- if (!this.arrayCache.length) {
- this.offset = offset
- }
- return
- }
- if (type === 45) {
- this.returnError(response)
- } else {
- this.returnReply(response)
- }
- }
- this.buffer = null
- }
- module.exports = JavascriptRedisParser
|