UNPKG

padlocal-client-ts

Version:
212 lines 10.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.IOError = exports.SocketClient = void 0; const net_1 = require("net"); const RetryStrategy_1 = require("../utils/RetryStrategy"); const ByteUtils_1 = require("../utils/ByteUtils"); const verror_1 = __importDefault(require("verror")); const SerialExecutor_1 = require("../utils/SerialExecutor"); const Utils_1 = require("../utils/Utils"); const Log_1 = __importDefault(require("../utils/Log")); class SendDataBlockQueue { constructor(data) { const blocksCount = Math.floor((data.length + SendDataBlockQueue.BLOCK_MAX_SIZE - 1) / SendDataBlockQueue.BLOCK_MAX_SIZE); if (blocksCount > 1) { this._dataToSendBlocks = []; for (let i = 0; i < blocksCount; ++i) { const start = i * SendDataBlockQueue.BLOCK_MAX_SIZE; const end = Math.min(data.length, start + SendDataBlockQueue.BLOCK_MAX_SIZE); const block = ByteUtils_1.subBytes(data, start, end); this._dataToSendBlocks.push(block); } } else { this._dataToSendBlocks = [data]; } this._cursor = 0; } hasMoreDataToSend() { return this._cursor < this._dataToSendBlocks.length; } getNextDataBlock() { return this._dataToSendBlocks[this._cursor++]; } resetCursor() { this._cursor = 0; } } SendDataBlockQueue.BLOCK_MAX_SIZE = 65536; class SocketClient { constructor(host, port, traceId, callback) { this.retryStrategy = RetryStrategy_1.RetryStrategy.getStrategy(RetryStrategy_1.RetryStrategyRule.FAST, 5); // retry almost 1 min this._id = Utils_1.genUUID().substr(0, 4); this.host = host; this.port = port; this.traceId = traceId; this._callback = callback; this._retryOnError = true; this._callbackExecutor = new SerialExecutor_1.SerialExecutor(); } get LOGPRE() { return `[SocketClient] [${this._id}]`; } send(data) { return __awaiter(this, void 0, void 0, function* () { this._retryOnError = true; this._callbackExecutor.clear(); try { yield this._sendImpl(data); } catch (error) { if (!this._retryOnError || !this.retryStrategy.canRetry()) { const des = `[tid:${this.traceId}] Fail to send socket to:\"${this.host}:${this.port}\", data:${ByteUtils_1.bytesToHexString(data, ByteUtils_1.MAX_LOG_BYTES_LEN)}, after max retry:${this.retryStrategy.retryCount}`; throw new IOError(error, des); } const delay = this.retryStrategy.nextRetryDelay(); Log_1.default.silly(this.LOGPRE, `[tid:${this.traceId}] socket #${this.retryStrategy.retryCount} retry send, after delay: ${delay}ms, addr:\"${this.host}:${this.port}\" data:${ByteUtils_1.bytesToHexString(data, ByteUtils_1.MAX_LOG_BYTES_LEN)}`); return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { setTimeout(() => { try { this.send(data); resolve(); } catch (e) { reject(e); } }, delay); })); } }); } cancel() { if (!this._socket) { return; } this._socket.destroy(); this._socket = undefined; this._callbackExecutor .execute(() => __awaiter(this, void 0, void 0, function* () { var _a, _b; yield ((_b = (_a = this._callback) === null || _a === void 0 ? void 0 : _a.onCancel) === null || _b === void 0 ? void 0 : _b.call(_a)); })) .then(); } _sendImpl(sendData) { return __awaiter(this, void 0, void 0, function* () { if (this._socket) { Log_1.default.warn(this.LOGPRE, "can not send again while socket is working"); return; } this._sendDataBlockQueue = new SendDataBlockQueue(sendData); const sendDataBlock = (socket, dataQueue) => { if (socket.destroyed || !dataQueue.hasMoreDataToSend()) { return; } const block = dataQueue.getNextDataBlock(); Log_1.default.silly(this.LOGPRE, `socket send:${ByteUtils_1.bytesToHexString(block, ByteUtils_1.MAX_LOG_BYTES_LEN)}`); socket.write(block, (err) => { if (err) { // stop sending next block while error happen. // do not handle this error, because on-error event will be issued. return; } sendDataBlock(socket, dataQueue); }); }; return new Promise((resolve, reject) => { const onSocketFinish = (error, retryOnError = true) => { if (!this._socket) { return; } if (error) { this._callbackExecutor.execute(() => __awaiter(this, void 0, void 0, function* () { var _a, _b; yield ((_b = (_a = this._callback) === null || _a === void 0 ? void 0 : _a.onError) === null || _b === void 0 ? void 0 : _b.call(_a, error)); })); Log_1.default.silly(this.LOGPRE, `socket on error: ${error}, retryOnError:${retryOnError}`); this._retryOnError = retryOnError; reject(error); } else { resolve(); } Log_1.default.silly(this.LOGPRE, `destroy socket`); this._socket.destroy(); this._socket = undefined; }; const startDate = new Date(); Log_1.default.silly(this.LOGPRE, `socket start connect: ${this.host}:${this.port}`); const socket = new net_1.Socket(); socket.setTimeout(SocketClient.READ_WRITE_TIMEOUT); this._socket = socket; const connectTimeout = setTimeout(() => { onSocketFinish(new IOError("[SocketClient] socket connect timeout")); }, SocketClient.CONNECT_TIMEOUT); socket.connect({ host: this.host, port: this.port, }, () => { clearTimeout(connectTimeout); const endDate = new Date(); Log_1.default.silly(this.LOGPRE, `socket connect success, cost: ${endDate.getTime() - startDate.getTime()}ms`); this._callbackExecutor.execute(() => __awaiter(this, void 0, void 0, function* () { var _a, _b; yield ((_b = (_a = this._callback) === null || _a === void 0 ? void 0 : _a.onConnect) === null || _b === void 0 ? void 0 : _b.call(_a)); })); sendDataBlock(socket, this._sendDataBlockQueue); }); socket.on("data", (data) => { Log_1.default.silly(this.LOGPRE, `socket recv:${ByteUtils_1.bytesToHexString(data, ByteUtils_1.MAX_LOG_BYTES_LEN)}`); this._callbackExecutor.execute(() => __awaiter(this, void 0, void 0, function* () { try { const finished = yield this._callback.onReceive(data); Log_1.default.silly(this.LOGPRE, `process data, finished:${finished}`); if (finished) { // do not execute onReceive in queue this._callbackExecutor.clear("onReceive"); onSocketFinish(); } } catch (e) { // do not execute onReceive in queue this._callbackExecutor.clear("onReceive"); onSocketFinish(e, false); } }), "onReceive"); }); socket.on("close", () => { this._callbackExecutor.execute(() => __awaiter(this, void 0, void 0, function* () { var _a, _b; yield ((_b = (_a = this._callback) === null || _a === void 0 ? void 0 : _a.onClose) === null || _b === void 0 ? void 0 : _b.call(_a)); })); onSocketFinish(); }); socket.on("timeout", () => { onSocketFinish(new IOError("[SocketClient] socket is read-write timeout")); }); socket.on("error", (error) => { onSocketFinish(new IOError(error, "[SocketClient] socket error")); }); }); }); } } exports.SocketClient = SocketClient; SocketClient.CONNECT_TIMEOUT = 10 * 1000; SocketClient.READ_WRITE_TIMEOUT = 10 * 1000; class IOError extends verror_1.default { } exports.IOError = IOError; //# sourceMappingURL=SocketClient.js.map