You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
7.9 KiB

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Command_1 = require("./Command");
const utils_1 = require("./utils");
const RedisParser = require("redis-parser");
const SubscriptionSet_1 = require("./SubscriptionSet");
const debug = (0, utils_1.Debug)("dataHandler");
class DataHandler {
constructor(redis, parserOptions) {
this.redis = redis;
const parser = new RedisParser({
stringNumbers: parserOptions.stringNumbers,
returnBuffers: true,
returnError: (err) => {
this.returnError(err);
},
returnFatalError: (err) => {
this.returnFatalError(err);
},
returnReply: (reply) => {
this.returnReply(reply);
},
});
redis.stream.on("data", (data) => {
parser.execute(data);
});
}
returnFatalError(err) {
err.message += ". Please report this.";
this.redis.recoverFromFatalError(err, err, { offlineQueue: false });
}
returnError(err) {
const item = this.shiftCommand(err);
if (!item) {
return;
}
err.command = {
name: item.command.name,
args: item.command.args,
};
this.redis.handleReconnection(err, item);
}
returnReply(reply) {
if (this.handleMonitorReply(reply)) {
return;
}
if (this.handleSubscriberReply(reply)) {
return;
}
const item = this.shiftCommand(reply);
if (!item) {
return;
}
if (Command_1.default.checkFlag("ENTER_SUBSCRIBER_MODE", item.command.name)) {
this.redis.condition.subscriber = new SubscriptionSet_1.default();
this.redis.condition.subscriber.add(item.command.name, reply[1].toString());
if (!fillSubCommand(item.command, reply[2])) {
this.redis.commandQueue.unshift(item);
}
}
else if (Command_1.default.checkFlag("EXIT_SUBSCRIBER_MODE", item.command.name)) {
if (!fillUnsubCommand(item.command, reply[2])) {
this.redis.commandQueue.unshift(item);
}
}
else {
item.command.resolve(reply);
}
}
handleSubscriberReply(reply) {
if (!this.redis.condition.subscriber) {
return false;
}
const replyType = Array.isArray(reply) ? reply[0].toString() : null;
debug('receive reply "%s" in subscriber mode', replyType);
switch (replyType) {
case "message":
if (this.redis.listeners("message").length > 0) {
// Check if there're listeners to avoid unnecessary `toString()`.
this.redis.emit("message", reply[1].toString(), reply[2] ? reply[2].toString() : "");
}
this.redis.emit("messageBuffer", reply[1], reply[2]);
break;
case "pmessage": {
const pattern = reply[1].toString();
if (this.redis.listeners("pmessage").length > 0) {
this.redis.emit("pmessage", pattern, reply[2].toString(), reply[3].toString());
}
this.redis.emit("pmessageBuffer", pattern, reply[2], reply[3]);
break;
}
case "smessage": {
if (this.redis.listeners("smessage").length > 0) {
this.redis.emit("smessage", reply[1].toString(), reply[2] ? reply[2].toString() : "");
}
this.redis.emit("smessageBuffer", reply[1], reply[2]);
break;
}
case "ssubscribe":
case "subscribe":
case "psubscribe": {
const channel = reply[1].toString();
this.redis.condition.subscriber.add(replyType, channel);
const item = this.shiftCommand(reply);
if (!item) {
return;
}
if (!fillSubCommand(item.command, reply[2])) {
this.redis.commandQueue.unshift(item);
}
break;
}
case "sunsubscribe":
case "unsubscribe":
case "punsubscribe": {
const channel = reply[1] ? reply[1].toString() : null;
if (channel) {
this.redis.condition.subscriber.del(replyType, channel);
}
const count = reply[2];
if (Number(count) === 0) {
this.redis.condition.subscriber = false;
}
const item = this.shiftCommand(reply);
if (!item) {
return;
}
if (!fillUnsubCommand(item.command, count)) {
this.redis.commandQueue.unshift(item);
}
break;
}
default: {
const item = this.shiftCommand(reply);
if (!item) {
return;
}
item.command.resolve(reply);
}
}
return true;
}
handleMonitorReply(reply) {
if (this.redis.status !== "monitoring") {
return false;
}
const replyStr = reply.toString();
if (replyStr === "OK") {
// Valid commands in the monitoring mode are AUTH and MONITOR,
// both of which always reply with 'OK'.
// So if we got an 'OK', we can make certain that
// the reply is made to AUTH & MONITOR.
return false;
}
// Since commands sent in the monitoring mode will trigger an exception,
// any replies we received in the monitoring mode should consider to be
// realtime monitor data instead of result of commands.
const len = replyStr.indexOf(" ");
const timestamp = replyStr.slice(0, len);
const argIndex = replyStr.indexOf('"');
const args = replyStr
.slice(argIndex + 1, -1)
.split('" "')
.map((elem) => elem.replace(/\\"/g, '"'));
const dbAndSource = replyStr.slice(len + 2, argIndex - 2).split(" ");
this.redis.emit("monitor", timestamp, args, dbAndSource[1], dbAndSource[0]);
return true;
}
shiftCommand(reply) {
const item = this.redis.commandQueue.shift();
if (!item) {
const message = "Command queue state error. If you can reproduce this, please report it.";
const error = new Error(message +
(reply instanceof Error
? ` Last error: ${reply.message}`
: ` Last reply: ${reply.toString()}`));
this.redis.emit("error", error);
return null;
}
return item;
}
}
exports.default = DataHandler;
const remainingRepliesMap = new WeakMap();
function fillSubCommand(command, count) {
let remainingReplies = remainingRepliesMap.has(command)
? remainingRepliesMap.get(command)
: command.args.length;
remainingReplies -= 1;
if (remainingReplies <= 0) {
command.resolve(count);
remainingRepliesMap.delete(command);
return true;
}
remainingRepliesMap.set(command, remainingReplies);
return false;
}
function fillUnsubCommand(command, count) {
let remainingReplies = remainingRepliesMap.has(command)
? remainingRepliesMap.get(command)
: command.args.length;
if (remainingReplies === 0) {
if (Number(count) === 0) {
remainingRepliesMap.delete(command);
command.resolve(count);
return true;
}
return false;
}
remainingReplies -= 1;
if (remainingReplies <= 0) {
command.resolve(count);
return true;
}
remainingRepliesMap.set(command, remainingReplies);
return false;
}