padlocal-client-ts
Version:
Padlocal ts client
212 lines • 10.3 kB
JavaScript
;
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