@dalongrong/nacos-config
Version:
nacos config client
259 lines • 11.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpAgent = void 0;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const const_1 = require("./const");
const interface_1 = require("./interface");
const urllib = require("urllib");
const crypto = require("crypto");
const utils_1 = require("./utils");
const dns = require("dns");
class HttpAgent {
constructor(options) {
this.loggerDomain = 'Nacos';
this.debugPrefix = this.loggerDomain.toLowerCase();
this.debug = require('debug')(`${this.debugPrefix}:${process.pid}:http_agent`);
this.options = options;
this._accessToken = null;
}
get configuration() {
return this.options.configuration;
}
get serverListMgr() {
return this.configuration.get(interface_1.ClientOptionKeys.SERVER_MGR);
}
/**
* HTTP 请求客户端
*/
get httpclient() {
return this.configuration.get(interface_1.ClientOptionKeys.HTTPCLIENT) || urllib;
}
get unit() {
return this.configuration.get(interface_1.ClientOptionKeys.UNIT);
}
get secretKey() {
return this.configuration.get(interface_1.ClientOptionKeys.SECRETKEY);
}
get requestTimeout() {
return this.configuration.get(interface_1.ClientOptionKeys.REQUEST_TIMEOUT);
}
get accessKey() {
return this.configuration.get(interface_1.ClientOptionKeys.ACCESSKEY);
}
get ssl() {
return this.configuration.get(interface_1.ClientOptionKeys.SSL);
}
get serverPort() {
return this.configuration.get(interface_1.ClientOptionKeys.SERVER_PORT);
}
get contextPath() {
return this.configuration.get(interface_1.ClientOptionKeys.CONTEXTPATH) || 'nacos';
}
get clusterName() {
return this.configuration.get(interface_1.ClientOptionKeys.CLUSTER_NAME) || 'serverlist';
}
get defaultEncoding() {
return this.configuration.get(interface_1.ClientOptionKeys.DEFAULT_ENCODING) || 'utf8';
}
get identityKey() {
return this.configuration.get(interface_1.ClientOptionKeys.IDENTITY_KEY);
}
get identityValue() {
return this.configuration.get(interface_1.ClientOptionKeys.IDENTITY_VALUE);
}
get endpointQueryParams() {
return this.configuration.get(interface_1.ClientOptionKeys.ENDPOINT_QUERY_PARAMS);
}
get decodeRes() {
return this.configuration.get(interface_1.ClientOptionKeys.DECODE_RES);
}
async login(username, password) {
const unit = this.unit;
const currentServer = await this.serverListMgr.getCurrentServerAddr(unit);
let url = this.getRequestUrl(currentServer) + `/v1/auth/login`;
try {
const { encode = false, method = 'GET', data, timeout = this.requestTimeout, headers = {}, dataAsQueryString = false } = this.options;
let requestData = {
username: username,
password: password,
};
if (encode) {
requestData = (0, utils_1.encodingParams)(data, this.defaultEncoding);
}
const res = await this.httpclient.request(url, {
rejectUnauthorized: false,
httpsAgent: false,
method,
data: requestData,
dataType: 'text',
headers,
timeout,
secureProtocol: 'TLSv1_2_method',
dataAsQueryString,
});
this.debug('%s %s, got %s, body: %j', method, url, res.status, res.data);
switch (res.status) {
case const_1.HTTP_OK:
if (this.decodeRes) {
return Object.assign(Object.assign({}, JSON.parse(this.decodeRes(res, method, this.defaultEncoding))), { genTime: Date.now() });
}
return Object.assign(Object.assign({}, JSON.parse(this.decodeResData(res, method))), { genTime: Date.now() });
case const_1.HTTP_NOT_FOUND:
return null;
case const_1.HTTP_CONFLICT:
await this.serverListMgr.updateCurrentServer(unit);
break;
default:
await this.serverListMgr.updateCurrentServer(unit);
break;
}
}
catch (err) {
if (err.code === dns.NOTFOUND) {
throw err;
}
}
}
/**
* 请求
* @param {String} path - 请求 path
* @param {Object} [options] - 参数
* @return {String} value
*/
async request(path, options = {}) {
var _a, _b, _c, _d, _e, _f;
// 默认为当前单元
const unit = options.unit || this.unit;
const ts = String(Date.now());
const { encode = false, method = 'GET', data, timeout = this.requestTimeout, headers = {}, dataAsQueryString = false } = options;
const endTime = Date.now() + timeout;
let lastErr;
if (((_c = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.configuration) === null || _b === void 0 ? void 0 : _b.innerConfig) === null || _c === void 0 ? void 0 : _c.username) &&
((_f = (_e = (_d = this.options) === null || _d === void 0 ? void 0 : _d.configuration) === null || _e === void 0 ? void 0 : _e.innerConfig) === null || _f === void 0 ? void 0 : _f.password)) {
if (this._accessToken) {
if (Date.now() - this._accessToken.genTime < this._accessToken.tokenTtl) {
this._accessToken = await this.login(this.options.configuration.innerConfig.username, this.options.configuration.innerConfig.password);
}
}
else {
this._accessToken = await this.login(this.options.configuration.innerConfig.username, this.options.configuration.innerConfig.password);
}
data.accessToken = this._accessToken.accessToken;
}
let signStr = data.tenant;
if (data.group && data.tenant) {
signStr = data.tenant + '+' + data.group;
}
else if (data.group) {
signStr = data.group;
}
const signature = crypto.createHmac('sha1', this.secretKey)
.update(signStr + '+' + ts).digest()
.toString('base64');
// 携带统一的头部信息
Object.assign(headers, Object.assign({ 'Client-Version': const_1.VERSION, 'Content-Type': 'application/x-www-form-urlencoded; charset=GBK', 'Spas-AccessKey': this.accessKey, timeStamp: ts, exConfigInfo: 'true', 'Spas-Signature': signature }, this.identityKey ? { [this.identityKey]: this.identityValue } : {}));
let requestData = data;
if (encode) {
requestData = (0, utils_1.encodingParams)(data, this.defaultEncoding);
}
do {
const currentServer = await this.serverListMgr.getCurrentServerAddr(unit);
let url = this.getRequestUrl(currentServer) + `${path}`;
this.debug('request unit: [%s] with url: %s', unit, url);
try {
const res = await this.httpclient.request(url, {
rejectUnauthorized: false,
httpsAgent: false,
method,
data: requestData,
dataType: 'text',
headers,
timeout,
secureProtocol: 'TLSv1_2_method',
dataAsQueryString,
});
this.debug('%s %s, got %s, body: %j', method, url, res.status, res.data);
switch (res.status) {
case const_1.HTTP_OK:
if (this.decodeRes) {
return this.decodeRes(res, method, this.defaultEncoding);
}
return this.decodeResData(res, method);
case const_1.HTTP_NOT_FOUND:
return null;
case const_1.HTTP_CONFLICT:
await this.serverListMgr.updateCurrentServer(unit);
// JAVA 在外面业务类处理的这个逻辑,应该是需要重试的
lastErr = new Error(`[Client Worker] ${this.loggerDomain} server config being modified concurrently, data: ${JSON.stringify(data)}`);
lastErr.name = `${this.loggerDomain}ServerConflictError`;
break;
default:
await this.serverListMgr.updateCurrentServer(unit);
// JAVA 还有一个针对 HTTP_FORBIDDEN 的处理,不过合并到 default 应该也没问题
lastErr = new Error(`${this.loggerDomain} Server Error Status: ${res.status}, url: ${url}, data: ${JSON.stringify(data)}`);
lastErr.name = `${this.loggerDomain}ServerResponseError`;
lastErr.body = res.data;
break;
}
}
catch (err) {
if (err.code === dns.NOTFOUND) {
throw err;
}
err.url = `${method} ${url}`;
err.data = data;
err.headers = headers;
lastErr = err;
}
} while (Date.now() < endTime);
throw lastErr;
}
// 获取请求 url
getRequestUrl(currentServer) {
let url;
if (/:/.test(currentServer)) {
url = `http://${currentServer}`;
if (this.ssl) {
url = `https://${currentServer}`;
}
}
else {
url = `http://${currentServer}:${this.serverPort}`;
if (this.ssl) {
url = `https://${currentServer}:${this.serverPort}`;
}
}
return `${url}/${this.contextPath}`;
}
decodeResData(res, method = 'GET') {
if (method === 'GET' && /charset=GBK/.test(res.headers['content-type']) && this.defaultEncoding === 'utf8') {
try {
return (0, utils_1.transformGBKToUTF8)(res.data);
}
catch (err) {
console.error(`transform gbk data to utf8 error, msg=${err.messager}`);
return res.data;
}
}
else {
return res.data;
}
}
}
exports.HttpAgent = HttpAgent;
//# sourceMappingURL=http_agent.js.map