beanstalkd-protocol
Version:
Beanstalkd protocol parser for Node.js/Javascript
228 lines (177 loc) • 5.88 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.convertArgs = convertArgs;
var _misc = require('./misc');
var _default = require('./default');
var _assert = require('assert');
var _assert2 = _interopRequireDefault(_assert);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
class BeanstalkdProtocol {
constructor() {
this.reset();
}
reset() {
this.types = Object.assign({}, _default.types);
this.commandMap = {};
this.replyMap = {};
_default.commands.forEach(signature => {
this.addCommand(signature);
});
_default.replies.forEach(signature => {
this.addReply(signature);
});
}
addType(key, type) {
this.types[key] = type;
}
add(signature, key) {
if (signature.substr(-2) !== _misc.CRLF.toString()) {
throw new Error(`${key} ${signature} does not end in CRLF`);
}
let parts = signature.split(_misc.CRLF.toString()).slice(0, -1).map(part => {
return part.split(_misc.SPACE.toString());
});
let identifier = parts[0].shift();
parts = parts.map(part => {
return part.map(arg => {
arg = arg.substr(1, arg.length - 2);
if (!this.types[arg]) throw new Error(`arg ${arg} does not have a type defined`);
return arg;
});
});
let args = parts.reduce((args, part) => args.concat(part), []);
let existing = this[key + 'Map'][identifier];
if (!existing) {
this[key + 'Map'][identifier] = {
args: args,
parts,
argsOptional: false
};
} else if (existing.args.length !== args.length) {
existing.argsOptional = true;
if (!existing.args.length && args.length) {
existing.args = args;
}
}
}
parse(buffer, key) {
if (buffer.length < 4) return [buffer, null];
let boundary = buffer.indexOf(_misc.CRLF);
if (boundary === -1) return [buffer, null];
let specMap = this[key + 'Map'];
if (buffer.indexOf(' ') === -1) {
let identifier = buffer.slice(0, boundary).toString();
if (specMap[identifier]) {
return [null, {
[key]: identifier,
args: {}
}];
}
}
let parts = [buffer.slice(0, boundary).toString().split(_misc.SPACE.toString())];
let identifier = parts[0].shift();
let spec = specMap[identifier];
let remainder = buffer.length > boundary + _misc.CRLF.length ? buffer.slice(boundary + _misc.CRLF.length) : null;
let args = parts[0];
if (!spec) {
return [remainder, {
[key]: identifier,
args: {},
unknown: true
}];
}
if (spec.parts.length > 1) {
for (let i = 1; i < spec.parts.length; i++) {
if (!remainder) return [buffer, null];
let previous = i && spec.parts[i - 1] || null;
let bytes = previous[previous.length - 1] === 'bytes' && parseInt(args[args.length - 1], 10) || null;
if (bytes > remainder.length) return [buffer, null];
let boundary = bytes || remainder.indexOf(_misc.CRLF);
if (boundary === -1) return [buffer, null];
// TODO: Support non buffer args
let part = remainder.slice(0, boundary);
remainder = remainder.length > boundary + _misc.CRLF.length ? remainder.slice(remainder + _misc.CRLF.length) : null;
args.push(part);
}
}
return [remainder, {
[key]: identifier,
args: convertArgs(spec, args, this.types)
}];
}
build(identifier, args, key) {
let spec = this[key + 'Map'][identifier],
isArray = args && Array.isArray(args),
expectsArgsLength = spec.args.length,
argsOptional = spec.argsOptional,
argsLength = args && (isArray ? args.length : Object.keys(args).length);
if (!isArray && args) {
args = spec.args.map(key => args[key]);
}
(0, _assert2.default)(argsOptional || !expectsArgsLength || args || args.length, `${identifier} requires args`);
(0, _assert2.default)(argsOptional || !args || !args.length || expectsArgsLength === argsLength, `${identifier} expects ${spec.args.length} args`);
if (!args || !argsLength) {
return Buffer.from(identifier + _misc.CRLF);
}
if (spec.parts.length < 2) {
return Buffer.from(identifier + ' ' + args.join(' ') + _misc.CRLF);
}
let buffers = [Buffer.from(identifier + ' ')];
let offset = 0;
spec.parts.forEach(function (part) {
let partArgs = args.slice(offset, offset + part.length);
offset += part.length;
partArgs.forEach(function (arg, i) {
if (i) {
buffers.push(_misc.SPACE);
}
if (Buffer.isBuffer(arg)) {
buffers.push(arg);
} else {
buffers.push(Buffer.from(arg.toString()));
}
});
buffers.push(_misc.CRLF);
});
return Buffer.concat(buffers);
}
addCommand(signature) {
this.add(signature, 'command');
}
parseCommand(buffer) {
return this.parse(buffer, 'command');
}
buildCommand(command, args) {
return this.build(command, args, 'command');
}
buildPut(args) {
let isArray = Array.isArray(args);
if (!isArray) {
let spec = this.commandMap['put'];
args = spec.args.map(key => args[key]);
}
let body = args.pop();
args.push(body.length);
args.push(body);
return this.buildCommand('put', args);
}
addReply(signature) {
this.add(signature, 'reply');
}
parseReply(buffer) {
return this.parse(buffer, 'reply');
}
buildReply(reply, args) {
return this.build(reply, args, 'reply');
}
}
exports.default = BeanstalkdProtocol;
function convertArgs(spec, args, types) {
return (0, _misc.reduce)(args, function (args, arg, i) {
let key = spec.args[i];
args[key] = types[key](arg);
return args;
}, {});
}