UNPKG

faktory-worker

Version:
186 lines (185 loc) 6.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Connection = void 0; const assert_1 = require("assert"); const debug_1 = __importDefault(require("debug")); const events_1 = require("events"); const net_1 = require("net"); const redis_parser_1 = __importDefault(require("redis-parser")); const tls_1 = require("tls"); const debug = (0, debug_1.default)("faktory-worker:connection"); const SOCKET_TIMEOUT = 10000; /** * A connection to the faktory server for sending commands * and receiving messages. Abstracts the underlying node Socket * and allows easier async sending and receiving. Not "threadsafe". Use in * a connection pool. * * @private */ class Connection extends events_1.EventEmitter { /** * @param {Number} port the port to connect on * @param {String} host the hostname to connect to * @param {Object} options additional options */ constructor(port, host, tlsOptions) { super(); this.host = host; this.port = port; this.connected = false; this.socket = new net_1.Socket(); this.socket.setKeepAlive(true); this.tlsOptions = tlsOptions; this.pending = []; this.parser = new redis_parser_1.default({ returnReply: (response) => { var _a; return (_a = this.pending.pop()) === null || _a === void 0 ? void 0 : _a.resolve(response); }, returnError: (err) => { var _a; return (_a = this.pending.pop()) === null || _a === void 0 ? void 0 : _a.reject(err); }, }); this.listen(); } /** * Sets the socket timeout * @param {Number} ms timeout in milliseconds */ setTimeout(ms = SOCKET_TIMEOUT) { this.socket.setTimeout(ms); } /** * Registers listeners on the underlying node socket * @private * @return {Connection} self */ listen() { this.socket .once("connect", this.onConnect.bind(this)) .on("data", this.parser.execute.bind(this.parser)) .on("timeout", this.onTimeout.bind(this)) .on("error", this.onError.bind(this)) .on("close", this.onClose.bind(this)); return this; } /** * Opens a connection to the server * @return {Promise} resolves with the server's greeting */ async open() { if (this.connected) throw new Error("already connected!"); debug("connecting"); const receiveGreetingResponse = new Promise((resolve, reject) => { this.pending.unshift({ resolve, reject }); }); if (this.tlsOptions !== undefined) { this.socket = (0, tls_1.connect)({ host: this.host, port: Number(this.port), ...this.tlsOptions, }); this.socket.setKeepAlive(true); this.listen(); // Re-setup event listeners for the TLS socket } else { this.socket.connect(this.port, this.host || "127.0.0.1"); } const response = await receiveGreetingResponse; const greeting = JSON.parse(response.split(" ")[1]); this.emit("greeting", greeting); return greeting; } /** * @private */ onConnect() { this.connected = true; this.emit("connect"); this.setTimeout(); } /** * @private */ clearPending(err) { this.pending.forEach(({ reject }) => reject(err)); } /** * @private */ onClose() { debug("close"); this.closing = false; this.connected = false; this.emit("close"); // dead letters? this.clearPending(this.lastError || new Error("Connection closed")); } /** * @private */ onTimeout() { this.emit("timeout"); debug("timeout"); } /** * Sends a command to the faktory server and asserts that the response * matches the provided expectedResponse argument * @param {Command} command command * @param {String} expectedResponse the expected string response from the server. If the * response from the server does not match, an error is * thrown * @return {String} the server's response string * @throws {AssertionError} */ async sendWithAssert(command, expectedResponse) { const response = await this.send(command); (0, assert_1.strictEqual)(response, expectedResponse, `expected ${expectedResponse} response, but got ${response}`); return response; } /** * Sends a command to the server * @param {Command} command command to send to server * is an array of strings or objects * @return {Promise} resolved with the server's parsed response or * rejected with an error */ send(command) { const commandString = command.join(" "); debug("SEND: %s", commandString); return new Promise((resolve, reject) => { this.socket.write(`${commandString}\r\n`); this.pending.unshift({ resolve: (message) => { debug("client=%o, server=%o", commandString, message); resolve(message); }, reject, }); }); } /** * @private */ onError(err) { this.lastError = err; this.emit("error", err); this.close(); } /** * Closes the connection to the server * @return {Promise} resolved when underlying socket emits "close" */ async close() { if (this.closing) return; this.closing = true; return new Promise((resolve) => this.socket .once("close", () => { this.socket.removeAllListeners(); resolve(); }) .end("END\r\n")); } } exports.Connection = Connection;