libs/message.js
'use strict';
const CRLF = '\r\n';
const CRLF_LENGTH = CRLF.length;
const CRLF_TAIL = /\r\n$/;
/**
* Socket reply message wrapper
*/
class Message {
/**
* Constructor
*/
constructor() {
this.buffers = [];
this.size = 0;
this._message = null;
}
/**
* Append to stack buffer
*
* @param {Buffer} chunk append chunk buffer
* @return {Void} -
*/
append(chunk) {
if (this._message) {
return;
}
this.buffers.push(chunk);
this.size += chunk.length;
}
/**
* Freeze message
* After call, message buffer cannot modify any more.
*
* @return {Message} this
*/
freeze() {
if (this._message) {
return;
}
this._message = Buffer.concat(this.buffers, this.size);
return this;
}
/**
* Check message buffer is EOF
*
* @static
* @param {Buffer} buffer chunk buffer
* @param {Boolean} isNumberReply expected number reply
* @return {Boolean} -
*/
static isEOF(buffer, isNumberReply = false) {
// Special case of incr / decr reply
if (isNumberReply && /^[0-9]+\r\n/.test(buffer.toString('utf8'))) {
return true;
}
const EOFMessageList = [
Message.STORED,
Message.NOT_STORED,
Message.NOT_FOUND,
Message.ERROR,
Message.EXISTS,
Message.TOUCHED,
Message.DELETED,
Message.END
];
return EOFMessageList.some(msg => {
const index = buffer.indexOf(msg);
return (index !== -1 && index + msg.length + CRLF_LENGTH === buffer.length);
});
}
/**
* Try to single reply code
*
* @return {String} code reply code
*/
get code() {
if (!this._message) {
return '';
}
// If reply message is too huge, to stringify all buffer takes high cost.
// So we check some length from top of message (about 14 bytes is enough)
const buffer = this.buffer;
const end = Math.min(buffer.length, 14);
const code = Buffer.alloc(end);
buffer.copy(code, 0, 0, end);
return code.toString('utf8').replace(CRLF_TAIL, '');
}
/**
* Get raw data to string
*
* @return {String} -
*/
get rawData() {
if (!this._message) {
return '';
}
return this._message.toString('utf8').replace(CRLF_TAIL, '');
}
/**
* Buffer getter
*
* @return {Buffer} -
*/
get buffer() {
return this._message;
}
/**
* Get parsed value from "get" command response
*
* @return {String} -
*/
getValue() {
return this.getObjectValue().value;
}
/**
* Get parsed multiple-value from "get" command response
*
* @return {Object} -
*/
getBulkValues() {
const values = this.getBulkObjectValues();
const ret = {};
Object.keys(values).forEach(k => {
ret[k] = values[k].value;
});
return ret;
}
/**
* Get parsed value from "gets" command response contains "cas unique"
*
* @return {Object} -
*/
getObjectValue() {
const buffer = this.buffer;
let start = buffer.indexOf(CRLF);
const meta = buffer.slice(0, start).toString('utf8').split(' ');
start += CRLF_LENGTH;
const value = buffer.slice(start, start + parseInt(meta[3], 10));
return {
key: meta[1],
flags: meta[2],
bytes: meta[3],
value: value.toString('utf8'),
cas: meta[4] || null
};
}
/**
* Get parsed multiple-value from "gets" command response contains "cas unique"
*
* @return {Object} -
*/
getBulkObjectValues() {
const values = {};
const buffer = this.buffer;
let index = 0;
do {
const delim = buffer.indexOf(CRLF, index);
const meta = buffer.slice(index, delim).toString('utf8').split(' ');
if (meta[0] === Message.END) {
break;
}
index = delim + CRLF_LENGTH;
const dataSize = parseInt(meta[3], 10);
values[meta[1]] = {
key: meta[1],
flags: meta[2],
bytes: meta[3],
value: buffer.slice(index, index + dataSize).toString('utf8'),
cas: meta[4] || null
};
index += dataSize + CRLF_LENGTH;
} while(true);
return values;
}
}
Message.STORED = 'STORED';
Message.NOT_STORED = 'NOT_STORED';
Message.NOT_FOUND = 'NOT_FOUND';
Message.EXISTS = 'EXISTS';
Message.END = 'END';
Message.DELETED = 'DELETED';
Message.TOUCHED = 'TOUCHED';
Message.ERROR = 'ERROR';
Message.CLIENT_ERROR = 'CLIENT_ERROR';
Message.SERVER_ERROR = 'SERVER_ERROR';
module.exports = Message;