├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── config_service.js ├── config_service_direct.js └── package.json ├── lerna.json ├── package.json ├── packages ├── nacos-config │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── client.ts │ │ ├── client_worker.ts │ │ ├── configuration.ts │ │ ├── const.ts │ │ ├── http_agent.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── server_list_mgr.ts │ │ ├── snapshot.ts │ │ └── utils.ts │ ├── test │ │ ├── client.test.ts │ │ ├── client_worker.test.ts │ │ ├── index.test.ts │ │ ├── server_list_mgr.test.ts │ │ ├── snapshot.test.ts │ │ ├── utils.test.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── tslint.json ├── nacos-naming │ ├── .autod.conf.js │ ├── .eslintignore │ ├── .eslintrc │ ├── .travis.yml │ ├── CHANGELOG.md │ ├── README.md │ ├── example │ │ └── client.js │ ├── index.d.ts │ ├── index.js │ ├── lib │ │ ├── const.js │ │ ├── naming │ │ │ ├── beat_reactor.js │ │ │ ├── client.js │ │ │ ├── host_reactor.js │ │ │ ├── instance.js │ │ │ ├── proxy.js │ │ │ ├── push_receiver.js │ │ │ └── service_info.js │ │ └── util │ │ │ └── index.js │ ├── package.json │ └── test │ │ ├── naming │ │ ├── client.test.js │ │ ├── host_reactor.test.js │ │ ├── instance.test.js │ │ ├── proxy.test.js │ │ ├── push_receiver.test.js │ │ └── service_info.test.js │ │ └── util │ │ └── index.test.js └── nacos │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ └── tsconfig.json └── scripts ├── cov.sh ├── publish.sh └── tag.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .DS_Store 61 | 62 | # next.js build output 63 | .next 64 | 65 | .idea 66 | dist/ 67 | package-lock.json 68 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | - '10' 6 | - '12' 7 | jdk: oraclejdk8 8 | before_install: 9 | - echo $JAVA_HOME 10 | - java -version 11 | - 'wget https://github.com/alibaba/nacos/releases/download/1.0.0/nacos-server-1.0.0.tar.gz' 12 | - 'wget https://github.com/gxcsoccer/PDisk/releases/download/1.0.1/startup.sh' 13 | - 'tar xf nacos-server-1.0.0.tar.gz' 14 | - 'mv ./startup.sh ./nacos/bin/startup.sh' 15 | - 'chmod 755 ./nacos/bin/startup.sh' 16 | - 'nohup ./nacos/bin/startup.sh -m standalone 2>&1 &' 17 | - 'sleep 30' 18 | - 'cat nohup.out' 19 | - 'curl "127.0.0.1:8848/nacos/v1/ns/operator/metrics"' 20 | install: 21 | - npm i npminstall && npminstall 22 | - npm run bootstrap 23 | - npm run build 24 | script: 25 | - npm run ci 26 | after_script: 27 | - npminstall codecov && codecov 28 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Ordered by date of first contribution. 2 | # Auto-generated by 'contributors' on Tue, 26 Jan 2021 05:30:11 GMT. 3 | # https://github.com/xingrz/node-contributors 4 | 5 | yanlinly 6 | zōng yǔ (https://github.com/gxcsoccer) 7 | Harry Chen (https://github.com/czy88840616) 8 | zōng yǔ 9 | netfoxor 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.0.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v2.0.0...v2.0.1) (2021-01-26) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * fix the request address is undefined after SSL is enabled ([#42](https://github.com/nacos-group/nacos-sdk-nodejs/issues/42)) ([6146235](https://github.com/nacos-group/nacos-sdk-nodejs/commit/614623577baa510fefc575f875e2ff3076f8a781)) 12 | 13 | 14 | 15 | 16 | 17 | ## [1.1.2](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.1.1...v1.1.2) (2019-03-31) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * fix gbk text ([93c3559](https://github.com/nacos-group/nacos-sdk-nodejs/commit/93c3559)) 23 | * fix options in direct mode ([2942e19](https://github.com/nacos-group/nacos-sdk-nodejs/commit/2942e19)) 24 | 25 | 26 | 27 | 28 | 29 | ## [1.1.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.1.0...v1.1.1) (2019-01-17) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * beatinfo is wrong ([26f8cee](https://github.com/nacos-group/nacos-sdk-nodejs/commit/26f8cee)) 35 | 36 | 37 | 38 | 39 | 40 | # [1.1.0](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.0.2...v1.1.0) (2019-01-15) 41 | 42 | 43 | ### Features 44 | 45 | * support registerInstance(serviceName, instance) ([32d2670](https://github.com/nacos-group/nacos-sdk-nodejs/commit/32d2670)) 46 | 47 | 48 | 49 | 50 | 51 | ## [1.0.2](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.0.1...v1.0.2) (2018-12-12) 52 | 53 | **Note:** Version bump only for package nacos 54 | 55 | 56 | 57 | 58 | 59 | ## [1.0.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.0.0...v1.0.1) (2018-12-10) 60 | 61 | **Note:** Version bump only for package nacos 62 | 63 | 64 | 65 | 66 | 67 | ## [0.2.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v0.2.0...v0.2.1) (2018-12-03) 68 | 69 | **Note:** Version bump only for package nacos 70 | 71 | 72 | 73 | 74 | 75 | # 0.2.0 (2018-12-03) 76 | 77 | 78 | ### Features 79 | 80 | * add config code ([3d7ebfa](https://github.com/nacos-group/nacos-sdk-nodejs/commit/3d7ebfa)) 81 | * export in one package ([fa747bc](https://github.com/nacos-group/nacos-sdk-nodejs/commit/fa747bc)) 82 | * implement nacos naming sdk ([d11c0df](https://github.com/nacos-group/nacos-sdk-nodejs/commit/d11c0df)) 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nacos-sdk-nodejs 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![build status][travis-image]][travis-url] 5 | [![David deps][david-image]][david-url] 6 | [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lernajs.io/) 7 | 8 | [npm-image]: https://img.shields.io/npm/v/nacos.svg?style=flat-square 9 | [npm-url]: https://npmjs.org/package/nacos 10 | [travis-image]: https://img.shields.io/travis/nacos-group/nacos-sdk-nodejs.svg?style=flat-square 11 | [travis-url]: https://travis-ci.org/nacos-group/nacos-sdk-nodejs 12 | [david-image]: https://img.shields.io/david/nacos-group/nacos-sdk-nodejs.svg?style=flat-square 13 | [david-url]: https://david-dm.org/nacos-group/nacos-sdk-nodejs 14 | 15 | 16 | [Nacos](https://nacos.io/en-us/) Node.js SDK 17 | 18 | ## Install 19 | 20 | ```bash 21 | npm install nacos --save 22 | ``` 23 | 24 | ## Version Mapping 25 | 26 | Node.js SDK \ Nacos Server | 0.x.0 | 1.0.0 | 27 | --- | --- | --- | 28 | 1.x | √ | | 29 | 2.x | | √ | 30 | 31 | ## Usage 32 | 33 | ### Service Discovery 34 | 35 | ```js 36 | 'use strict'; 37 | 38 | const NacosNamingClient = require('nacos').NacosNamingClient; 39 | const logger = console; 40 | 41 | const client = new NacosNamingClient({ 42 | logger, 43 | serverList: '127.0.0.1:8848', // replace to real nacos serverList 44 | namespace: 'public', 45 | }); 46 | await client.ready(); 47 | 48 | const serviceName = 'nodejs.test.domain'; 49 | 50 | // registry instance 51 | await client.registerInstance(serviceName, { 52 | ip: '1.1.1.1', 53 | port: 8080, 54 | }); 55 | await client.registerInstance(serviceName, { 56 | ip: '2.2.2.2', 57 | port: 8080, 58 | }); 59 | 60 | // subscribe instance 61 | client.subscribe(serviceName, hosts => { 62 | console.log(hosts); 63 | }); 64 | 65 | // deregister instance 66 | await client.deregisterInstance(serviceName, { 67 | ip: '1.1.1.1', 68 | port: 8080, 69 | }); 70 | ``` 71 | 72 | ### Config Service 73 | 74 | ```js 75 | import {NacosConfigClient} from 'nacos'; // ts 76 | const NacosConfigClient = require('nacos').NacosConfigClient; // js 77 | 78 | // for find address mode 79 | const configClient = new NacosConfigClient({ 80 | endpoint: 'acm.aliyun.com', 81 | namespace: '***************', 82 | accessKey: '***************', 83 | secretKey: '***************', 84 | requestTimeout: 6000, 85 | }); 86 | 87 | // for direct mode 88 | const configClient = new NacosConfigClient({ 89 | serverAddr: '127.0.0.1:8848', 90 | }); 91 | 92 | // get config once 93 | const content= await configClient.getConfig('test', 'DEFAULT_GROUP'); 94 | console.log('getConfig = ',content); 95 | 96 | // listen data changed 97 | configClient.subscribe({ 98 | dataId: 'test', 99 | group: 'DEFAULT_GROUP', 100 | }, content => { 101 | console.log(content); 102 | }); 103 | 104 | // publish config 105 | const content= await configClient.publishSingle('test', 'DEFAULT_GROUP', '测试'); 106 | console.log('getConfig = ',content); 107 | 108 | // remove config 109 | await configClient.remove('test', 'DEFAULT_GROUP'); 110 | ``` 111 | 112 | NacosConfigClient options: [ClientOptions](https://github.com/nacos-group/nacos-sdk-nodejs/blob/master/packages/nacos-config/src/interface.ts#L247) 113 | 114 | default value: [ClientOptions default value](https://github.com/nacos-group/nacos-sdk-nodejs/blob/master/packages/nacos-config/src/const.ts#L34) 115 | 116 | ## APIs 117 | 118 | ### Service Discovery 119 | 120 | - `registerInstance(serviceName, instance, [groupName])` Register an instance to service. 121 | - serviceName {String} Service name 122 | - instance {Instance} 123 | - ip {String} IP of instance 124 | - port {Number} Port of instance 125 | - [weight] {Number} weight of the instance, default is 1.0 126 | - [ephemeral] {Boolean} active until the client is alive, default is true 127 | - [clusterName] {String} Virtual cluster name 128 | - [groupName] {String} group name, default is `DEFAULT_GROUP` 129 | - `deregisterInstance(serviceName, ip, port, [cluster])` Delete instance from service. 130 | - serviceName {String} Service name 131 | - instance {Instance} 132 | - ip {String} IP of instance 133 | - port {Number} Port of instance 134 | - [weight] {Number} weight of the instance, default is 1.0 135 | - [ephemeral] {Boolean} active until the client is alive, default is true 136 | - [clusterName] {String} Virtual cluster name 137 | - [groupName] {String} group name, default is `DEFAULT_GROUP` 138 | - `getAllInstances(serviceName, [groupName], [clusters], [subscribe])` Query instance list of service. 139 | - serviceName {String} Service name 140 | - [groupName] {String} group name, default is `DEFAULT_GROUP` 141 | - [clusters] {String} Cluster names 142 | - [subscribe] {Boolean} whether subscribe the service, default is true 143 | - `getServerStatus()` Get the status of nacos server, 'UP' or 'DOWN'. 144 | - `subscribe(info, listener)` Subscribe the instances of the service 145 | - info {Object}|{String} service info, if type is string, it's the serviceName 146 | - listener {Function} the listener function 147 | - `unSubscribe(info, [listener])` Unsubscribe the instances of the service 148 | - info {Object}|{String} service info, if type is string, it's the serviceName 149 | - listener {Function} the listener function, if not provide, will unSubscribe all listeners under this service 150 | 151 | ### Config Service 152 | 153 | - `async function getConfig(dataId, group)` 154 | - {String} dataId - data id 155 | - {String} group - group name 156 | - `async function publishSingle(dataId, group, content)` 157 | - {String} dataId - data id 158 | - {String} group - group name 159 | - {String} content - content you want to publish 160 | - `async function remove(dataId, group)` 161 | - {String} dataId - data id 162 | - {String} group - group name 163 | - `function subscribe(info, listener)` 164 | - {Object} info 165 | - {String} dataId - data id 166 | - {String} group - group name 167 | - {Function} listener - callback handler 168 | - `function unSubscribe(info, [listener])` 169 | - {Object} info 170 | - {String} dataId - data id 171 | - {String} group - group 172 | - {Function} listener - callback handler(optional,remove all listener when it is null) 173 | 174 | ## Questions & Suggestions 175 | 176 | Please let us know how can we help. Do check out [issues](https://github.com/nacos-group/nacos-sdk-nodejs/issues) for bug reports or suggestions first. 177 | 178 | PR is welcome. 179 | 180 | nacos-sdk-nodejs ding group : 44654232 181 | ![image](https://user-images.githubusercontent.com/17695352/172582005-c661e2a0-49fa-425c-bf99-785bb7cd4dc1.png) 182 | 183 | 184 | ## License 185 | 186 | [Apache License V2](LICENSE) 187 | -------------------------------------------------------------------------------- /example/config_service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const NacosConfigClient = require('nacos').NacosConfigClient; 4 | 5 | const configClient = new NacosConfigClient({ 6 | endpoint: 'acm.aliyun.com', 7 | namespace: '81597370-5076-4216-9df5-538a2b55bac3', 8 | accessKey: '4c796a4dcd0d4f5895d4ba83a296b489', // this accessKey just for testing and unstable,please put your key in application 9 | secretKey: 'UjLemP8inirhjMg1NZyY0faOk1E=', 10 | requestTimeout: 6000, 11 | }); 12 | 13 | function sleep(time){ 14 | return new Promise((resolve) => { 15 | setTimeout(() => { 16 | resolve(); 17 | }, time); 18 | }) 19 | } 20 | 21 | (async () => { 22 | await configClient.ready(); 23 | 24 | const dataId = 'nacos.test.1'; 25 | const group = 'DEFAULT_GROUP'; 26 | const str = `example_test_${Math.random()}_${Date.now()}`; 27 | 28 | console.log('---------ready to publish------------'); 29 | await configClient.publishSingle(dataId, group, str); 30 | await sleep(2000); 31 | console.log('---------publish complete------------'); 32 | await sleep(2000); 33 | console.log('---------ready to getConfig----------'); 34 | await sleep(2000); 35 | let content = await configClient.getConfig(dataId, group); 36 | console.log('---------getConfig complete----------'); 37 | console.log('current content => ' + content); 38 | await sleep(2000); 39 | console.log('---------ready to remove config------'); 40 | await configClient.remove(dataId, group); 41 | console.log('---------remove config complete------'); 42 | await sleep(2000); 43 | content = await configClient.getConfig(dataId, group); 44 | console.log('---------getConfig complete----------'); 45 | console.log('current content => ' + content); 46 | await sleep(2000); 47 | console.log('---------remove config success-------'); 48 | configClient.close(); 49 | process.exit(0); 50 | })(); 51 | -------------------------------------------------------------------------------- /example/config_service_direct.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const NacosConfigClient = require('nacos').NacosConfigClient; 4 | 5 | const configClient = new NacosConfigClient({ 6 | serverAddr: 'aliyun.nacos.net:80', 7 | namespace: '', 8 | // 如果nacos开启了认证鉴权,需要在此处填写用户名密码 9 | // username: 'xxx', 10 | // password: 'xxx' 11 | }); 12 | 13 | function sleep(time){ 14 | return new Promise((resolve) => { 15 | setTimeout(() => { 16 | resolve(); 17 | }, time); 18 | }) 19 | } 20 | 21 | (async () => { 22 | await configClient.ready(); 23 | 24 | const dataId = 'nacos.test.22'; 25 | const group = 'DEFAULT_GROUP'; 26 | const str = `example_test_${Math.random()}_${Date.now()}`; 27 | 28 | console.log('---------ready to publish------------'); 29 | await configClient.publishSingle(dataId, group, str); 30 | await sleep(2000); 31 | console.log('---------publish complete------------'); 32 | await sleep(2000); 33 | console.log('---------ready to getConfig----------'); 34 | await sleep(2000); 35 | let content = await configClient.getConfig(dataId, group); 36 | console.log('---------getConfig complete----------'); 37 | console.log('current content => ' + content); 38 | await sleep(2000); 39 | console.log('---------ready to remove config------'); 40 | await configClient.remove(dataId, group); 41 | console.log('---------remove config complete------'); 42 | await sleep(2000); 43 | content = await configClient.getConfig(dataId, group); 44 | console.log('---------getConfig complete----------'); 45 | console.log('current content => ' + content); 46 | await sleep(2000); 47 | console.log('---------remove config success-------'); 48 | configClient.close(); 49 | process.exit(0); 50 | })(); 51 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nacos-example", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "nacos": "^2.5.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "command": { 6 | "bootstrap": { 7 | "hoist": true, 8 | "noCi": true, 9 | "npmClientArgs": [ 10 | "--no-package-lock" 11 | ] 12 | }, 13 | "publish": { 14 | "ignoreChanges": [ 15 | "*.md" 16 | ] 17 | } 18 | }, 19 | "version": "2.6.0" 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nacos", 3 | "version": "1.0.0", 4 | "description": "nacos (https://nacos.io/en-us/) nodejs sdk", 5 | "scripts": { 6 | "contributors": "contributors -f plain -o AUTHORS", 7 | "clean": "lerna clean --yes; rm -rf ./packages/**/package-lock.json", 8 | "bootstrap": "rm -f ./packages/.DS*; lerna bootstrap --no-ci", 9 | "release": "rm -f ./packages/.DS*; sh scripts/publish.sh", 10 | "next": "sh scripts/publish.sh --npm-tag next", 11 | "test": "lerna run test", 12 | "cov": "sh scripts/cov.sh", 13 | "ci": "npm run cov", 14 | "build": "lerna run build && cp ./README.md ./packages/nacos/README.md" 15 | }, 16 | "license": "Apache-2.0", 17 | "bugs": { 18 | "url": "https://github.com/nacos-group/nacos-sdk-nodejs/issues" 19 | }, 20 | "homepage": "https://github.com/nacos-group/nacos-sdk-nodejs#readme", 21 | "engines": { 22 | "node": ">= 8.0.0" 23 | }, 24 | "ci": { 25 | "type": "travis", 26 | "version": "8, 10" 27 | }, 28 | "devDependencies": { 29 | "contributors": "^0.5.1", 30 | "lerna": "^3.4.3", 31 | "lerna-relinker": "^1.5.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/nacos-config/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | .idea 64 | dist/ 65 | package-lock.json 66 | .cache 67 | -------------------------------------------------------------------------------- /packages/nacos-config/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.0.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v2.0.0...v2.0.1) (2021-01-26) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * fix the request address is undefined after SSL is enabled ([#42](https://github.com/nacos-group/nacos-sdk-nodejs/issues/42)) ([6146235](https://github.com/nacos-group/nacos-sdk-nodejs/commit/614623577baa510fefc575f875e2ff3076f8a781)) 12 | 13 | 14 | 15 | 16 | 17 | ## [1.1.2](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.1.1...v1.1.2) (2019-03-31) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * fix gbk text ([93c3559](https://github.com/nacos-group/nacos-sdk-nodejs/commit/93c3559)) 23 | * fix options in direct mode ([2942e19](https://github.com/nacos-group/nacos-sdk-nodejs/commit/2942e19)) 24 | 25 | 26 | 27 | 28 | 29 | # [1.1.0](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.0.2...v1.1.0) (2019-01-15) 30 | 31 | 32 | ### Features 33 | 34 | * support registerInstance(serviceName, instance) ([32d2670](https://github.com/nacos-group/nacos-sdk-nodejs/commit/32d2670)) 35 | 36 | 37 | 38 | 39 | 40 | ## [1.0.2](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.0.1...v1.0.2) (2018-12-12) 41 | 42 | **Note:** Version bump only for package nacos-config 43 | 44 | 45 | 46 | 47 | 48 | # 0.2.0 (2018-12-03) 49 | 50 | 51 | ### Features 52 | 53 | * add config code ([3d7ebfa](https://github.com/nacos-group/nacos-sdk-nodejs/commit/3d7ebfa)) 54 | * export in one package ([fa747bc](https://github.com/nacos-group/nacos-sdk-nodejs/commit/fa747bc)) 55 | -------------------------------------------------------------------------------- /packages/nacos-config/README.md: -------------------------------------------------------------------------------- 1 | # nacos-config 2 | 3 | > Nacos config client for Node.js 客户端 https://help.aliyun.com/document_detail/60137.html 4 | 5 | 重新使用了 typescript 编码,使用 async/await 重构。 6 | 7 | ## Usage 8 | 9 | ```js 10 | import {NacosConfigClient} from 'nacos'; // ts 11 | const NacosConfigClient = require('nacos').NacosConfigClient; // js 12 | 13 | // 下面的代码是寻址模式 14 | const configClient = new NacosConfigClient({ 15 | endpoint: 'acm.aliyun.com', // acm 控制台查看 16 | namespace: '***************', // acm 控制台查看 17 | accessKey: '***************', // acm 控制台查看 18 | secretKey: '***************', // acm 控制台查看 19 | requestTimeout: 6000, // 请求超时时间,默认6s 20 | }); 21 | 22 | // 下面的代码是直连模式 23 | const configClient = new NacosConfigClient({ 24 | serverAddr: '127.0.0.1:8848', // 对端的 ip 和端口,其他参数同寻址模式 25 | }); 26 | 27 | // 主动拉取配置 28 | const content= await configClient.getConfig('test', 'DEFAULT_GROUP'); 29 | console.log('getConfig = ',content); 30 | 31 | // 监听数据更新 32 | configClient.subscribe({ 33 | dataId: 'test', 34 | group: 'DEFAULT_GROUP', 35 | }, content => { 36 | console.log(content); 37 | }); 38 | 39 | // 发布配置接口 40 | const content= await configClient.publishSingle('test', 'DEFAULT_GROUP', '测试'); 41 | console.log('getConfig = ',content); 42 | 43 | // 删除配置 44 | await configClient.remove('test', 'DEFAULT_GROUP'); 45 | 46 | ### Error Events 异常处理 47 | 48 | ```js 49 | configClient.on('error', function (err) { 50 | // 可以在这里统一进行日志的记录 51 | // 如果不监听错误事件,所有的异常都将会打印到 stderr 52 | }); 53 | ``` 54 | 55 | NacosConfigClient 的 options 定义见 [ClientOptions](https://github.com/nacos-group/nacos-sdk-nodejs/blob/master/packages/nacos-config/src/interface.ts#L247) 56 | 57 | 默认值见 [ClientOptions 默认值](https://github.com/nacos-group/nacos-sdk-nodejs/blob/6786534c023c9b5200960363ff6c541707f4d3bf/packages/nacos-config/src/const.ts#L34) 58 | 59 | ### API 60 | 61 | #### 获取配置 62 | * `async function getConfig(dataId, group)` 63 | - {String} dataId - 配置id 64 | - {String} group - 配置分组 65 | 66 | #### 发布配置 67 | * `async function publishSingle(dataId, group, content)` 68 | - {String} dataId - 配置id 69 | - {String} group - 配置分组 70 | - {String} content - 发布内容 71 | 72 | #### 删除配置 73 | * `async function remove(dataId, group)` 74 | - {String} dataId - 配置id 75 | - {String} group - 配置分组 76 | 77 | #### 订阅配置 78 | * `function subscribe(info, listener)` 79 | - {Object} info 80 | - {String} dataId - 配置id 81 | - {String} group - 配置分组 82 | - {Function} listener - 回调函数 83 | 84 | #### 取消订阅 85 | * `function unSubscribe(info, [listener])` 86 | - {Object} info 87 | - {String} dataId - 配置id 88 | - {String} group - 配置分组 89 | - {Function} listener - 回调函数(可选,不传就移除所有监听函数) 90 | 91 | ## Contacts 92 | 93 | * [@Harry Chen](https://github.com/czy88840616) 94 | -------------------------------------------------------------------------------- /packages/nacos-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nacos-config", 3 | "version": "2.6.0", 4 | "description": "nacos config client", 5 | "keywords": [ 6 | "nacos-config", 7 | "nacos" 8 | ], 9 | "main": "dist/index.js", 10 | "author": "czy88840616@gmail.com", 11 | "dependencies": { 12 | "cluster-client": "^2.1.1", 13 | "co-gather": "^1.0.0", 14 | "debug": "^3.1.0", 15 | "iconv-lite": "^0.4.24", 16 | "is-type-of": "^1.2.0", 17 | "mz": "^2.7.0", 18 | "mz-modules": "^2.1.0", 19 | "osenv": "^0.1.5", 20 | "sdk-base": "^3.5.0", 21 | "urlencode": "^1.1.0", 22 | "urllib": "^2.29.1", 23 | "utility": "^1.14.0" 24 | }, 25 | "devDependencies": { 26 | "@types/mocha": "^5.2.5", 27 | "@types/node": "^10.9.4", 28 | "contributors": "^0.5.1", 29 | "midway-bin": "1", 30 | "mm": "^2.4.1", 31 | "pedding": "^1.1.0", 32 | "tslint": "^5.11.0", 33 | "typescript": "^3.2.2" 34 | }, 35 | "engines": { 36 | "node": ">= 8.0.0" 37 | }, 38 | "scripts": { 39 | "autod": "midway-bin autod", 40 | "lint": "tslint . --ext .ts", 41 | "test": "npm run test-local", 42 | "test-local": "TEST_REPORTER=spec midway-bin test --ts", 43 | "cov": "TEST_REPORTER=spec midway-bin cov --ts", 44 | "contributors": "contributors", 45 | "ci": "npm run lint && npm run cov", 46 | "build": "midway-bin build --ts" 47 | }, 48 | "files": [ 49 | "d.ts", 50 | "dist", 51 | "src" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /packages/nacos-config/src/client.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { 18 | BaseClient, 19 | ClientOptionKeys, 20 | ClientOptions, 21 | IClientWorker, 22 | IConfiguration, 23 | IServerListManager, 24 | ISnapshot, 25 | } from './interface'; 26 | import { ServerListManager } from './server_list_mgr'; 27 | import { ClientWorker } from './client_worker'; 28 | import { Snapshot } from './snapshot'; 29 | import { CURRENT_UNIT, DEFAULT_OPTIONS } from './const'; 30 | import { checkParameters } from './utils'; 31 | import { HttpAgent } from './http_agent'; 32 | import { Configuration } from './configuration'; 33 | import * as assert from 'assert'; 34 | 35 | const Base = require('sdk-base'); 36 | 37 | 38 | export class DataClient extends Base implements BaseClient { 39 | 40 | private clients: Map; 41 | private configuration: IConfiguration; 42 | protected snapshot: ISnapshot; 43 | protected serverMgr: IServerListManager; 44 | protected httpAgent; 45 | 46 | constructor(options: ClientOptions) { 47 | if(!options.endpoint && !options.serverAddr) { 48 | assert(options.endpoint, '[Client] options.endpoint or options.serverAddr is required'); 49 | } 50 | 51 | options = Object.assign({}, DEFAULT_OPTIONS, options); 52 | super(options); 53 | this.configuration = this.options.configuration = new Configuration(options); 54 | 55 | this.snapshot = this.getSnapshot(); 56 | this.serverMgr = this.getServerListManager(); 57 | const CustomHttpAgent = this.configuration.get(ClientOptionKeys.HTTP_AGENT); 58 | this.httpAgent = CustomHttpAgent ? new CustomHttpAgent({ configuration: this.configuration }) : new HttpAgent({ configuration: this.configuration }); 59 | 60 | this.configuration.merge({ 61 | snapshot: this.snapshot, 62 | serverMgr: this.serverMgr, 63 | httpAgent: this.httpAgent, 64 | }); 65 | 66 | this.clients = new Map(); 67 | (this.snapshot).on('error', err => this.throwError(err)); 68 | (this.serverMgr).on('error', err => this.throwError(err)); 69 | this.ready(true); 70 | } 71 | 72 | get appName() { 73 | return this.configuration.get(ClientOptionKeys.APPNAME); 74 | } 75 | 76 | get httpclient() { 77 | return this.configuration.get(ClientOptionKeys.HTTPCLIENT); 78 | } 79 | 80 | /** 81 | * 获取当前机器所在机房 82 | * @return {String} currentUnit 83 | */ 84 | async getCurrentUnit() { 85 | return await this.serverMgr.getCurrentUnit(); 86 | } 87 | 88 | /** 89 | * 获取所有单元信息 90 | * @return {Array} units 91 | */ 92 | async getAllUnits() { 93 | return await this.serverMgr.fetchUnitLists(); 94 | } 95 | 96 | /** 97 | * 订阅 98 | * @param {Object} info 99 | * - {String} dataId - id of the data you want to subscribe 100 | * - {String} [group] - group name of the data 101 | * - {String} [unit] - which unit you want to connect, default is current unit 102 | * @param {Function} listener - listener 103 | * @return {DataClient} self 104 | */ 105 | subscribe(info, listener) { 106 | const { dataId, group } = info; 107 | checkParameters(dataId, group); 108 | const client = this.getClient(info); 109 | client.subscribe({ dataId, group }, listener); 110 | return this; 111 | } 112 | 113 | /** 114 | * 退订 115 | * @param {Object} info 116 | * - {String} dataId - id of the data you want to subscribe 117 | * - {String} [group] - group name of the data 118 | * - {String} [unit] - which unit you want to connect, default is current unit 119 | * @param {Function} listener - listener 120 | * @return {DataClient} self 121 | */ 122 | unSubscribe(info, listener) { 123 | const { dataId, group } = info; 124 | checkParameters(dataId, group); 125 | const client = this.getClient(info); 126 | client.unSubscribe({ dataId, group }, listener); 127 | return this; 128 | } 129 | 130 | /** 131 | * 获取配置 132 | * @param {String} dataId - id of the data 133 | * @param {String} group - group name of the data 134 | * @param {Object} options 135 | * - {Stirng} unit - which unit you want to connect, default is current unit 136 | * @return {String} value 137 | */ 138 | async getConfig(dataId, group, options) { 139 | checkParameters(dataId, group); 140 | const client = this.getClient(options); 141 | return await client.getConfig(dataId, group); 142 | } 143 | 144 | /** 145 | * 查询租户下的所有的配置 146 | * @return {Array} config 147 | */ 148 | async getConfigs() { 149 | const client = this.getClient(); 150 | return await client.getConfigs(); 151 | } 152 | 153 | 154 | /** 155 | * 发布配置 156 | * @param {String} dataId - id of the data 157 | * @param {String} group - group name of the data 158 | * @param {String} content - config value 159 | * @param {Object} options 160 | * - {Stirng} unit - which unit you want to connect, default is current unit 161 | * @return {Boolean} success 162 | */ 163 | async publishSingle(dataId, group, content, options) { 164 | checkParameters(dataId, group); 165 | const client = this.getClient(options); 166 | return await client.publishSingle(dataId, group, content, options && options.type); 167 | } 168 | 169 | /** 170 | * 删除配置 171 | * @param {String} dataId - id of the data 172 | * @param {String} group - group name of the data 173 | * @param {Object} options 174 | * - {Stirng} unit - which unit you want to connect, default is current unit 175 | * @return {Boolean} success 176 | */ 177 | async remove(dataId, group, options) { 178 | checkParameters(dataId, group); 179 | const client = this.getClient(options); 180 | return await client.remove(dataId, group); 181 | } 182 | 183 | /** 184 | * 批量获取配置 185 | * @param {Array} dataIds - data id array 186 | * @param {String} group - group name of the data 187 | * @param {Object} options 188 | * - {Stirng} unit - which unit you want to connect, default is current unit 189 | * @return {Array} result 190 | */ 191 | async batchGetConfig(dataIds, group, options) { 192 | checkParameters(dataIds, group); 193 | const client = this.getClient(options); 194 | return await client.batchGetConfig(dataIds, group); 195 | } 196 | 197 | /** 198 | * 批量查询 199 | * @param {Array} dataIds - data id array 200 | * @param {String} group - group name of the data 201 | * @param {Object} options 202 | * - {Stirng} unit - which unit you want to connect, default is current unit 203 | * @return {Object} result 204 | */ 205 | async batchQuery(dataIds, group, options) { 206 | checkParameters(dataIds, group); 207 | const client = this.getClient(options); 208 | return await client.batchQuery(dataIds, group); 209 | } 210 | 211 | /** 212 | * 将配置发布到所有单元 213 | * @param {String} dataId - id of the data 214 | * @param {String} group - group name of the data 215 | * @param {String} content - config value 216 | * @return {Boolean} success 217 | */ 218 | async publishToAllUnit(dataId, group, content) { 219 | checkParameters(dataId, group); 220 | const units = await this.getAllUnits(); 221 | await units.map(unit => this.getClient({ unit }).publishSingle(dataId, group, content)); 222 | return true; 223 | } 224 | 225 | /** 226 | * 将配置从所有单元中删除 227 | * @param {String} dataId - id of the data 228 | * @param {String} group - group name of the data 229 | * @return {Boolean} success 230 | */ 231 | async removeToAllUnit(dataId, group) { 232 | checkParameters(dataId, group); 233 | const units = await this.getAllUnits(); 234 | await units.map(unit => this.getClient({ unit }).remove(dataId, group)); 235 | return true; 236 | } 237 | 238 | async publishAggr(dataId, group, datumId, content, options) { 239 | checkParameters(dataId, group, datumId); 240 | const client = this.getClient(options); 241 | return await client.publishAggr(dataId, group, datumId, content); 242 | } 243 | 244 | async removeAggr(dataId, group, datumId, options) { 245 | checkParameters(dataId, group, datumId); 246 | const client = this.getClient(options); 247 | return await client.removeAggr(dataId, group, datumId); 248 | } 249 | 250 | close() { 251 | this.serverMgr.close(); 252 | for (const client of this.clients.values()) { 253 | client.close(); 254 | } 255 | this.clients.clear(); 256 | } 257 | 258 | protected getClient(options: { unit?: string; group?; dataId? } = {}): IClientWorker { 259 | if (!options.unit) { 260 | options.unit = CURRENT_UNIT; 261 | } 262 | const { unit } = options; 263 | let client = this.clients.get(unit); 264 | if (!client) { 265 | client = this.getClientWorker(Object.assign({}, { 266 | configuration: this.configuration.attach({ unit }) 267 | })); 268 | client.on('error', err => { 269 | this.throwError(err); 270 | }); 271 | this.clients.set(unit, client); 272 | } 273 | return client; 274 | } 275 | 276 | /** 277 | * 默认异常处理 278 | * @param {Error} err - 异常 279 | * @return {void} 280 | * @private 281 | */ 282 | private throwError(err) { 283 | if (err) { 284 | setImmediate(() => this.emit('error', err)); 285 | } 286 | } 287 | 288 | /** 289 | * 供其他包覆盖 290 | * @param options 291 | */ 292 | protected getClientWorker(options): IClientWorker { 293 | return new ClientWorker(options); 294 | } 295 | 296 | protected getServerListManager(): IServerListManager { 297 | return new ServerListManager(this.options); 298 | } 299 | 300 | protected getSnapshot(): ISnapshot { 301 | return new Snapshot(this.options); 302 | } 303 | 304 | } 305 | -------------------------------------------------------------------------------- /packages/nacos-config/src/configuration.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { ClientOptionKeys, IConfiguration } from './interface'; 18 | 19 | export class Configuration implements IConfiguration { 20 | 21 | private innerConfig; 22 | 23 | constructor(initConfig) { 24 | this.innerConfig = initConfig || {}; 25 | } 26 | 27 | merge(config) { 28 | this.innerConfig = Object.assign(this.innerConfig, config); 29 | return this; 30 | } 31 | 32 | attach(config): Configuration { 33 | return new Configuration(Object.assign({}, this.innerConfig, config)); 34 | } 35 | 36 | get(configKey?: ClientOptionKeys) { 37 | return configKey ? this.innerConfig[configKey] : this.innerConfig; 38 | } 39 | 40 | has(configKey: ClientOptionKeys) { 41 | return !!this.innerConfig[configKey]; 42 | } 43 | 44 | set(configKey: ClientOptionKeys, target: any) { 45 | this.innerConfig[configKey] = target; 46 | return this; 47 | } 48 | 49 | modify(configKey: ClientOptionKeys, changeHandler: (target: any) => any) { 50 | this.innerConfig[configKey] = changeHandler.call(this, this.innerConfig[configKey]); 51 | return this; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /packages/nacos-config/src/const.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import * as urllib from 'urllib'; 18 | import * as path from 'path'; 19 | import * as osenv from 'osenv'; 20 | 21 | export const DEFAULT_GROUP = 'DEFAULT_GROUP'; 22 | 23 | export const LINE_SEPARATOR = String.fromCharCode(1); 24 | export const WORD_SEPARATOR = String.fromCharCode(2); 25 | 26 | export const VERSION = 'nodejs-diamond-client/' + require('../package.json').version; 27 | export const CURRENT_UNIT = 'CURRENT_UNIT'; 28 | 29 | export const HTTP_OK = 200; 30 | export const HTTP_NOT_FOUND = 404; 31 | export const HTTP_CONFLICT = 409; 32 | export const HTTP_UNAVAILABLE = 503; // 被限流 33 | 34 | export const DEFAULT_OPTIONS = { 35 | serverPort: 8848, 36 | requestTimeout: 5000, 37 | refreshInterval: 30000, 38 | cacheDir: path.join(osenv.home(), '.node-diamond-client-cache'), 39 | httpclient: urllib, 40 | contextPath: 'nacos', 41 | clusterName: 'serverlist', 42 | unit: CURRENT_UNIT, 43 | ssl: false, 44 | secretKey: '', 45 | defaultEncoding: 'utf8', 46 | }; 47 | -------------------------------------------------------------------------------- /packages/nacos-config/src/http_agent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { HTTP_CONFLICT, HTTP_NOT_FOUND, HTTP_OK, VERSION } from './const'; 18 | import { ClientOptionKeys, IConfiguration, IServerListManager } from './interface'; 19 | import * as urllib from 'urllib'; 20 | import * as crypto from 'crypto'; 21 | import { encodingParams, transformGBKToUTF8 } from './utils'; 22 | import * as dns from 'dns'; 23 | 24 | export class HttpAgent { 25 | 26 | options; 27 | protected loggerDomain = 'Nacos'; 28 | private debugPrefix = this.loggerDomain.toLowerCase(); 29 | private debug = require('debug')(`${this.debugPrefix}:${process.pid}:http_agent`); 30 | 31 | constructor(options) { 32 | this.options = options; 33 | } 34 | 35 | get configuration(): IConfiguration { 36 | return this.options.configuration; 37 | } 38 | 39 | get serverListMgr(): IServerListManager { 40 | return this.configuration.get(ClientOptionKeys.SERVER_MGR); 41 | } 42 | 43 | /** 44 | * HTTP 请求客户端 45 | */ 46 | get httpclient() { 47 | return this.configuration.get(ClientOptionKeys.HTTPCLIENT) || urllib; 48 | } 49 | 50 | get unit() { 51 | return this.configuration.get(ClientOptionKeys.UNIT); 52 | } 53 | 54 | get secretKey() { 55 | return this.configuration.get(ClientOptionKeys.SECRETKEY); 56 | } 57 | 58 | get requestTimeout() { 59 | return this.configuration.get(ClientOptionKeys.REQUEST_TIMEOUT); 60 | } 61 | 62 | get accessKey() { 63 | return this.configuration.get(ClientOptionKeys.ACCESSKEY); 64 | } 65 | 66 | get ssl() { 67 | return this.configuration.get(ClientOptionKeys.SSL); 68 | } 69 | 70 | get serverPort() { 71 | return this.configuration.get(ClientOptionKeys.SERVER_PORT); 72 | } 73 | 74 | get contextPath() { 75 | return this.configuration.get(ClientOptionKeys.CONTEXTPATH) || 'nacos'; 76 | } 77 | 78 | get clusterName() { 79 | return this.configuration.get(ClientOptionKeys.CLUSTER_NAME) || 'serverlist'; 80 | } 81 | 82 | get defaultEncoding() { 83 | return this.configuration.get(ClientOptionKeys.DEFAULT_ENCODING) || 'utf8'; 84 | } 85 | 86 | get identityKey() { 87 | return this.configuration.get(ClientOptionKeys.IDENTITY_KEY); 88 | } 89 | 90 | get identityValue() { 91 | return this.configuration.get(ClientOptionKeys.IDENTITY_VALUE); 92 | } 93 | 94 | get endpointQueryParams() { 95 | return this.configuration.get(ClientOptionKeys.ENDPOINT_QUERY_PARAMS) 96 | } 97 | 98 | get decodeRes() { 99 | return this.configuration.get(ClientOptionKeys.DECODE_RES); 100 | } 101 | 102 | 103 | /** 104 | * 请求 105 | * @param {String} path - 请求 path 106 | * @param {Object} [options] - 参数 107 | * @return {String} value 108 | */ 109 | async request(path, options: { 110 | encode?: boolean; 111 | method?: string; 112 | data?: any; 113 | timeout?: number; 114 | headers?: any; 115 | unit?: string; 116 | dataAsQueryString?: boolean; 117 | } = {}) { 118 | // 默认为当前单元 119 | const unit = options.unit || this.unit; 120 | const ts = String(Date.now()); 121 | const { encode = false, method = 'GET', data, timeout = this.requestTimeout, headers = {}, dataAsQueryString = false } = options; 122 | 123 | const endTime = Date.now() + timeout; 124 | let lastErr; 125 | 126 | if (this.options?.configuration?.innerConfig?.username && 127 | this.options?.configuration?.innerConfig?.password) { 128 | data.username = this.options.configuration.innerConfig.username; 129 | data.password = this.options.configuration.innerConfig.password; 130 | } 131 | let signStr = data.tenant; 132 | if (data.group && data.tenant) { 133 | signStr = data.tenant + '+' + data.group; 134 | } else if (data.group) { 135 | signStr = data.group; 136 | } 137 | 138 | const signature = crypto.createHmac('sha1', this.secretKey) 139 | .update(signStr + '+' + ts).digest() 140 | .toString('base64'); 141 | 142 | // 携带统一的头部信息 143 | Object.assign(headers, { 144 | 'Client-Version': VERSION, 145 | 'Content-Type': 'application/x-www-form-urlencoded; charset=GBK', 146 | 'Spas-AccessKey': this.accessKey, 147 | timeStamp: ts, 148 | exConfigInfo: 'true', 149 | 'Spas-Signature': signature, 150 | ...this.identityKey ? {[this.identityKey]: this.identityValue} : {} 151 | }); 152 | 153 | let requestData = data; 154 | if (encode) { 155 | requestData = encodingParams(data, this.defaultEncoding); 156 | } 157 | 158 | do { 159 | const currentServer = await this.serverListMgr.getCurrentServerAddr(unit); 160 | let url = this.getRequestUrl(currentServer) + `${path}`; 161 | this.debug('request unit: [%s] with url: %s', unit, url); 162 | 163 | try { 164 | const res = await this.httpclient.request(url, { 165 | rejectUnauthorized: false, 166 | httpsAgent: false, 167 | method, 168 | data: requestData, 169 | dataType: 'text', 170 | headers, 171 | timeout, 172 | secureProtocol: 'TLSv1_2_method', 173 | dataAsQueryString, 174 | }); 175 | this.debug('%s %s, got %s, body: %j', method, url, res.status, res.data); 176 | switch (res.status) { 177 | case HTTP_OK: 178 | if (this.decodeRes) { 179 | return this.decodeRes(res, method, this.defaultEncoding) 180 | } 181 | return this.decodeResData(res, method); 182 | case HTTP_NOT_FOUND: 183 | return null; 184 | case HTTP_CONFLICT: 185 | await this.serverListMgr.updateCurrentServer(unit); 186 | // JAVA 在外面业务类处理的这个逻辑,应该是需要重试的 187 | lastErr = new Error(`[Client Worker] ${this.loggerDomain} server config being modified concurrently, data: ${JSON.stringify(data)}`); 188 | lastErr.name = `${this.loggerDomain}ServerConflictError`; 189 | break; 190 | default: 191 | await this.serverListMgr.updateCurrentServer(unit); 192 | // JAVA 还有一个针对 HTTP_FORBIDDEN 的处理,不过合并到 default 应该也没问题 193 | lastErr = new Error(`${this.loggerDomain} Server Error Status: ${res.status}, url: ${url}, data: ${JSON.stringify(data)}`); 194 | lastErr.name = `${this.loggerDomain}ServerResponseError`; 195 | lastErr.body = res.data; 196 | break; 197 | } 198 | } catch (err) { 199 | if (err.code === dns.NOTFOUND) { 200 | throw err; 201 | } 202 | err.url = `${method} ${url}`; 203 | err.data = data; 204 | err.headers = headers; 205 | lastErr = err; 206 | } 207 | 208 | } while (Date.now() < endTime); 209 | 210 | throw lastErr; 211 | } 212 | 213 | // 获取请求 url 214 | getRequestUrl(currentServer) { 215 | let url; 216 | if (/:/.test(currentServer)) { 217 | url = `http://${currentServer}`; 218 | if (this.ssl) { 219 | url = `https://${currentServer}`; 220 | } 221 | } else { 222 | url = `http://${currentServer}:${this.serverPort}`; 223 | if (this.ssl) { 224 | url = `https://${currentServer}:${this.serverPort}`; 225 | } 226 | } 227 | return `${url}/${this.contextPath}`; 228 | } 229 | 230 | decodeResData(res, method = 'GET') { 231 | if (method === 'GET' && /charset=GBK/.test(res.headers[ 'content-type' ]) && this.defaultEncoding === 'utf8') { 232 | try { 233 | return transformGBKToUTF8(res.data); 234 | } catch (err) { 235 | console.error(`transform gbk data to utf8 error, msg=${err.messager}`); 236 | return res.data; 237 | } 238 | } else { 239 | return res.data; 240 | } 241 | } 242 | 243 | } 244 | -------------------------------------------------------------------------------- /packages/nacos-config/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import { DataClient } from './client'; 19 | import { checkParameters } from './utils'; 20 | import { BaseClient, ClientOptions } from './interface'; 21 | 22 | export * from './interface'; 23 | export { DataClient } from './client'; 24 | export { ClientWorker } from './client_worker'; 25 | export { ServerListManager } from './server_list_mgr'; 26 | export { Snapshot } from './snapshot'; 27 | export { HttpAgent } from './http_agent' 28 | 29 | const APIClientBase = require('cluster-client').APIClientBase; 30 | 31 | export class NacosConfigClient extends APIClientBase implements BaseClient { 32 | 33 | protected _client: BaseClient; 34 | 35 | /** 36 | * cluster-client wrapper client 37 | * set after constructor 38 | */ 39 | constructor(options: ClientOptions = {}) { 40 | super(options); 41 | } 42 | 43 | get DataClient() { 44 | return DataClient; 45 | } 46 | 47 | get clusterOptions() { 48 | const host = this.options.endpoint; 49 | return { 50 | name: `DiamondClient@${host}`, 51 | }; 52 | } 53 | 54 | /** 55 | * 订阅 56 | * @param {Object} reg 57 | * - {String} dataId - id of the data you want to subscribe 58 | * - {String} [group] - group name of the data 59 | * - {String} [unit] - which unit you want to connect, default is current unit 60 | * @param {Function} listener - listener 61 | * @return {DiamondClient} self 62 | */ 63 | subscribe(reg, listener) { 64 | const { dataId, group } = reg; 65 | checkParameters(dataId, group); 66 | this._client.subscribe(reg, listener); 67 | return this; 68 | } 69 | 70 | /** 71 | * 退订 72 | * @param {Object} reg 73 | * - {String} dataId - id of the data you want to subscribe 74 | * - {String} [group] - group name of the data 75 | * - {String} [unit] - which unit you want to connect, default is current unit 76 | * @param {Function} listener - listener 77 | * @return {DiamondClient} self 78 | */ 79 | unSubscribe(reg, listener) { 80 | const { dataId, group } = reg; 81 | checkParameters(dataId, group); 82 | this._client.unSubscribe(reg, listener); 83 | return this; 84 | } 85 | 86 | /** 87 | * 获取当前机器所在机房 88 | * @return {String} currentUnit 89 | */ 90 | async getCurrentUnit() { 91 | return await this._client.getCurrentUnit(); 92 | } 93 | 94 | /** 95 | * 获取所有单元信息 96 | * @return {Array} units 97 | */ 98 | async getAllUnits() { 99 | return await this._client.getAllUnits(); 100 | } 101 | 102 | /** 103 | * 查询租户下的所有的配置 104 | * @return {Array} config 105 | */ 106 | async getConfigs() { 107 | return await this._client.getConfigs(); 108 | } 109 | 110 | /** 111 | * 获取配置 112 | * @param {String} dataId - id of the data 113 | * @param {String} group - group name of the data 114 | * @param {Object} options 115 | * - {Stirng} unit - which unit you want to connect, default is current unit 116 | * @return {String} value 117 | */ 118 | async getConfig(dataId, group, options?) { 119 | checkParameters(dataId, group); 120 | return await this._client.getConfig(dataId, group, options); 121 | } 122 | 123 | /** 124 | * 发布配置 125 | * @param {String} dataId - id of the data 126 | * @param {String} group - group name of the data 127 | * @param {String} content - config value 128 | * @param {Object} options 129 | * - {Stirng} unit - which unit you want to connect, default is current unit 130 | * @return {Boolean} success 131 | */ 132 | async publishSingle(dataId, group, content, options?) { 133 | checkParameters(dataId, group); 134 | return await this._client.publishSingle(dataId, group, content, options); 135 | } 136 | 137 | /** 138 | * 删除配置 139 | * @param {String} dataId - id of the data 140 | * @param {String} group - group name of the data 141 | * @param {Object} options 142 | * - {Stirng} unit - which unit you want to connect, default is current unit 143 | * @return {Boolean} success 144 | */ 145 | async remove(dataId, group, options?) { 146 | checkParameters(dataId, group); 147 | return await this._client.remove(dataId, group, options); 148 | } 149 | 150 | /** 151 | * 批量获取配置 152 | * @param {Array} dataIds - data id array 153 | * @param {String} group - group name of the data 154 | * @param {Object} options 155 | * - {Stirng} unit - which unit you want to connect, default is current unit 156 | * @return {Array} result 157 | */ 158 | async batchGetConfig(dataIds, group, options?) { 159 | checkParameters(dataIds, group); 160 | return await this._client.batchGetConfig(dataIds, group, options); 161 | } 162 | 163 | /** 164 | * 批量查询 165 | * @param {Array} dataIds - data id array 166 | * @param {String} group - group name of the data 167 | * @param {Object} options 168 | * - {Stirng} unit - which unit you want to connect, default is current unit 169 | * @return {Object} result 170 | */ 171 | async batchQuery(dataIds, group, options?) { 172 | checkParameters(dataIds, group); 173 | return await this._client.batchQuery(dataIds, group, options); 174 | } 175 | 176 | /** 177 | * 将配置发布到所有单元 178 | * @param {String} dataId - id of the data 179 | * @param {String} group - group name of the data 180 | * @param {String} content - config value 181 | * @return {Boolean} success 182 | */ 183 | async publishToAllUnit(dataId, group, content?) { 184 | checkParameters(dataId, group); 185 | return await this._client.publishToAllUnit(dataId, group, content); 186 | } 187 | 188 | /** 189 | * 将配置从所有单元中删除 190 | * @param {String} dataId - id of the data 191 | * @param {String} group - group name of the data 192 | * @return {Boolean} success 193 | */ 194 | async removeToAllUnit(dataId, group) { 195 | checkParameters(dataId, group); 196 | return await this._client.removeToAllUnit(dataId, group); 197 | } 198 | 199 | async publishAggr(dataId, group, datumId, content, options) { 200 | checkParameters(dataId, group, datumId); 201 | return await this._client.publishAggr(dataId, group, datumId, content, options); 202 | } 203 | 204 | async removeAggr(dataId, group, datumId, options) { 205 | checkParameters(dataId, group, datumId); 206 | return await this._client.removeAggr(dataId, group, datumId, options); 207 | } 208 | 209 | close() { 210 | return this._client.close(); 211 | } 212 | 213 | static get DataClient() { 214 | return DataClient; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /packages/nacos-config/src/interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | // import { EventEmitter } from 'events'; 18 | 19 | interface ListenFunc { 20 | (): void; 21 | } 22 | 23 | /** 24 | * @description The subscribe listener 25 | */ 26 | interface Subscriber { 27 | (content: any): void; 28 | } 29 | 30 | 31 | /** 32 | * @description common options 33 | */ 34 | export interface CommonInputOptions { 35 | dataId: string; 36 | group?: string; 37 | unit?: string; 38 | } 39 | 40 | export interface UnitOptions { 41 | unit: string; 42 | } 43 | 44 | /** 45 | * @description Diamond client interface 46 | */ 47 | export interface BaseClient extends IClientWorker { 48 | /** 49 | * @description 获取当前机器所在机房 50 | * @returns {Promise} currentUnit 51 | */ 52 | getCurrentUnit(): Promise; 53 | 54 | /** 55 | * @description 获取所有单元信息 56 | */ 57 | getAllUnits(): Promise; 58 | 59 | /** 60 | * 将配置发布到所有单元 61 | * @param {String} dataId - id of the data 62 | * @param {String} group - group name of the data 63 | * @param {String} content - config value 64 | * @returns {Promise} true | false 65 | */ 66 | publishToAllUnit(dataId: string, group: string, content: string): Promise; 67 | 68 | /** 69 | * @description 将配置从所有单元中删除 70 | * @param {String} dataId - id of the data 71 | * @param {String} group - group name of the data 72 | * @returns {Promise} true | false 73 | */ 74 | removeToAllUnit(dataId: string, group: string): Promise; 75 | 76 | } 77 | 78 | /** 79 | * 每个 diamond 环境实例 80 | */ 81 | export interface IClientWorker { 82 | /** 83 | * @description 获取配置 84 | * @param {String} dataId - id of the data 85 | * @param {String} group - group name of the data 86 | * @param {Object} [options] 87 | * - {String} unit - which unit you want to connect, default is current unit 88 | * @returns {Promise} value 89 | */ 90 | getConfig(dataId: string, group: string, options?: UnitOptions): Promise; 91 | 92 | /** 93 | * @description 发布配置 94 | * @param {String} dataId - id of the data 95 | * @param {String} group - group name of the data 96 | * @param {String} content - config value 97 | * @param {Object} [options] 98 | * - {String} unit - which unit you want to connect, default is current unit 99 | * @returns {Promise} true | false 100 | */ 101 | publishSingle(dataId: string, group: string, content: string, options?: UnitOptions): Promise; 102 | 103 | /** 104 | * @description 删除配置 105 | * @param {String} dataId - id of the data 106 | * @param {String} group - group name of the data 107 | * @param {Object} [options] 108 | * - {String} unit - which unit you want to connect, default is current unit 109 | * @return {Promise} true | false 110 | */ 111 | remove(dataId: string, group: string, options?: UnitOptions): Promise; 112 | 113 | /** 114 | * @description 推送聚合数据 115 | * @param {String} dataId - id of the data 116 | * @param {String} group - group name of the data 117 | * @param {String} datumId - id of datum 118 | * @param {String} content 119 | * @param {Object} [options] 120 | * - {String} unit 121 | * @returns {Promise} true | false 122 | */ 123 | publishAggr(dataId: string, group: string, datumId: string, content: string, options?: UnitOptions): Promise; 124 | 125 | /** 126 | * @description 删除聚合数据 127 | * @param {String} dataId - id of the data 128 | * @param {String} group - group name of the data 129 | * @param {String} datumId - id of datum 130 | * @param {Object} [options] 131 | * - {String} unit 132 | * @returns {Promise} true | false 133 | */ 134 | removeAggr(dataId: string, group: string, datumId: string, options?: UnitOptions): Promise; 135 | 136 | /** 137 | * @description 批量获取配置 138 | * @param {Array} dataIds - data id array 139 | * @param {String} group - group name of the data 140 | * @param {Object} [options] 141 | * - {String} unit - which unit you want to connect, default is current unit 142 | * @returns {Promise} result 143 | */ 144 | batchGetConfig(dataIds: string[], group: string, options?: UnitOptions): Promise; 145 | 146 | /** 147 | * @description 批量查询 148 | * @param {Array} dataIds - data id array 149 | * @param {String} group - group name of the data 150 | * @param {Object} [options] 151 | * - {String} unit - which unit you want to connect, default is current unit 152 | * @returns {Promise} result 153 | */ 154 | batchQuery(dataIds: string[], group: string, options?: UnitOptions): Promise; 155 | 156 | /** 157 | * @description 订阅 158 | * @param {Object} reg 159 | * - {String} dataId - id of the data you want to subscribe 160 | * - {String} [group] - group name of the data 161 | * - {String} [unit] - which unit you want to connect, default is current unit 162 | * @param {Function} listener - listener(content: string) 163 | * @returns {InstanceType} DiamondClient 164 | */ 165 | subscribe(reg: CommonInputOptions, listener: Subscriber); 166 | 167 | /** 168 | * @description 取消订阅 169 | * @param {Object} reg 170 | * - {String} dataId - id of the data you want to unsubscribe 171 | * - {String} [group] - group name of the data 172 | * - {String} [unit] - which unit you want to connect, default is current unit 173 | * @param {Function} [listener] 174 | * - listener(content: string) 175 | * @returns {InstanceType} DiamondClient 176 | */ 177 | unSubscribe(reg: CommonInputOptions, listener?: ListenFunc); 178 | 179 | /** 180 | * @description 查询租户下的所有的配置 181 | */ 182 | getConfigs(): Promise>; 183 | 184 | /** 185 | * @description close connection 186 | */ 187 | close(): void; 188 | 189 | on?(evt: string, fn: (err: Error) => void): void; 190 | } 191 | 192 | /** 193 | * 服务列表管理器 194 | */ 195 | export interface IServerListManager { 196 | /** 197 | * 获取当前单元 198 | */ 199 | getCurrentUnit(): Promise; 200 | 201 | /** 202 | * 获取单元列表 203 | */ 204 | fetchUnitLists(): Promise>; 205 | 206 | /** 207 | * 更新当前服务器 208 | */ 209 | updateCurrentServer(unit?: string): Promise; 210 | 211 | /** 212 | * 获取一个服务器地址 213 | * @param unit 214 | */ 215 | getCurrentServerAddr(unit?: string): Promise; 216 | 217 | /** 218 | * @description close connection 219 | */ 220 | close(); 221 | 222 | // on(evt: string, fn: (err: Error) => void): void; 223 | } 224 | 225 | export interface ISnapshot { 226 | cacheDir; 227 | get(key: string): any; 228 | save(key: string, value: any); 229 | delete(key: string); 230 | batchSave(arr: Array); 231 | } 232 | 233 | export interface NacosHttpError extends Error { 234 | url?: string; 235 | params?: any; 236 | body?: any; 237 | unit?: string; 238 | dataId?: string; 239 | group?: string; 240 | } 241 | 242 | export interface SnapShotData { 243 | key?: string; 244 | value?: string; 245 | } 246 | 247 | export interface ClientOptions { 248 | endpoint?: string; // 寻址模式下的对端 host 249 | serverPort?: number; // 对端端口 250 | namespace?: string; // 阿里云的 namespace 251 | accessKey?: string; // 阿里云的 accessKey 252 | secretKey?: string; // 阿里云的 secretKey 253 | httpclient?: any; // http 请求客户端,默认为 urllib 254 | httpAgent?: any; // httpAgent 255 | appName?: string; // 应用名,可选 256 | ssl?: boolean; // 是否为 https 请求 257 | refreshInterval?: number; // 重新拉取地址列表的间隔时间 258 | contextPath?: string; // 请求的 contextPath 259 | clusterName?: string; // 请求的 path 260 | requestTimeout?: number; // 请求超时时间 261 | defaultEncoding?: string; // 请求编码 262 | serverAddr?: string; // 用于直连,包含端口 263 | unit?: string; // 内部单元化用 264 | nameServerAddr?: string; // 老的兼容参数,逐步废弃,同 endpoint 265 | username?: string; // 认证的用户名 266 | password?: string; // 认证的密码 267 | cacheDir?: string; // 缓存文件的路径 268 | identityKey?: string; // Identity Key 269 | identityValue?: string; // Identity Value 270 | endpointQueryParams?: string; // endPoint 查询参数 e.g: param_1=1¶m_2=2 271 | decodeRes?: (res: any, method?: string, encoding?: string) => any 272 | } 273 | 274 | export enum ClientOptionKeys { 275 | ENDPOINT = 'endpoint', 276 | SERVER_PORT = 'serverPort', 277 | NAMESPACE = 'namespace', 278 | ACCESSKEY = 'accessKey', 279 | SECRETKEY = 'secretKey', 280 | HTTPCLIENT = 'httpclient', 281 | APPNAME = 'appName', 282 | SSL = 'ssl', 283 | SNAPSHOT = 'snapshot', 284 | CACHE_DIR = 'cacheDir', 285 | NAMESERVERADDR = 'nameServerAddr', 286 | SERVERADDR = 'serverAddr', 287 | UNIT = 'unit', 288 | REFRESH_INTERVAL = 'refreshInterval', 289 | CONTEXTPATH = 'contextPath', 290 | CLUSTER_NAME = 'clusterName', 291 | REQUEST_TIMEOUT = 'requestTimeout', 292 | HTTP_AGENT = 'httpAgent', 293 | SERVER_MGR = 'serverMgr', 294 | DEFAULT_ENCODING = 'defaultEncoding', 295 | IDENTITY_KEY = 'identityKey', 296 | IDENTITY_VALUE = 'identityValue', 297 | DECODE_RES = 'decodeRes', 298 | ENDPOINT_QUERY_PARAMS = 'endpointQueryParams' 299 | } 300 | 301 | export interface IConfiguration { 302 | merge(config: any): IConfiguration; 303 | attach(config: any): IConfiguration; 304 | get(configKey?: ClientOptionKeys): any; 305 | has(configKey: ClientOptionKeys): boolean; 306 | set(configKey: ClientOptionKeys, target: any): IConfiguration; 307 | modify(configKey: ClientOptionKeys, changeHandler: (target: any) => any): IConfiguration; 308 | } 309 | 310 | export interface API_ROUTE { 311 | GET: string; 312 | BATCH_GET: string; 313 | BATCH_QUERY: string; 314 | PUBLISH: string; 315 | PUBLISH_ALL: string; 316 | REMOVE: string; 317 | REMOVE_ALL: string; 318 | LISTENER: string; 319 | } 320 | -------------------------------------------------------------------------------- /packages/nacos-config/src/server_list_mgr.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { Snapshot } from './snapshot'; 18 | import { ClientOptionKeys, NacosHttpError, IConfiguration, IServerListManager, ISnapshot } from './interface'; 19 | import { CURRENT_UNIT } from './const'; 20 | import * as path from 'path'; 21 | 22 | const Base = require('sdk-base'); 23 | const gather = require('co-gather'); 24 | const { random } = require('utility'); 25 | const { sleep } = require('mz-modules'); 26 | 27 | interface ServerCacheData { 28 | hosts: [ string ]; 29 | index: number; 30 | } 31 | 32 | export class ServerListManager extends Base implements IServerListManager { 33 | 34 | private isSync = false; 35 | private isClosed = false; 36 | private serverListCache: Map = new Map(); // unit => { hosts: [ addr1, addr2 ], index } 37 | private currentUnit = CURRENT_UNIT; 38 | private isDirectMode = false; 39 | protected loggerDomain = 'Nacos'; 40 | private debugPrefix = this.loggerDomain.toLowerCase(); 41 | protected debug = require('debug')(`${this.debugPrefix}:${process.pid}:mgr`); 42 | private currentServerAddrMap = new Map(); 43 | 44 | /** 45 | * 服务地址列表管理器 46 | * 47 | * @param {Object} options 48 | * - {HttpClient} httpclient - http 客户端 49 | * - {Snapshot} [snapshot] - 快照对象 50 | * - {String} nameServerAddr - 命名服务器地址 `hostname:port` 51 | * @constructor 52 | */ 53 | constructor(options) { 54 | super(options); 55 | this.formatOptions(); 56 | this.syncServers(); 57 | this.ready(true); 58 | } 59 | 60 | formatOptions() { 61 | if (this.configuration.has(ClientOptionKeys.ENDPOINT)) { 62 | this.configuration.modify(ClientOptionKeys.ENDPOINT, (endpoint) => { 63 | const temp = endpoint.split(':'); 64 | return temp[ 0 ] + ':' + (temp[ 1 ] || '8080'); 65 | }); 66 | } 67 | 68 | if (this.configuration.has(ClientOptionKeys.SERVERADDR)) { 69 | this.isDirectMode = true; 70 | this.serverListCache.set(CURRENT_UNIT, { 71 | hosts: [ this.configuration.get(ClientOptionKeys.SERVERADDR) ], 72 | index: 0 73 | }); 74 | } 75 | } 76 | 77 | get configuration(): IConfiguration { 78 | return this.options.configuration; 79 | } 80 | 81 | get snapshot(): ISnapshot { 82 | if(!this.configuration.has(ClientOptionKeys.SNAPSHOT)) { 83 | this.configuration.set(ClientOptionKeys.SNAPSHOT, new Snapshot(this.options)); 84 | } 85 | return this.configuration.get(ClientOptionKeys.SNAPSHOT); 86 | } 87 | 88 | get httpclient() { 89 | return this.configuration.get(ClientOptionKeys.HTTPCLIENT); 90 | } 91 | 92 | get nameServerAddr(): string { 93 | if (this.configuration.has(ClientOptionKeys.ENDPOINT)) { 94 | return this.configuration.get(ClientOptionKeys.ENDPOINT); 95 | } 96 | return this.configuration.get(ClientOptionKeys.NAMESERVERADDR); 97 | } 98 | 99 | get refreshInterval(): number { 100 | return this.configuration.get(ClientOptionKeys.REFRESH_INTERVAL); 101 | } 102 | 103 | get contextPath(): string { 104 | return this.configuration.get(ClientOptionKeys.CONTEXTPATH) || 'nacos'; 105 | } 106 | 107 | get clusterName(): string { 108 | return this.configuration.get(ClientOptionKeys.CLUSTER_NAME) || 'serverlist'; 109 | } 110 | 111 | get endpointQueryParams() { 112 | return this.configuration.get(ClientOptionKeys.ENDPOINT_QUERY_PARAMS) 113 | } 114 | 115 | get requestTimeout(): number { 116 | return this.configuration.get(ClientOptionKeys.REQUEST_TIMEOUT); 117 | } 118 | 119 | /** 120 | * 关闭地址列表服务 121 | */ 122 | close() { 123 | this.isClosed = true; 124 | } 125 | 126 | private async request(url, options) { 127 | const res = await this.httpclient.request(url, options); 128 | const { status, data } = res; 129 | if (status !== 200) { 130 | const err: NacosHttpError = new Error(`[${this.loggerDomain}#ServerListManager] request url: ${url} failed with statusCode: ${status}`); 131 | err.name = `${this.loggerDomain}ServerResponseError`; 132 | err.url = url; 133 | err.params = options; 134 | err.body = res.data; 135 | throw err; 136 | } 137 | return data; 138 | } 139 | 140 | /* 141 | * 获取当前机器所在单元 142 | */ 143 | async getCurrentUnit() { 144 | return this.currentUnit; 145 | } 146 | 147 | /** 148 | * 同步服务器列表 149 | * @return {void} 150 | */ 151 | syncServers() { 152 | if (this.isSync || this.isDirectMode) { 153 | return; 154 | } 155 | (async () => { 156 | try { 157 | this.isSync = true; 158 | while (!this.isClosed) { 159 | await sleep(this.refreshInterval); 160 | const units = Array.from(this.serverListCache.keys()); 161 | this.debug('syncServers for units: %j', units); 162 | const results = await gather(units.map(unit => this.fetchServerList(unit))); 163 | for (let i = 0, len = results.length; i < len; i++) { 164 | if (results[ i ].isError) { 165 | const err = new Error(results[ i ].error); 166 | err.name = `${this.loggerDomain}UpdateServersError`; 167 | this.emit('error', err); 168 | } 169 | } 170 | } 171 | this.isSync = false; 172 | } catch (err) { 173 | this.emit('error', err); 174 | } 175 | })(); 176 | } 177 | 178 | // 获取某个单元的 server 列表 179 | async fetchServerList(unit = CURRENT_UNIT) { 180 | const key = this.formatKey(unit); 181 | const url = this.getRequestUrl(unit); 182 | let hosts; 183 | try { 184 | let data = await this.request(url, { 185 | timeout: this.requestTimeout, 186 | dataType: 'text', 187 | }); 188 | data = data || ''; 189 | hosts = data.split('\n').map(host => host.trim()).filter(host => !!host); 190 | const length = hosts.length; 191 | this.debug('got %d hosts, the serverlist is: %j', length, hosts); 192 | if (!length) { 193 | const err: NacosHttpError = new Error(`[${this.loggerDomain}#ServerListManager] ${this.loggerDomain} return empty hosts`); 194 | err.name = `${this.loggerDomain}ServerHostEmptyError`; 195 | err.unit = unit; 196 | throw err; 197 | } 198 | await this.snapshot.save(key, JSON.stringify(hosts)); 199 | } catch (err) { 200 | this.emit('error', err); 201 | const data = await this.snapshot.get(key); 202 | if (data) { 203 | try { 204 | hosts = JSON.parse(data); 205 | } catch (err) { 206 | await this.snapshot.delete(key); 207 | err.name = 'ServerListSnapShotJSONParseError'; 208 | err.unit = unit; 209 | err.data = data; 210 | this.emit('error', err); 211 | } 212 | } 213 | } 214 | if (!hosts || !hosts.length) { 215 | // 这里主要是为了让后面定时同步可以执行 216 | this.serverListCache.set(unit, null); 217 | return null; 218 | } 219 | const serverData = { 220 | hosts, 221 | index: random(hosts.length), 222 | }; 223 | this.serverListCache.set(unit, serverData); 224 | return serverData; 225 | } 226 | 227 | formatKey(unit) { 228 | return path.join('server_list', unit); 229 | } 230 | 231 | // 获取请求 url 232 | protected getRequestUrl(unit) { 233 | const endpointQueryParams = !!this.endpointQueryParams ? `?${this.endpointQueryParams}` : ''; 234 | return `http://${this.nameServerAddr}/${this.contextPath}/${this.clusterName}${endpointQueryParams}`; 235 | } 236 | 237 | /** 238 | * 获取单元列表 239 | * @return {Array} units 240 | */ 241 | async fetchUnitLists() { 242 | return [ this.currentUnit ]; 243 | } 244 | 245 | async updateCurrentServer(unit = CURRENT_UNIT) { 246 | if (!this.serverListCache.has(unit)) { 247 | await this.fetchServerList(unit); 248 | } 249 | let serverData = this.serverListCache.get(unit); 250 | if (serverData) { 251 | let currentHostIndex = serverData.index; 252 | if (currentHostIndex >= serverData.hosts.length) { 253 | // 超出序号,重头开始循环 254 | currentHostIndex = 0; 255 | } 256 | const currentServer = serverData.hosts[ currentHostIndex++ ]; 257 | this.currentServerAddrMap.set(unit, currentServer); 258 | } 259 | } 260 | 261 | /** 262 | * 获取某个单元的地址 263 | * @param {String} unit 单元名,默认为当前单元 264 | * @return {String} address 265 | */ 266 | async getCurrentServerAddr(unit = CURRENT_UNIT) { 267 | if (!this.currentServerAddrMap.has(unit)) { 268 | await this.updateCurrentServer(unit); 269 | } 270 | return this.currentServerAddrMap.get(unit); 271 | } 272 | 273 | // for test 274 | hasServerInCache(serverName) { 275 | return this.serverListCache.has(serverName); 276 | } 277 | 278 | // for test 279 | getServerInCache(unit = CURRENT_UNIT) { 280 | return this.serverListCache.get(unit); 281 | } 282 | 283 | // for test 284 | clearServerCache() { 285 | this.serverListCache.clear(); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /packages/nacos-config/src/snapshot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { ClientOptionKeys, IConfiguration, ISnapshot, SnapShotData } from './interface'; 18 | import * as path from 'path'; 19 | import * as assert from 'assert'; 20 | 21 | const Base = require('sdk-base'); 22 | const is = require('is-type-of'); 23 | const { mkdirp, rimraf } = require('mz-modules'); 24 | const debug = require('debug')('diamond-client:snapshot'); 25 | const fs = require('mz/fs'); 26 | 27 | export class Snapshot extends Base implements ISnapshot { 28 | 29 | private uuid = Math.random(); 30 | 31 | constructor(options) { 32 | super(options); 33 | this.ready(true); 34 | debug(this.uuid); 35 | } 36 | 37 | get cacheDir() { 38 | return this.configuration.get(ClientOptionKeys.CACHE_DIR); 39 | } 40 | 41 | get configuration(): IConfiguration { 42 | return this.options.configuration; 43 | } 44 | 45 | async get(key) { 46 | const filepath = this.getSnapshotFile(key); 47 | try { 48 | const exists = await fs.exists(filepath); 49 | if (exists) { 50 | return await fs.readFile(filepath, 'utf8'); 51 | } 52 | } catch (err) { 53 | err.name = 'SnapshotReadError'; 54 | this.emit('error', err); 55 | } 56 | return null; 57 | } 58 | 59 | async save(key, value) { 60 | const filepath = this.getSnapshotFile(key); 61 | const dir = path.dirname(filepath); 62 | value = value || ''; 63 | try { 64 | await mkdirp(dir); 65 | await fs.writeFile(filepath, value); 66 | } catch (err) { 67 | err.name = 'SnapshotWriteError'; 68 | err.key = key; 69 | err.value = value; 70 | this.emit('error', err); 71 | } 72 | } 73 | 74 | async delete(key) { 75 | const filepath = this.getSnapshotFile(key); 76 | try { 77 | await rimraf(filepath); 78 | } catch (err) { 79 | err.name = 'SnapshotDeleteError'; 80 | err.key = key; 81 | this.emit('error', err); 82 | } 83 | } 84 | 85 | async batchSave(arr: Array) { 86 | assert(is.array(arr), '[diamond#Snapshot] batchSave(arr) arr should be an Array.'); 87 | await Promise.all(arr.map(({ key, value }) => this.save(key, value))); 88 | } 89 | 90 | private getSnapshotFile(key) { 91 | return path.join(this.cacheDir, 'snapshot', key); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /packages/nacos-config/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | const crypto = require('crypto'); 18 | const is = require('is-type-of'); 19 | const assert = require('assert'); 20 | const qs = require('querystring'); 21 | const iconv = require('iconv-lite'); 22 | const urlencode = require('urlencode'); 23 | 24 | const sep = '&'; 25 | const eq = '='; 26 | const REG_VALID_CHAR = /^[a-z0-9A-Z_\.\-\:]+$/; 27 | 28 | /** 29 | * 获取字符串的 md5 值 30 | * @param {String} val - 字符串 31 | * @return {String} md5 32 | */ 33 | export function getMD5String(val, encodingFormat = 'utf8') { 34 | if (is.nullOrUndefined(val)) { 35 | return ''; 36 | } 37 | const md5 = crypto.createHash('md5'); 38 | // 注意:这里的编码 39 | md5.update(iconv.encode(val, encodingFormat)); 40 | return md5.digest('hex'); 41 | } 42 | 43 | export function encodingParams(data, encodingFormat = 'utf8') { 44 | return qs.stringify(data, sep, eq, { 45 | encodeURIComponent(str) { 46 | // nacos 默认 utf8,其他是 gbk 47 | return urlencode.encode(str, encodingFormat); 48 | }, 49 | }); 50 | } 51 | 52 | /** 53 | * 是否是合法字符 54 | * @param {String} val - 字符串 55 | * @return {Boolean} valid or not? 56 | */ 57 | export function isValid(val) { 58 | return val && REG_VALID_CHAR.test(val); 59 | } 60 | 61 | // Helper 62 | // -------------------- 63 | export function checkParameters(dataIds, group, datumId?) { 64 | if (Array.isArray(dataIds)) { 65 | const invalidDataIds = dataIds.filter(function(dataId) { 66 | return !exports.isValid(dataId); 67 | }); 68 | assert(invalidDataIds.length === 0, `[dataId] only allow digital, letter and symbols in [ "_", "-", ".", ":" ], but got ${invalidDataIds}`); 69 | } else { 70 | assert(dataIds && exports.isValid(dataIds), `[dataId] only allow digital, letter and symbols in [ "_", "-", ".", ":" ], but got ${dataIds}`); 71 | } 72 | assert(group && exports.isValid(group), `[group] only allow digital, letter and symbols in [ "_", "-", ".", ":" ], but got ${group}`); 73 | if (datumId) { 74 | assert(exports.isValid(datumId), `[datumId] only allow digital, letter and symbols in [ "_", "-", ".", ":" ], but got ${datumId}`); 75 | } 76 | } 77 | 78 | 79 | export function transformGBKToUTF8(text) { 80 | return iconv.decode(iconv.encode(text, 'gbk'), 'utf8'); 81 | } 82 | -------------------------------------------------------------------------------- /packages/nacos-config/test/client.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { DataClient } from '../src/client'; 18 | import { HttpAgent } from '../src/http_agent' 19 | 20 | const mm = require('mm'); 21 | const assert = require('assert'); 22 | const pedding = require('pedding'); 23 | const httpclient = require('urllib'); 24 | const { sleep } = require('mz-modules'); 25 | 26 | describe('test/client.test.ts', () => { 27 | let client; 28 | before(async () => { 29 | client = new DataClient({ 30 | appName: 'test', 31 | endpoint: 'acm.aliyun.com', 32 | namespace: '81597370-5076-4216-9df5-538a2b55bac3', 33 | accessKey: '4c796a4dcd0d4f5895d4ba83a296b489', 34 | secretKey: 'UjLemP8inirhjMg1NZyY0faOk1E=', 35 | httpclient, 36 | ssl: false 37 | }); 38 | }); 39 | after(() => { 40 | client.close(); 41 | }); 42 | afterEach(mm.restore); 43 | 44 | it('should have proper properties', () => { 45 | assert(client.httpclient === httpclient); 46 | assert(client.appName); 47 | assert(client.snapshot); 48 | assert(client.serverMgr); 49 | }); 50 | 51 | it('should listen error', done => { 52 | done = pedding(done, 2); 53 | client.on('error', err => { 54 | assert(err.message === 'mock error'); 55 | done(); 56 | }); 57 | client.snapshot.emit('error', new Error('mock error')); 58 | client.serverMgr.emit('error', new Error('mock error')); 59 | }); 60 | 61 | it('should remove config ok', async () => { 62 | await client.publishSingle('test-data-id', 'test-group', 'hello'); 63 | await sleep(1000); 64 | let data = await client.getConfig('test-data-id', 'test-group'); 65 | assert(data === 'hello'); 66 | await client.remove('test-data-id', 'test-group'); 67 | await sleep(1000); 68 | data = await client.getConfig('test-data-id', 'test-group'); 69 | assert(!data); 70 | }); 71 | 72 | xit('should batchGetConfig ok', async () => { 73 | const ret = await client.batchGetConfig([ 'com.taobao.hsf.redis' ], 'HSF'); 74 | assert(Array.isArray(ret) && ret.length > 0); 75 | }); 76 | 77 | xit('should batchQuery ok', async () => { 78 | const ret = await client.batchQuery([ 'com.taobao.hsf.redis' ], 'HSF'); 79 | assert(Array.isArray(ret) && ret.length > 0); 80 | }); 81 | 82 | xit('should publishAggr & removeAggr ok', async () => { 83 | let isSuccess = await client.publishAggr('NS_DIAMOND_SUBSCRIPTION_TOPIC_chenwztest', 'DEFAULT_GROUP', 'somebody-pub-test', 'xx xx'); 84 | assert(isSuccess === true); 85 | await sleep(1000); 86 | let content = await client.getConfig('NS_DIAMOND_SUBSCRIPTION_TOPIC_chenwztest', 'DEFAULT_GROUP'); 87 | assert(content === 'xx xx'); 88 | 89 | isSuccess = await client.removeAggr('NS_DIAMOND_SUBSCRIPTION_TOPIC_chenwztest', 'DEFAULT_GROUP', 'somebody-pub-test2'); 90 | assert(isSuccess === true); 91 | 92 | isSuccess = await client.publishAggr('NS_DIAMOND_SUBSCRIPTION_TOPIC_unicode', 'DEFAULT_GROUP', 'somebody-pub-test', '宗羽'); 93 | assert(isSuccess === true); 94 | content = await client.getConfig('NS_DIAMOND_SUBSCRIPTION_TOPIC_unicode', 'DEFAULT_GROUP'); 95 | assert(content === '宗羽'); 96 | }); 97 | 98 | describe('invalid parameters', () => { 99 | it('should throw when subscribe with invalid dataId or group', () => { 100 | try { 101 | client.subscribe({ 102 | dataId: 'xxx xxx', 103 | }, () => { 104 | }); 105 | } catch (err) { 106 | assert(err.message.indexOf('only allow digital, letter and symbols in [ "_", "-", ".", ":" ]') > -1); 107 | } 108 | try { 109 | client.subscribe({ 110 | dataId: 'dataId', 111 | group: 'DEFAULT GROUP', 112 | }, function () { 113 | }); 114 | assert(false, 'should not run this!'); 115 | } catch (err) { 116 | assert(err.message.indexOf('only allow digital, letter and symbols in [ "_", "-", ".", ":" ]') > -1); 117 | } 118 | }); 119 | }); 120 | 121 | it('support custom httpAgent', () => { 122 | class CustomHttpAgent {}; 123 | assert(client.httpAgent instanceof HttpAgent); 124 | client = new DataClient({ 125 | appName: 'test', 126 | endpoint: 'acm.aliyun.com', 127 | namespace: '81597370-5076-4216-9df5-538a2b55bac3', 128 | accessKey: '4c796a4dcd0d4f5895d4ba83a296b489', 129 | secretKey: 'UjLemP8inirhjMg1NZyY0faOk1E=', 130 | httpclient, 131 | httpAgent: CustomHttpAgent, 132 | ssl: false 133 | }); 134 | assert(client.httpAgent instanceof CustomHttpAgent); 135 | }); 136 | 137 | }); 138 | -------------------------------------------------------------------------------- /packages/nacos-config/test/index.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | const mm = require('mm'); 18 | const assert = require('assert'); 19 | const httpclient = require('urllib'); 20 | const {sleep} = require('mz-modules'); 21 | const co = require('co'); 22 | 23 | import {NacosConfigClient} from '../src'; 24 | 25 | describe('test/index.test.ts', () => { 26 | let client; 27 | before(async () => { 28 | client = new NacosConfigClient({ 29 | endpoint: 'acm.aliyun.com', 30 | namespace: '81597370-5076-4216-9df5-538a2b55bac3', 31 | accessKey: '4c796a4dcd0d4f5895d4ba83a296b489', 32 | secretKey: 'UjLemP8inirhjMg1NZyY0faOk1E=', 33 | httpclient, 34 | ssl: false 35 | }); 36 | await client.ready(); 37 | }); 38 | after(() => { 39 | client.close(); 40 | }); 41 | afterEach(mm.restore); 42 | 43 | it('should publishSingle and getConfig ok', async () => { 44 | const dataId = 'acm.test'; 45 | const group = 'DEFAULT_GROUP'; 46 | const str = `淘杰tj_test_${Math.random()}_${Date.now()}`; 47 | await client.publishSingle(dataId, group, str); 48 | await sleep(1000); 49 | const content = await client.getConfig(dataId, group); 50 | assert(content === str); 51 | }); 52 | 53 | it('should subscribe ok', function (done) { 54 | const dataId = 'subscribe.test'; 55 | const group = 'DEFAULT_GROUP'; 56 | const str = `subscribe.test_${Math.random().toString(32)}_${Date.now()}`; 57 | 58 | client.subscribe({ 59 | dataId, 60 | group, 61 | }, content => { 62 | if (content !== str) { 63 | co(async () => { 64 | await client.publishSingle(dataId, group, str); 65 | }); 66 | return; 67 | } 68 | assert(content === str); 69 | done(); 70 | }); 71 | }); 72 | 73 | it('should remove ok', async () => { 74 | const dataId = 'remove.test'; 75 | const group = 'DEFAULT_GROUP'; 76 | const str = `subscribe.test_${Math.random().toString(32)}_${Date.now()}`; 77 | await client.publishSingle(dataId, group, str); 78 | await sleep(1000); 79 | const content = await client.getConfig(dataId, group); 80 | assert(content === str); 81 | await client.remove(dataId, group); 82 | await sleep(1000); 83 | const temp = await client.getConfig(dataId, group); 84 | assert(temp == null); 85 | }); 86 | 87 | xit('should batchQuery ok', async () => { 88 | const dataIds = ['acm.test', 'json.data']; 89 | const group = 'DEFAULT_GROUP'; 90 | const content = await client.batchQuery(dataIds, group); 91 | assert(content[0].dataId === dataIds[0]); 92 | assert(content[1].dataId === dataIds[1]); 93 | }); 94 | 95 | xit('should getAllConfigInfo ok', async () => { 96 | const configs = await client.getAllConfigInfo(); 97 | assert(configs.length); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /packages/nacos-config/test/server_list_mgr.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { ServerListManager } from '../src'; 18 | import { createDefaultConfiguration } from './utils'; 19 | 20 | const fs = require('mz/fs'); 21 | const path = require('path'); 22 | const mm = require('mm'); 23 | const assert = require('assert'); 24 | const pedding = require('pedding'); 25 | const httpclient = require('urllib'); 26 | const { rimraf, sleep } = require('mz-modules'); 27 | 28 | describe('test/server_list_mgr.test.ts', () => { 29 | const cacheDir = path.join(__dirname, '.cache'); 30 | 31 | describe('find server addresss list by server', () => { 32 | let serverManager: ServerListManager; 33 | const defaultOptions = { 34 | appName: 'test', 35 | endpoint: 'acm.aliyun.com', 36 | namespace: '81597370-5076-4216-9df5-538a2b55bac3', 37 | accessKey: '4c796a4dcd0d4f5895d4ba83a296b489', 38 | secretKey: 'UjLemP8inirhjMg1NZyY0faOk1E=', 39 | httpclient, 40 | ssl: false, 41 | cacheDir 42 | }; 43 | 44 | const configuration = createDefaultConfiguration(defaultOptions); 45 | 46 | before(async () => { 47 | serverManager = new ServerListManager({ configuration }); 48 | await serverManager.ready(); 49 | }); 50 | 51 | afterEach(mm.restore); 52 | 53 | after(async () => { 54 | serverManager.close(); 55 | await rimraf(cacheDir); 56 | await sleep(4000); 57 | }); 58 | 59 | 60 | it('should ready', done => { 61 | done = pedding(3, done); 62 | serverManager.ready(done); 63 | serverManager.ready(done); 64 | serverManager.ready(() => serverManager.ready(done)); 65 | }); 66 | 67 | describe('getCurrentServerAddr()', () => { 68 | it('should got nacos server list data', async () => { 69 | let host = await serverManager.getCurrentServerAddr(); 70 | assert(host && /^\d+\.\d+\.\d+\.\d+$/.test(host)); 71 | host = await serverManager.getCurrentServerAddr(); 72 | assert(host && /^\d+\.\d+\.\d+\.\d+$/.test(host)); 73 | }); 74 | 75 | it('should traverse all diamond server list data', async () => { 76 | const start = await serverManager.getCurrentServerAddr(); 77 | let host; 78 | do { 79 | host = await serverManager.getCurrentServerAddr(); 80 | } while (start !== host); 81 | }); 82 | 83 | it('should return null when server list return empty', async () => { 84 | mm.data(serverManager, 'fetchServerList', null); 85 | mm(serverManager, 'serverListCache', new Map()); 86 | mm(serverManager, 'currentServerAddrMap', new Map()); 87 | const result = await serverManager.getCurrentServerAddr(); 88 | assert(result == null); 89 | }); 90 | }); 91 | 92 | describe('fetchServerList(unit)', () => { 93 | it('should use CURRENT_UNIT if unit not providered', async () => { 94 | const data = await serverManager.fetchServerList(); 95 | assert(data && Array.isArray(data.hosts)); 96 | assert(data.index < data.hosts.length && data.index >= 0); 97 | }); 98 | 99 | it('should empty server list', async () => { 100 | mm.http.request(/nacos/, Buffer.alloc(0), {}); 101 | mm.data(serverManager.snapshot, 'get', null); 102 | let data = await serverManager.fetchServerList(); 103 | assert(!data); 104 | 105 | mm.data(serverManager.httpclient, 'request', { status: 500 }); 106 | data = await serverManager.fetchServerList(); 107 | assert(!data); 108 | 109 | mm.restore(); 110 | mm.data(serverManager, 'request', null); 111 | // mm.data(serverManager.snapshot, 'get', '{'); 112 | const key = serverManager.formatKey('CURRENT_UNIT'); 113 | await serverManager.snapshot.save(key, '{'); 114 | let cache = await serverManager.snapshot.get(key); 115 | assert(cache === '{'); 116 | data = await serverManager.fetchServerList(); 117 | cache = await serverManager.snapshot.get(key); 118 | assert(!cache); 119 | assert(!data); 120 | }); 121 | 122 | it('should get server list of certain unit', async () => { 123 | const currentUnit = await serverManager.getCurrentUnit(); 124 | const data = await serverManager.fetchServerList(currentUnit); 125 | assert(data && Array.isArray(data.hosts)); 126 | assert(data.index < data.hosts.length && data.index >= 0); 127 | }); 128 | }); 129 | 130 | describe('snapshot', () => { 131 | it('should save diamond server list ok', async () => { 132 | const host = await serverManager.getCurrentServerAddr(); 133 | assert(host && /^\d+\.\d+\.\d+\.\d+$/.test(host)); 134 | const isExist = await fs.exists(path.join(cacheDir, 'snapshot', 'server_list', 'CURRENT_UNIT')); 135 | assert(isExist); 136 | }); 137 | 138 | it('should get diamond server list ok when request error', async () => { 139 | let host = await serverManager.getCurrentServerAddr(); 140 | assert(host && /^\d+\.\d+\.\d+\.\d+$/.test(host)); 141 | mm(serverManager, 'serverListCache', new Map()); 142 | mm.data(serverManager, 'request', null); 143 | host = await serverManager.getCurrentServerAddr(); 144 | assert(host && /^\d+\.\d+\.\d+\.\d+$/.test(host)); 145 | }); 146 | }); 147 | 148 | describe('_syncServers', () => { 149 | it('should auto _syncServers', async () => { 150 | // assert(serverManager.isSync); 151 | const host = await serverManager.getCurrentServerAddr(); 152 | assert(host && /^\d+\.\d+\.\d+\.\d+$/.test(host)); 153 | assert(serverManager.hasServerInCache('CURRENT_UNIT')); 154 | serverManager.syncServers(); 155 | serverManager.syncServers(); 156 | }); 157 | 158 | it('should _syncServers periodically', async () => { 159 | mm.error(serverManager, 'fetchServerList', 'mock error'); 160 | let error; 161 | try { 162 | await serverManager.await('error'); 163 | } catch (err) { 164 | error = err; 165 | } 166 | assert(error && error.name === 'NacosUpdateServersError'); 167 | assert(error.message.includes('mock error')); 168 | }); 169 | 170 | it('should update server list', async () => { 171 | serverManager.clearServerCache(); 172 | mm.data(serverManager, 'request', null); 173 | mm.data(serverManager.snapshot, 'get', null); 174 | mm(serverManager, 'currentServerAddrMap', new Map()); 175 | 176 | const host = await serverManager.getCurrentServerAddr('CURRENT_UNIT'); 177 | assert(!host); 178 | mm.restore(); 179 | await sleep(4000); 180 | 181 | assert(serverManager.hasServerInCache('CURRENT_UNIT')); 182 | }); 183 | }); 184 | }); 185 | 186 | describe('use ip and direct mode', () => { 187 | let serverManager: ServerListManager; 188 | 189 | before(async () => { 190 | const configuration = createDefaultConfiguration({ 191 | httpclient, 192 | serverAddr: '106.14.43.196:8848', 193 | cacheDir, 194 | }); 195 | serverManager = new ServerListManager({ configuration }); 196 | await serverManager.ready(); 197 | }); 198 | 199 | afterEach(mm.restore); 200 | 201 | after(async () => { 202 | serverManager.close(); 203 | await rimraf(cacheDir); 204 | await sleep(4000); 205 | }); 206 | 207 | it('should get server from direct mode', async () => { 208 | const addr = await serverManager.getServerInCache(); 209 | assert(addr.hosts.length === 1); 210 | assert(addr.index === 0); 211 | }); 212 | 213 | it('should get one addr from direct mode', async () => { 214 | const addr = await serverManager.getCurrentServerAddr(); 215 | assert(addr && /^\d+\.\d+\.\d+\.\d+:\d+$/.test(addr)); 216 | }); 217 | }); 218 | }); 219 | -------------------------------------------------------------------------------- /packages/nacos-config/test/snapshot.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { Snapshot } from '../src/snapshot'; 18 | import { createDefaultConfiguration } from './utils'; 19 | 20 | const fs = require('mz/fs'); 21 | const path = require('path'); 22 | const mm = require('mm'); 23 | const assert = require('assert'); 24 | const { rimraf } = require('mz-modules'); 25 | 26 | const cacheDir = path.join(__dirname, '.cache'); 27 | 28 | describe('test/snapshot.test.ts', () => { 29 | let snapshot; 30 | before(async () => { 31 | const configuration = createDefaultConfiguration({ 32 | cacheDir, 33 | }); 34 | snapshot = new Snapshot({ configuration }); 35 | await snapshot.ready(); 36 | }); 37 | afterEach(mm.restore); 38 | after(async () => { await rimraf(cacheDir); }); 39 | 40 | describe('save()', () => { 41 | it('should get/save snapshot ok', async () => { 42 | const key = 'foo'; 43 | let content = await snapshot.get(key); 44 | assert(!content); 45 | await snapshot.save(key, 'bar'); 46 | content = await snapshot.get(key); 47 | assert(content === 'bar'); 48 | 49 | await snapshot.save(key, null); 50 | content = await snapshot.get(key); 51 | assert(content === ''); 52 | }); 53 | 54 | it('should delete snapshot ok', async () => { 55 | const key = 'foo'; 56 | await snapshot.save(key, 'bar'); 57 | let isExist = await fs.exists(snapshot.getSnapshotFile(key)); 58 | assert(isExist); 59 | await snapshot.delete(key); 60 | isExist = await fs.exists(snapshot.getSnapshotFile(key)); 61 | assert(!isExist); 62 | }); 63 | 64 | it('should throw error if batchSave param not Array', async () => { 65 | let error; 66 | try { 67 | await snapshot.batchSave({ key: 'key', value: 'value' }); 68 | } catch (err) { 69 | error = err; 70 | } 71 | assert(error); 72 | assert(error.message === '[diamond#Snapshot] batchSave(arr) arr should be an Array.'); 73 | }); 74 | 75 | it('should emit error when save error', async () => { 76 | mm.error(fs, 'writeFile', 'mock error'); 77 | const key = 'key'; 78 | let error; 79 | try { 80 | await Promise.all([ 81 | snapshot.await('error'), 82 | snapshot.save(key, 'content'), 83 | ]); 84 | } catch (err) { 85 | error = err; 86 | } 87 | assert(error && error.name === 'SnapshotWriteError'); 88 | assert(error.message === 'mock error'); 89 | const isExist = await fs.exists(snapshot.getSnapshotFile(key)); 90 | assert(!isExist); 91 | }); 92 | 93 | it('should emit error when getSnapshot error', async () => { 94 | mm.error(fs, 'readFile', 'mock error'); 95 | const key = 'key'; 96 | await snapshot.save(key, 'content'); 97 | let error; 98 | try { 99 | await Promise.all([ 100 | snapshot.await('error'), 101 | snapshot.get(key), 102 | ]); 103 | } catch (err) { 104 | error = err; 105 | } 106 | assert(error && error.name === 'SnapshotReadError'); 107 | assert(error.message === 'mock error'); 108 | const isExist = await fs.exists(snapshot.getSnapshotFile(key)); 109 | assert(isExist); 110 | }); 111 | 112 | it('should delete key ok', async () => { 113 | const key = path.join('group', 'id'); 114 | await snapshot.save(key, 'value'); 115 | const value = await snapshot.get(key); 116 | assert(value === 'value'); 117 | let isExist = await fs.exists(snapshot.getSnapshotFile(key)); 118 | assert(isExist); 119 | await snapshot.delete(key); 120 | isExist = await fs.exists(snapshot.getSnapshotFile(key)); 121 | assert(!isExist); 122 | await snapshot.delete(key); 123 | }); 124 | 125 | it('should delete with error', async () => { 126 | mm.error(require('fs'), 'unlink', 'mock error'); 127 | const key = 'foo1'; 128 | await snapshot.save(key, 'value'); 129 | let error; 130 | try { 131 | await Promise.all([ 132 | snapshot.await('error'), 133 | snapshot.delete(key), 134 | ]); 135 | } catch (err) { 136 | error = err; 137 | } 138 | assert(error && error.name === 'SnapshotDeleteError'); 139 | assert(error.key === key); 140 | assert(error.message === 'mock error'); 141 | }); 142 | 143 | it('should batchSave ok', async () => { 144 | await snapshot.batchSave([ 145 | { key: 'key1', value: 'value1' }, 146 | { key: 'key2', value: 'value2' }, 147 | ]); 148 | let data = await snapshot.get('key1'); 149 | assert(data === 'value1'); 150 | data = await snapshot.get('key2'); 151 | assert(data === 'value2'); 152 | }); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /packages/nacos-config/test/utils.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { getMD5String } from '../src/utils'; 18 | 19 | const assert = require('assert'); 20 | 21 | describe('test/utils.test.ts', function() { 22 | it('should getMD5String ok', function() { 23 | const str = '172.24.13.28:5198#172.24.30.28:5198#172.23.13.10:5198#172.23.14.46:5198#group3#100#Mon Mar 20 13:32:49 CST 2010#online'; 24 | assert(getMD5String(str) === '3001aeb96c243fa3302e42ab2c1a16ad'); 25 | }); 26 | 27 | it('should getMD5String ok with 中文', function() { 28 | const str = 'cashier.function.switcher.status=on\ncashier.function.switcher.whiteListStrategy.tbNickPattern=临观|lichen6928|fangyuct01|朱琳1219|xiaoyin1916|简单de老公|奚薇0716|安桔熟了|七空八档|lichen6928|蝶羽轻尘|漂亮一下吧11|xupingan126|qqk2006|tb5808466|江南好吃|zhang_junlong|ct测试账号002|cguo82|'; 29 | assert(getMD5String(str, 'gbk') === 'f7c5371396b7e7c2777a43590d4c5be2'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/nacos-config/test/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import { Configuration } from '../src/configuration'; 19 | import { DEFAULT_OPTIONS } from '../src/const'; 20 | 21 | export function createDefaultConfiguration(config: any) { 22 | return new Configuration(Object.assign({}, DEFAULT_OPTIONS)).merge(config); 23 | } 24 | -------------------------------------------------------------------------------- /packages/nacos-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "experimentalDecorators": true, 7 | "noImplicitThis": true, 8 | "noUnusedLocals": true, 9 | "stripInternal": true, 10 | "pretty": true, 11 | "declaration": true, 12 | "outDir": "dist", 13 | "lib": ["es2017"], 14 | "sourceMap": true 15 | }, 16 | "exclude": [ 17 | "dist", 18 | "node_modules", 19 | "test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/nacos-config/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "**/dist/**", 4 | "node_modules" 5 | ], 6 | "rules": { 7 | "jsdoc-format": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "indent": [ 14 | true, 15 | "spaces" 16 | ], 17 | "no-duplicate-variable": true, 18 | "no-eval": true, 19 | "no-internal-module": true, 20 | "no-trailing-whitespace": true, 21 | "no-unsafe-finally": true, 22 | "no-var-keyword": true, 23 | "one-line": [ 24 | true, 25 | "check-open-brace", 26 | "check-whitespace" 27 | ], 28 | "quotemark": [ 29 | true, 30 | "single" 31 | ], 32 | "semicolon": [ 33 | true, 34 | "always" 35 | ], 36 | "triple-equals": [ 37 | true, 38 | "allow-null-check" 39 | ], 40 | "typedef-whitespace": [ 41 | true, 42 | { 43 | "call-signature": "nospace", 44 | "index-signature": "nospace", 45 | "parameter": "nospace", 46 | "property-declaration": "nospace", 47 | "variable-declaration": "nospace" 48 | } 49 | ], 50 | "variable-name": [ 51 | true, 52 | "ban-keywords" 53 | ], 54 | "whitespace": [ 55 | true, 56 | "check-decl", 57 | "check-operator", 58 | "check-separator", 59 | "check-type" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/nacos-naming/.autod.conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'ues strict'; 19 | 20 | module.exports = { 21 | write: true, 22 | prefix: '^', 23 | devprefix: '^', 24 | exclude: [ 25 | 'test/fixtures', 26 | ], 27 | devdep: [ 28 | 'autod', 29 | 'egg-ci', 30 | 'egg-bin', 31 | 'eslint', 32 | 'eslint-config-egg', 33 | 'contributors', 34 | ], 35 | keep: [], 36 | semver: [], 37 | }; 38 | -------------------------------------------------------------------------------- /packages/nacos-naming/.eslintignore: -------------------------------------------------------------------------------- 1 | test/fixtures 2 | coverage 3 | examples/**/app/public 4 | logs 5 | run 6 | -------------------------------------------------------------------------------- /packages/nacos-naming/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg" 3 | } 4 | -------------------------------------------------------------------------------- /packages/nacos-naming/.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | node_js: 4 | - '8' 5 | - '10' 6 | - '12' 7 | before_install: 8 | - npm i npminstall -g 9 | install: 10 | - npminstall 11 | script: 12 | - npm run ci 13 | after_script: 14 | - npminstall codecov && codecov 15 | -------------------------------------------------------------------------------- /packages/nacos-naming/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.0.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v2.0.0...v2.0.1) (2021-01-26) 7 | 8 | **Note:** Version bump only for package nacos-naming 9 | 10 | 11 | 12 | 13 | 14 | ## [1.1.2](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.1.1...v1.1.2) (2019-03-31) 15 | 16 | **Note:** Version bump only for package nacos-naming 17 | 18 | 19 | 20 | 21 | 22 | ## [1.1.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.1.0...v1.1.1) (2019-01-17) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * beatinfo is wrong ([26f8cee](https://github.com/nacos-group/nacos-sdk-nodejs/commit/26f8cee)) 28 | 29 | 30 | 31 | 32 | 33 | # [1.1.0](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.0.2...v1.1.0) (2019-01-15) 34 | 35 | 36 | ### Features 37 | 38 | * support registerInstance(serviceName, instance) ([32d2670](https://github.com/nacos-group/nacos-sdk-nodejs/commit/32d2670)) 39 | 40 | 41 | 42 | 43 | 44 | # 0.2.0 (2018-12-03) 45 | 46 | 47 | ### Features 48 | 49 | * export in one package ([fa747bc](https://github.com/nacos-group/nacos-sdk-nodejs/commit/fa747bc)) 50 | -------------------------------------------------------------------------------- /packages/nacos-naming/README.md: -------------------------------------------------------------------------------- 1 | # nacos-sdk-nodejs 2 | ======= 3 | 4 | [![NPM version][npm-image]][npm-url] 5 | [![build status][travis-image]][travis-url] 6 | [![David deps][david-image]][david-url] 7 | 8 | [npm-image]: https://img.shields.io/npm/v/ali-ons.svg?style=flat-square 9 | [npm-url]: https://npmjs.org/package/ali-ons 10 | [travis-image]: https://img.shields.io/travis/ali-sdk/ali-ons.svg?style=flat-square 11 | [travis-url]: https://travis-ci.org/ali-sdk/ali-ons 12 | [david-image]: https://img.shields.io/david/ali-sdk/ali-ons.svg?style=flat-square 13 | [david-url]: https://david-dm.org/ali-sdk/ali-ons 14 | 15 | 16 | [Nacos](https://nacos.io/en-us/) Node.js SDK 17 | 18 | ## Install 19 | 20 | ```bash 21 | npm install nacos --save 22 | ``` 23 | 24 | ## Usage 25 | 26 | ### Service Discovery 27 | 28 | ```js 29 | 'use strict'; 30 | 31 | const NacosNamingClient = require('nacos').NacosNamingClient; 32 | const logger = console; 33 | 34 | const client = new NacosNamingClient({ 35 | logger, 36 | serverList: '127.0.0.1:8848', // replace to real nacos serverList 37 | namespace: 'public', 38 | }); 39 | await client.ready(); 40 | 41 | const serviceName = 'nodejs.test.domain'; 42 | 43 | // registry instance 44 | await client.registerInstance(serviceName, { 45 | ip: '1.1.1.1', 46 | port: 8080, 47 | }); 48 | await client.registerInstance(serviceName, { 49 | ip: '2.2.2.2', 50 | port: 8080, 51 | }); 52 | 53 | // subscribe instance 54 | client.subscribe(serviceName, hosts => { 55 | console.log(hosts); 56 | }); 57 | 58 | // deregister instance 59 | await client.deregisterInstance(serviceName, { 60 | ip: '1.1.1.1', 61 | port: 8080, 62 | }); 63 | ``` 64 | 65 | ## APIs 66 | 67 | ### Service Discovery 68 | 69 | - `registerInstance(serviceName, instance, [groupName])` Register an instance to service. 70 | - serviceName {String} Service name 71 | - instance {Instance} 72 | - ip {String} IP of instance 73 | - port {Number} Port of instance 74 | - [weight] {Number} weight of the instance, default is 1.0 75 | - [ephemeral] {Boolean} active until the client is alive, default is true 76 | - [clusterName] {String} Virtual cluster name 77 | - [groupName] {String} group name, default is `DEFAULT_GROUP` 78 | - `deregisterInstance(serviceName, ip, port, [cluster])` Delete instance from service. 79 | - serviceName {String} Service name 80 | - instance {Instance} 81 | - ip {String} IP of instance 82 | - port {Number} Port of instance 83 | - [weight] {Number} weight of the instance, default is 1.0 84 | - [ephemeral] {Boolean} active until the client is alive, default is true 85 | - [clusterName] {String} Virtual cluster name 86 | - [groupName] {String} group name, default is `DEFAULT_GROUP` 87 | - `getAllInstances(serviceName, [groupName], [clusters], [subscribe])` Query instance list of service. 88 | - serviceName {String} Service name 89 | - [groupName] {String} group name, default is `DEFAULT_GROUP` 90 | - [clusters] {String} Cluster names 91 | - [subscribe] {Boolean} whether subscribe the service, default is true 92 | - `getServerStatus()` Get the status of nacos server, 'UP' or 'DOWN'. 93 | - `subscribe(info, listener)` Subscribe the instances of the service 94 | - info {Object}|{String} service info, if type is string, it's the serviceName 95 | - listener {Function} the listener function 96 | - `unSubscribe(info, [listener])` Unsubscribe the instances of the service 97 | - info {Object}|{String} service info, if type is string, it's the serviceName 98 | - listener {Function} the listener function, if not provide, will unSubscribe all listeners under this service 99 | 100 | ## Questions & Suggestions 101 | 102 | Please let us know how can we help. Do check out [issues](https://github.com/nacos-group/nacos-sdk-nodejs/issues) for bug reports or suggestions first. 103 | 104 | PR is welcome. 105 | 106 | ## License 107 | 108 | [Apache License V2](LICENSE) 109 | -------------------------------------------------------------------------------- /packages/nacos-naming/example/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const NacosNamingClient = require('../lib/naming/client'); 21 | const sleep = require('mz-modules/sleep'); 22 | const logger = console; 23 | 24 | async function test() { 25 | const client = new NacosNamingClient({ 26 | logger, 27 | serverList: '127.0.0.1:8848', 28 | namespace: 'public', 29 | }); 30 | await client.ready(); 31 | 32 | const serviceName = 'nodejs.test.nodejs.1'; 33 | 34 | // console.log(); 35 | // console.log('before', await client.getAllInstances(serviceName, ['NODEJS'])); 36 | // console.log(); 37 | 38 | client.subscribe(serviceName, hosts => { 39 | console.log(hosts); 40 | }); 41 | 42 | await client.registerInstance(serviceName, { 43 | ip: '1.1.1.1', 44 | port: 8080, 45 | }); 46 | await client.registerInstance(serviceName, { 47 | ip: '2.2.2.2', 48 | port: 8080, 49 | }); 50 | 51 | // const hosts = await client.getAllInstances(serviceName); 52 | // console.log(); 53 | // console.log(hosts); 54 | // console.log(); 55 | 56 | await sleep(5000); 57 | 58 | await client.deregisterInstance(serviceName, { 59 | ip: '1.1.1.1', 60 | port: 8080, 61 | }); 62 | } 63 | 64 | test().catch(err => { 65 | console.log(err); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/nacos-naming/index.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: ugrg 3 | * Create Time: 2021/8/20 9:12 4 | */ 5 | interface Instance { 6 | instanceId: string, 7 | ip: string, //IP of instance 8 | port: number, //Port of instance 9 | healthy: boolean, 10 | enabled: boolean, 11 | serviceName?: string, 12 | weight?: number, 13 | ephemeral?: boolean, 14 | clusterName?: string 15 | } 16 | 17 | export interface Host { 18 | instanceId: string; 19 | ip: string; 20 | port: number; 21 | weight: number; 22 | healthy: boolean; 23 | enabled: boolean; 24 | ephemeral: boolean; 25 | clusterName: string; 26 | serviceName: string; 27 | metadata: any; 28 | instanceHeartBeatInterval: number; 29 | instanceIdGenerator: string; 30 | instanceHeartBeatTimeOut: number; 31 | ipDeleteTimeout: number; 32 | } 33 | 34 | type Hosts = Host[]; 35 | 36 | interface SubscribeInfo { 37 | serviceName: string, 38 | groupName?: string, 39 | clusters?: string 40 | } 41 | 42 | interface NacosNamingClientConfig { 43 | logger: typeof console, 44 | serverList: string | string[], 45 | namespace?: string, 46 | username?: string, 47 | password?: string, 48 | endpoint?: string, 49 | vipSrvRefInterMillis?: number, 50 | ssl?: boolean 51 | } 52 | 53 | /** 54 | * Nacos服务发现组件 55 | */ 56 | export class NacosNamingClient { 57 | constructor (config: NacosNamingClientConfig); 58 | 59 | ready: () => Promise; 60 | // Register an instance to service 61 | registerInstance: ( 62 | serviceName: string, //Service name 63 | instance: Instance, //Instance 64 | groupName?: string // group name, default is DEFAULT_GROUP 65 | ) => Promise; 66 | // Delete instance from service. 67 | deregisterInstance: ( 68 | serviceName: string, //Service name 69 | instance: Instance, //Instance 70 | groupName?: string // group name, default is DEFAULT_GROUP 71 | ) => Promise; 72 | // Query instance list of service. 73 | getAllInstances: ( 74 | serviceName: string, //Service name 75 | groupName?: string, //group name, default is DEFAULT_GROUP 76 | clusters?: string, //Cluster names 77 | subscribe?: boolean //whether subscribe the service, default is true 78 | ) => Promise; 79 | // Select instance list of service. 80 | selectInstances: ( 81 | serviceName: string, 82 | groupName?: string, 83 | clusters?: string, 84 | healthy?: boolean, 85 | subscribe?: boolean 86 | ) => Promise; 87 | // Get the status of nacos server, 'UP' or 'DOWN'. 88 | getServerStatus: () => 'UP' | 'DOWN'; 89 | subscribe: ( 90 | info: SubscribeInfo | string, //service info, if type is string, it's the serviceName 91 | listener: (host: Hosts) => void //the listener function 92 | ) => void; 93 | unSubscribe: ( 94 | info: SubscribeInfo | string, //service info, if type is string, it's the serviceName 95 | listener: (host: Hosts) => void //the listener function 96 | ) => void; 97 | } 98 | -------------------------------------------------------------------------------- /packages/nacos-naming/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | exports.NacosNamingClient = require('./lib/naming/client'); 21 | -------------------------------------------------------------------------------- /packages/nacos-naming/lib/const.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | exports.VERSION = 'Nacos-Java-Client:v1.0.0'; 21 | 22 | exports.ENCODING = 'UTF-8'; 23 | 24 | exports.ENV_LIST_KEY = 'envList'; 25 | 26 | exports.ALL_IPS = '000--00-ALL_IPS--00--000'; 27 | 28 | exports.FAILOVER_SWITCH = '00-00---000-VIPSRV_FAILOVER_SWITCH-000---00-00'; 29 | 30 | exports.NACOS_URL_BASE = '/nacos/v1/ns'; 31 | 32 | exports.NACOS_URL_INSTANCE = exports.NACOS_URL_BASE + '/instance'; 33 | 34 | exports.DEFAULT_NAMESPACE_ID = 'default'; 35 | 36 | exports.REQUEST_DOMAIN_RETRY_COUNT = 3; 37 | 38 | exports.DEFAULT_NAMING_ID = 'default'; 39 | 40 | exports.NACOS_NAMING_LOG_NAME = 'com.alibaba.nacos.naming.log.filename'; 41 | 42 | exports.NACOS_NAMING_LOG_LEVEL = 'com.alibaba.nacos.naming.log.level'; 43 | 44 | exports.SERVER_ADDR_IP_SPLITER = ':'; 45 | 46 | exports.NAMING_INSTANCE_ID_SPLITTER = '#'; 47 | 48 | exports.NAMING_DEFAULT_CLUSTER_NAME = 'DEFAULT'; 49 | 50 | exports.SERVICE_INFO_SPLITER = '@@'; 51 | 52 | exports.DEFAULT_GROUP = 'DEFAULT_GROUP'; 53 | 54 | exports.DEFAULT_DELAY = 5000; 55 | -------------------------------------------------------------------------------- /packages/nacos-naming/lib/naming/beat_reactor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const Base = require('sdk-base'); 21 | const assert = require('assert'); 22 | const Constants = require('../const'); 23 | const sleep = require('mz-modules/sleep'); 24 | 25 | class BeatReactor extends Base { 26 | constructor(options = {}) { 27 | assert(options.serverProxy, '[BeatReactor] options.serverProxy is required'); 28 | super(options); 29 | 30 | this._isClosed = false; 31 | this._dom2Beat = new Map(); 32 | this._isRunning = false; 33 | this._clientBeatInterval = 10 * 1000; 34 | this._startBeat(); 35 | this.ready(true); 36 | } 37 | 38 | get serverProxy() { 39 | return this.options.serverProxy; 40 | } 41 | 42 | addBeatInfo(serviceName, beatInfo) { 43 | this._dom2Beat.set(this._buildKey(serviceName, beatInfo.ip, beatInfo.port), beatInfo); 44 | } 45 | 46 | removeBeatInfo(serviceName, ip, port) { 47 | this._dom2Beat.delete(this._buildKey(serviceName, ip, port)); 48 | } 49 | 50 | _buildKey(dom, ip, port) { 51 | return dom + Constants.NAMING_INSTANCE_ID_SPLITTER + ip + Constants.NAMING_INSTANCE_ID_SPLITTER + port; 52 | } 53 | 54 | async _beat(beatInfo) { 55 | if (beatInfo.scheduled) return; 56 | 57 | beatInfo.scheduled = true; 58 | this._clientBeatInterval = await this.serverProxy.sendBeat(beatInfo); 59 | beatInfo.scheduled = false; 60 | } 61 | 62 | async _startBeat() { 63 | if (this._isRunning) return; 64 | 65 | this._isRunning = true; 66 | while (!this._isClosed) { 67 | await Promise.all(Array.from(this._dom2Beat.values()) 68 | .map(beatInfo => this._beat(beatInfo))); 69 | await sleep(this._clientBeatInterval); 70 | } 71 | this._isRunning = false; 72 | } 73 | 74 | async _close() { 75 | this._isClosed = true; 76 | this._isRunning = false; 77 | this._dom2Beat.clear(); 78 | } 79 | } 80 | 81 | module.exports = BeatReactor; 82 | -------------------------------------------------------------------------------- /packages/nacos-naming/lib/naming/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const utils = require('../util'); 21 | const Base = require('sdk-base'); 22 | const assert = require('assert'); 23 | const Instance = require('./instance'); 24 | const NamingProxy = require('./proxy'); 25 | const BeatReactor = require('./beat_reactor'); 26 | const HostReactor = require('./host_reactor'); 27 | const Constants = require('../const'); 28 | 29 | const defaultOptions = { 30 | namespace: 'default', 31 | }; 32 | 33 | class NacosNamingClient extends Base { 34 | constructor(options = {}) { 35 | assert(options.logger, ''); 36 | super(Object.assign({}, defaultOptions, options, { initMethod: '_init' })); 37 | 38 | this._serverProxy = new NamingProxy(this.options); 39 | this._beatReactor = new BeatReactor({ 40 | serverProxy: this._serverProxy, 41 | logger: this.logger, 42 | }); 43 | this._hostReactor = new HostReactor({ 44 | serverProxy: this._serverProxy, 45 | logger: this.logger, 46 | }); 47 | } 48 | 49 | async _init() { 50 | await this._hostReactor.ready(); 51 | } 52 | 53 | get logger() { 54 | return this.options.logger; 55 | } 56 | 57 | async registerInstance(serviceName, instance, groupName = Constants.DEFAULT_GROUP) { 58 | if (!(instance instanceof Instance)) { 59 | instance = new Instance(instance); 60 | } 61 | const serviceNameWithGroup = utils.getGroupedName(serviceName, groupName); 62 | if (instance.ephemeral) { 63 | const beatInfo = { 64 | serviceName: serviceNameWithGroup, 65 | ip: instance.ip, 66 | port: instance.port, 67 | cluster: instance.clusterName, 68 | weight: instance.weight, 69 | metadata: instance.metadata, 70 | scheduled: false, 71 | }; 72 | this._beatReactor.addBeatInfo(serviceNameWithGroup, beatInfo); 73 | } 74 | await this._serverProxy.registerService(serviceNameWithGroup, groupName, instance); 75 | } 76 | 77 | async deregisterInstance(serviceName, instance, groupName = Constants.DEFAULT_GROUP) { 78 | if (!(instance instanceof Instance)) { 79 | instance = new Instance(instance); 80 | } 81 | const serviceNameWithGroup = utils.getGroupedName(serviceName, groupName); 82 | this._beatReactor.removeBeatInfo(serviceNameWithGroup, instance.ip, instance.port); 83 | await this._serverProxy.deregisterService(serviceName, instance); 84 | } 85 | 86 | async getAllInstances(serviceName, groupName = Constants.DEFAULT_GROUP, clusters = '', subscribe = true) { 87 | let serviceInfo; 88 | const serviceNameWithGroup = utils.getGroupedName(serviceName, groupName); 89 | if (subscribe) { 90 | serviceInfo = await this._hostReactor.getServiceInfo(serviceNameWithGroup, clusters); 91 | } else { 92 | serviceInfo = await this._hostReactor.getServiceInfoDirectlyFromServer(serviceNameWithGroup, clusters); 93 | } 94 | if (!serviceInfo) return []; 95 | return serviceInfo.hosts; 96 | } 97 | 98 | async selectInstances(serviceName, groupName = Constants.DEFAULT_GROUP, clusters = '', healthy = true, subscribe = true) { 99 | let serviceInfo; 100 | const serviceNameWithGroup = utils.getGroupedName(serviceName, groupName); 101 | if (subscribe) { 102 | serviceInfo = await this._hostReactor.getServiceInfo(serviceNameWithGroup, clusters); 103 | } else { 104 | serviceInfo = await this._hostReactor.getServiceInfoDirectlyFromServer(serviceNameWithGroup, clusters); 105 | } 106 | if (!serviceInfo || !serviceInfo.hosts || !serviceInfo.hosts.length) { 107 | return []; 108 | } 109 | return serviceInfo.hosts.filter(host => { 110 | return host.healthy === healthy && host.enabled && host.weight > 0; 111 | }); 112 | } 113 | 114 | async getServerStatus() { 115 | const isHealthy = await this._serverProxy.serverHealthy(); 116 | return isHealthy ? 'UP' : 'DOWN'; 117 | } 118 | 119 | subscribe(info, listener) { 120 | if (typeof info === 'string') { 121 | info = { 122 | serviceName: info, 123 | }; 124 | } 125 | const groupName = info.groupName || Constants.DEFAULT_GROUP; 126 | const serviceNameWithGroup = utils.getGroupedName(info.serviceName, groupName); 127 | this._hostReactor.subscribe({ 128 | serviceName: serviceNameWithGroup, 129 | clusters: info.clusters || '', 130 | }, listener); 131 | } 132 | 133 | unSubscribe(info, listener) { 134 | if (typeof info === 'string') { 135 | info = { 136 | serviceName: info, 137 | }; 138 | } 139 | const groupName = info.groupName || Constants.DEFAULT_GROUP; 140 | const serviceNameWithGroup = utils.getGroupedName(info.serviceName, groupName); 141 | this._hostReactor.unSubscribe({ 142 | serviceName: serviceNameWithGroup, 143 | clusters: info.clusters || '', 144 | }, listener); 145 | } 146 | 147 | async _close() { 148 | await this._beatReactor.close(); 149 | await this._hostReactor.close(); 150 | } 151 | } 152 | 153 | module.exports = NacosNamingClient; 154 | -------------------------------------------------------------------------------- /packages/nacos-naming/lib/naming/host_reactor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const assert = require('assert'); 21 | const Base = require('sdk-base'); 22 | const Constants = require('../const'); 23 | const ServiceInfo = require('./service_info'); 24 | const PushReceiver = require('./push_receiver'); 25 | const equals = require('equals'); 26 | 27 | class HostReactor extends Base { 28 | constructor(options = {}) { 29 | assert(options.logger, '[HostReactor] options.logger is required'); 30 | assert(options.serverProxy, '[HostReactor] options.serverProxy is required'); 31 | super(Object.assign({}, options, { initMethod: '_init' })); 32 | 33 | // TODO: 34 | // cacheDir 35 | 36 | this._serviceInfoMap = new Map(); 37 | this._updatingSet = new Set(); 38 | this._futureMap = new Map(); 39 | this._pushReceiver = new PushReceiver(this); 40 | } 41 | 42 | get logger() { 43 | return this.options.logger; 44 | } 45 | 46 | get serverProxy() { 47 | return this.options.serverProxy; 48 | } 49 | 50 | get getServiceInfoMap() { 51 | const map = {}; 52 | for (const key of this._serviceInfoMap.keys()) { 53 | map[key] = this._serviceInfoMap.get(key); 54 | } 55 | return map; 56 | } 57 | 58 | async _init() { 59 | await Promise.all([ 60 | this.serverProxy.ready(), 61 | this._pushReceiver.ready(), 62 | ]); 63 | } 64 | 65 | processServiceJSON(json) { 66 | const data = JSON.parse(json); 67 | const serviceInfo = new ServiceInfo(data); 68 | const oldService = this._serviceInfoMap.get(serviceInfo.getKey()); 69 | if (!serviceInfo.isValid) { 70 | return oldService; 71 | } 72 | 73 | serviceInfo.jsonFromServer = json; 74 | if (oldService) { 75 | if (oldService.lastRefTime > serviceInfo.lastRefTime) { 76 | this.logger.warn('[HostReactor] out of date data received, old-t: %s, new-t: ', oldService.lastRefTime, serviceInfo.lastRefTime); 77 | } 78 | 79 | this._serviceInfoMap.set(serviceInfo.getKey(), serviceInfo); 80 | 81 | const oldHostMap = new Map(); 82 | for (const host of oldService.hosts) { 83 | const key = host.ip + ':' + host.port; 84 | oldHostMap.set(key, host); 85 | } 86 | 87 | const modHosts = []; 88 | const newHosts = []; 89 | const remvHosts = []; 90 | 91 | const newHostMap = new Map(); 92 | for (const host of serviceInfo.hosts) { 93 | const key = host.ip + ':' + host.port; 94 | newHostMap.set(key, host); 95 | 96 | if (oldHostMap.has(key) && !equals(host, oldHostMap.get(key))) { 97 | modHosts.push(host); 98 | continue; 99 | } 100 | 101 | if (!oldHostMap.has(key)) { 102 | newHosts.push(host); 103 | continue; 104 | } 105 | } 106 | 107 | for (const key of oldHostMap.keys()) { 108 | if (newHostMap.has(key)) continue; 109 | 110 | if (!newHostMap.has(key)) { 111 | remvHosts.push(oldHostMap.get(key)); 112 | continue; 113 | } 114 | } 115 | 116 | if (newHosts.length) { 117 | this.logger.info('[HostReactor] new ips(%d) service: %s -> %j', newHosts.length, serviceInfo.name, newHosts); 118 | } 119 | if (remvHosts.length) { 120 | this.logger.info('[HostReactor] removed ips(%d) service: %s -> %j', remvHosts.length, serviceInfo.name, remvHosts); 121 | } 122 | if (modHosts.length) { 123 | this.logger.info('[HostReactor] modified ips(%d) service: %s -> %j', modHosts.length, serviceInfo.name, modHosts); 124 | } 125 | 126 | if (newHosts.length || remvHosts.length || modHosts.length) { 127 | this.emit(`${serviceInfo.getKey()}_changed`, serviceInfo.hosts, serviceInfo); 128 | // TODO: 本地缓存 129 | } else if (oldHostMap.size === 0) { 130 | this.emit(`${serviceInfo.getKey()}_changed`, serviceInfo.hosts, serviceInfo); 131 | } 132 | } else { 133 | this._serviceInfoMap.set(serviceInfo.getKey(), serviceInfo); 134 | this.emit(`${serviceInfo.getKey()}_changed`, serviceInfo.hosts, serviceInfo); 135 | // TODO: 本地缓存 136 | } 137 | return serviceInfo; 138 | } 139 | 140 | _getKey(param) { 141 | const serviceName = param.serviceName; 142 | const clusters = param.clusters || Constants.NAMING_DEFAULT_CLUSTER_NAME; 143 | return ServiceInfo.getKey(serviceName, clusters); 144 | } 145 | 146 | subscribe(param, listener) { 147 | const key = this._getKey(param); 148 | const serviceInfo = this._serviceInfoMap.get(key); 149 | if (serviceInfo) { 150 | setImmediate(() => { listener(serviceInfo.hosts); }); 151 | } else { 152 | this.getServiceInfo(param.serviceName, param.clusters || Constants.NAMING_DEFAULT_CLUSTER_NAME); 153 | } 154 | this.on(key + '_changed', listener); 155 | } 156 | 157 | unSubscribe(param, listener) { 158 | const key = this._getKey(param); 159 | if (listener) { 160 | this.removeListener(key + '_changed', listener); 161 | } else { 162 | this.removeAllListeners(key + '_changed'); 163 | } 164 | } 165 | 166 | async getServiceInfoDirectlyFromServer(serviceName, clusters = Constants.NAMING_DEFAULT_CLUSTER_NAME) { 167 | const result = await this.serverProxy.queryList(serviceName, clusters, 0, false); 168 | if (result) { 169 | return this.processServiceJSON(result); 170 | } 171 | return null; 172 | } 173 | 174 | async getServiceInfo(serviceName, clusters = Constants.NAMING_DEFAULT_CLUSTER_NAME) { 175 | const key = ServiceInfo.getKey(serviceName, clusters); 176 | // TODO: failover 177 | 178 | let serviceInfo = this._serviceInfoMap.get(key); 179 | if (!serviceInfo) { 180 | serviceInfo = new ServiceInfo({ 181 | name: serviceName, 182 | clusters, 183 | hosts: [], 184 | }); 185 | this._serviceInfoMap.set(key, serviceInfo); 186 | this._updatingSet.add(key); 187 | await this.updateServiceNow(serviceName, clusters); 188 | this._updatingSet.delete(key); 189 | } else if (this._updatingSet.has(key)) { 190 | // await updating 191 | await this.await(`${key}_changed`); 192 | } 193 | this._scheduleUpdateIfAbsent(serviceName, clusters); 194 | return this._serviceInfoMap.get(key); 195 | } 196 | 197 | async updateServiceNow(serviceName, clusters) { 198 | try { 199 | const result = await this.serverProxy.queryList(serviceName, clusters, this._pushReceiver.udpPort, false); 200 | if (result) { 201 | this.processServiceJSON(result); 202 | } 203 | this.logger.debug('[HostReactor] updateServiceNow() serviceName: %s, clusters: %s, result: %s', serviceName, clusters, result); 204 | } catch (err) { 205 | err.message = 'failed to update serviceName: ' + serviceName + ', caused by: ' + err.message; 206 | if (err.status === 404) { 207 | this.logger.warn(err.message); 208 | } else { 209 | this.emit('error', err); 210 | } 211 | } 212 | } 213 | 214 | async refreshOnly(serviceName, clusters) { 215 | try { 216 | await this.serverProxy.queryList(serviceName, clusters, this._pushReceiver.udpPort, false); 217 | } catch (err) { 218 | err.message = 'failed to update serviceName: ' + serviceName + ', caused by: ' + err.message; 219 | this.emit('error', err); 220 | } 221 | } 222 | 223 | _scheduleUpdateIfAbsent(serviceName, clusters) { 224 | const key = ServiceInfo.getKey(serviceName, clusters); 225 | if (this._futureMap.has(key)) { 226 | return; 227 | } 228 | // 第一次延迟 1s 更新 229 | const timer = setTimeout(() => { 230 | this._doUpdate(serviceName, clusters) 231 | .catch(err => { 232 | this.emit('error', err); 233 | }); 234 | }, 1000); 235 | const task = { 236 | timer, 237 | lastRefTime: Infinity, 238 | }; 239 | this._futureMap.set(key, task); 240 | } 241 | 242 | async _doUpdate(serviceName, clusters) { 243 | const key = ServiceInfo.getKey(serviceName, clusters); 244 | const task = this._futureMap.get(key); 245 | if (!task) return; 246 | 247 | const serviceInfo = this._serviceInfoMap.get(key); 248 | if (!serviceInfo || serviceInfo.lastRefTime <= task.lastRefTime) { 249 | await this.updateServiceNow(serviceName, clusters); 250 | } else { 251 | this.logger.debug('[HostReactor] refreshOnly, serviceInfo.lastRefTime: %s, task.lastRefTime: %s, serviceName: %s, clusters: %s', 252 | serviceInfo.lastRefTime, task.lastRefTime, serviceName, clusters); 253 | // if serviceName already updated by push, we should not override it 254 | // since the push data may be different from pull through force push 255 | await this.refreshOnly(serviceName, clusters); 256 | } 257 | 258 | if (this._futureMap.has(key)) { 259 | const serviceInfo = this._serviceInfoMap.get(key); 260 | let delay = Constants.DEFAULT_DELAY; 261 | if (serviceInfo) { 262 | delay = serviceInfo.cacheMillis; 263 | task.lastRefTime = serviceInfo.lastRefTime; 264 | } 265 | const timer = setTimeout(() => { 266 | this._doUpdate(serviceName, clusters) 267 | .catch(err => { 268 | this.emit('error', err); 269 | }); 270 | }, delay); 271 | task.timer = timer; 272 | this._futureMap.set(key, task); 273 | } 274 | } 275 | 276 | async _close() { 277 | this._pushReceiver.close(); 278 | this._updatingSet.clear(); 279 | 280 | for (const key of this._futureMap.keys()) { 281 | const task = this._futureMap.get(key); 282 | clearTimeout(task.timer); 283 | } 284 | for (const key of this._serviceInfoMap.keys()) { 285 | this.unSubscribe(key); 286 | } 287 | this._serviceInfoMap.clear(); 288 | this._futureMap.clear(); 289 | } 290 | } 291 | 292 | module.exports = HostReactor; 293 | -------------------------------------------------------------------------------- /packages/nacos-naming/lib/naming/instance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const Constants = require('../const'); 21 | 22 | class Instance { 23 | constructor(data = {}) { 24 | this.instanceId = data.instanceId; // Unique ID of this instance 25 | this.ip = data.ip; // ip address 26 | this.port = data.port; // port 27 | this.weight = data.weight || 1; 28 | if (typeof data.valid === 'boolean') { 29 | this.healthy = data.valid; 30 | } else if (typeof data.healthy === 'boolean') { 31 | this.healthy = data.healthy; 32 | } else { 33 | this.healthy = true; 34 | } 35 | this.enabled = typeof data.enabled === 'boolean' ? data.enabled : true; 36 | this.ephemeral = typeof data.ephemeral === 'boolean' ? data.ephemeral : true; 37 | this.clusterName = data.clusterName || Constants.NAMING_DEFAULT_CLUSTER_NAME; // Cluster information of instance 38 | this.serviceName = data.serviceName; 39 | this.metadata = data.metadata || {}; 40 | } 41 | 42 | toString() { 43 | return JSON.stringify(this); 44 | } 45 | 46 | toInetAddr() { 47 | return this.ip + ':' + this.port; 48 | } 49 | 50 | equal(instance) { 51 | const str1 = this.toString(); 52 | const str2 = instance.toString(); 53 | return str1 === str2; 54 | } 55 | } 56 | 57 | module.exports = Instance; 58 | -------------------------------------------------------------------------------- /packages/nacos-naming/lib/naming/proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const uuid = require('uuid/v4'); 21 | const Base = require('sdk-base'); 22 | const utils = require('../util'); 23 | const assert = require('assert'); 24 | const utility = require('utility'); 25 | const Constants = require('../const'); 26 | const localIp = require('address').ip(); 27 | const sleep = require('mz-modules/sleep'); 28 | 29 | const defaultOptions = { 30 | namespace: 'default', 31 | httpclient: require('urllib'), 32 | ssl: false, 33 | ak: null, 34 | sk: null, 35 | appName: '', 36 | endpoint: null, 37 | vipSrvRefInterMillis: 30000, 38 | }; 39 | const DEFAULT_SERVER_PORT = 8848; 40 | 41 | class NameProxy extends Base { 42 | constructor(options = {}) { 43 | assert(options.logger, '[NameProxy] options.logger is required'); 44 | if (typeof options.serverList === 'string' && options.serverList) { 45 | options.serverList = options.serverList.split(','); 46 | } 47 | super(Object.assign({}, defaultOptions, options, { initMethod: '_init' })); 48 | 49 | this.serverList = options.serverList || []; 50 | // 硬负载域名 51 | if (this.serverList.length === 1) { 52 | this.nacosDomain = this.serverList[0]; 53 | } 54 | this.serversFromEndpoint = []; 55 | this.lastSrvRefTime = 0; 56 | } 57 | 58 | get logger() { 59 | return this.options.logger; 60 | } 61 | 62 | get endpoint() { 63 | return this.options.endpoint; 64 | } 65 | 66 | get namespace() { 67 | return this.options.namespace; 68 | } 69 | 70 | get httpclient() { 71 | return this.options.httpclient; 72 | } 73 | 74 | async _getServerListFromEndpoint() { 75 | const urlString = 'http://' + this.endpoint + '/nacos/serverlist'; 76 | const headers = this._builderHeaders(); 77 | 78 | const result = await this.httpclient.request(urlString, { 79 | method: 'GET', 80 | headers, 81 | dataType: 'text', 82 | }); 83 | if (result.status !== 200) { 84 | throw new Error('Error while requesting: ' + urlString + ', Server returned: ' + result.status); 85 | } 86 | const content = result.data; 87 | return content.split('\r\n'); 88 | } 89 | 90 | async _refreshSrvIfNeed() { 91 | if (this.serverList.length !== 0) { 92 | return; 93 | } 94 | 95 | if (Date.now() - this.lastSrvRefTime < this.options.vipSrvRefInterMillis) { 96 | return; 97 | } 98 | 99 | try { 100 | const list = await this._getServerListFromEndpoint(); 101 | if (!list || !list.length) { 102 | throw new Error('Can not acquire Nacos list'); 103 | } 104 | 105 | this.serversFromEndpoint = list; 106 | this.lastSrvRefTime = Date.now(); 107 | } catch (err) { 108 | this.logger.warn(err); 109 | } 110 | } 111 | 112 | async _init() { 113 | if (!this.endpoint) return; 114 | 115 | await this._refreshSrvIfNeed(); 116 | this._refreshLoop(); 117 | } 118 | 119 | async _refreshLoop() { 120 | while (!this._closed) { 121 | await sleep(this.options.vipSrvRefInterMillis); 122 | await this._refreshSrvIfNeed(); 123 | } 124 | } 125 | 126 | _getSignData(serviceName) { 127 | return serviceName ? Date.now() + '@@' + serviceName : Date.now() + ''; 128 | } 129 | 130 | _checkSignature(params) { 131 | const { ak, sk, appName } = this.options; 132 | if (!ak && !sk) return; 133 | 134 | const signData = this._getSignData(params.serviceName); 135 | const signature = utils.sign(signData, sk); 136 | params.signature = signature; 137 | params.data = signData; 138 | params.ak = ak; 139 | params.app = appName; 140 | } 141 | 142 | _builderHeaders() { 143 | return { 144 | 'User-Agent': Constants.VERSION, 145 | 'Client-Version': Constants.VERSION, 146 | 'Accept-Encoding': 'gzip,deflate,sdch', 147 | 'Request-Module': 'Naming', 148 | Connection: 'Keep-Alive', 149 | RequestId: uuid(), 150 | }; 151 | } 152 | 153 | async _callServer(serverAddr, method, api, params = {}) { 154 | this._checkSignature(params); 155 | params.namespaceId = this.namespace; 156 | const headers = this._builderHeaders(); 157 | 158 | if (!serverAddr.includes(Constants.SERVER_ADDR_IP_SPLITER)) { 159 | serverAddr = serverAddr + Constants.SERVER_ADDR_IP_SPLITER + DEFAULT_SERVER_PORT; 160 | } 161 | 162 | const url = (this.options.ssl ? 'https://' : 'http://') + serverAddr + api; 163 | if (this.options.username && this.options.password) { 164 | params.username = this.options.username; 165 | params.password = this.options.password; 166 | } 167 | const result = await this.httpclient.request(url, { 168 | method, 169 | headers, 170 | data: params, 171 | dataType: 'text', 172 | dataAsQueryString: true, 173 | }); 174 | 175 | if (result.status === 200) { 176 | return result.data; 177 | } 178 | if (result.status === 304) { 179 | return ''; 180 | } 181 | const err = new Error('failed to req API: ' + url + '. code: ' + result.status + ' msg: ' + result.data); 182 | err.name = 'NacosException'; 183 | err.status = result.status; 184 | throw err; 185 | } 186 | 187 | async _reqAPI(api, params, method) { 188 | // TODO: 189 | const servers = this.serverList.length ? this.serverList : this.serversFromEndpoint; 190 | const size = servers.length; 191 | 192 | if (size === 0 && !this.nacosDomain) { 193 | throw new Error('[NameProxy] no server available'); 194 | } 195 | 196 | if (size > 0) { 197 | let index = utility.random(size); 198 | for (let i = 0; i < size; i++) { 199 | const server = servers[index]; 200 | try { 201 | return await this._callServer(server, method, api, params); 202 | } catch (err) { 203 | this.logger.warn(err); 204 | } 205 | index = (index + 1) % size; 206 | } 207 | throw new Error('failed to req API: ' + api + ' after all servers(' + servers.join(',') + ') tried'); 208 | } 209 | 210 | for (let i = 0; i < Constants.REQUEST_DOMAIN_RETRY_COUNT; i++) { 211 | try { 212 | return await this._callServer(this.nacosDomain, method, api, params); 213 | } catch (err) { 214 | this.logger.warn(err); 215 | } 216 | } 217 | throw new Error('failed to req API: ' + api + ' after all servers(' + this.nacosDomain + ') tried'); 218 | } 219 | 220 | async registerService(serviceName, groupName, instance) { 221 | this.logger.info('[NameProxy][REGISTER-SERVICE] %s registering service: %s with instance:%j', this.namespace, serviceName, instance); 222 | 223 | const params = { 224 | namespaceId: this.namespace, 225 | serviceName, 226 | groupName, 227 | clusterName: instance.clusterName, 228 | ip: instance.ip, 229 | port: instance.port + '', 230 | weight: instance.weight + '', 231 | enable: instance.enabled ? 'true' : 'false', 232 | healthy: instance.healthy ? 'true' : 'false', 233 | ephemeral: instance.ephemeral ? 'true' : 'false', 234 | metadata: JSON.stringify(instance.metadata), 235 | }; 236 | return await this._reqAPI(Constants.NACOS_URL_INSTANCE, params, 'POST'); 237 | } 238 | 239 | async deregisterService(serviceName, instance) { 240 | this.logger.info('[NameProxy][DEREGISTER-SERVICE] %s deregistering service: %s with instance:%j', this.namespace, serviceName, instance); 241 | 242 | const params = { 243 | namespaceId: this.namespace, 244 | serviceName, 245 | clusterName: instance.clusterName, 246 | ip: instance.ip, 247 | port: instance.port + '', 248 | ephemeral: instance.ephemeral !== false ? 'true' : 'false', 249 | }; 250 | return await this._reqAPI(Constants.NACOS_URL_INSTANCE, params, 'DELETE'); 251 | } 252 | 253 | async queryList(serviceName, clusters, udpPort, healthyOnly) { 254 | const params = { 255 | namespaceId: this.namespace, 256 | serviceName, 257 | clusters, 258 | udpPort: udpPort + '', 259 | clientIP: localIp, 260 | healthyOnly: healthyOnly ? 'true' : 'false', 261 | }; 262 | return await this._reqAPI(Constants.NACOS_URL_BASE + '/instance/list', params, 'GET'); 263 | } 264 | 265 | async serverHealthy() { 266 | try { 267 | const str = await this._reqAPI(Constants.NACOS_URL_BASE + '/operator/metrics', {}, 'GET'); 268 | const result = JSON.parse(str); 269 | return result && result.status === 'UP'; 270 | } catch (_) { 271 | return false; 272 | } 273 | } 274 | 275 | async sendBeat(beatInfo) { 276 | try { 277 | const params = { 278 | beat: JSON.stringify(beatInfo), 279 | namespaceId: this.namespace, 280 | serviceName: beatInfo.serviceName, 281 | }; 282 | const jsonStr = await this._reqAPI(Constants.NACOS_URL_BASE + '/instance/beat', params, 'PUT'); 283 | const result = JSON.parse(jsonStr); 284 | if (result && result.clientBeatInterval) { 285 | return Number(result.clientBeatInterval); 286 | } 287 | } catch (err) { 288 | err.message = `[CLIENT-BEAT] failed to send beat: ${JSON.stringify(beatInfo)}, caused by ${err.message}`; 289 | this.logger.error(err); 290 | } 291 | return Constants.DEFAULT_DELAY; 292 | } 293 | 294 | async getServiceList(pageNo, pageSize, groupName) { 295 | const params = { 296 | pageNo: pageNo + '', 297 | pageSize: pageSize + '', 298 | namespaceId: this.namespace, 299 | groupName, 300 | }; 301 | // TODO: selector 302 | const result = await this._reqAPI(Constants.NACOS_URL_BASE + '/service/list', params, 'GET'); 303 | const json = JSON.parse(result); 304 | return { 305 | count: Number(json.count), 306 | data: json.doms, 307 | }; 308 | } 309 | } 310 | 311 | module.exports = NameProxy; 312 | -------------------------------------------------------------------------------- /packages/nacos-naming/lib/naming/push_receiver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const dgram = require('dgram'); 21 | const util = require('../util'); 22 | const Base = require('sdk-base'); 23 | const assert = require('assert'); 24 | 25 | class PushReceiver extends Base { 26 | constructor(hostReactor) { 27 | assert(hostReactor, '[PushReceiver] hostReactor is required'); 28 | super({}); 29 | 30 | this._udpPort = 0; 31 | this._inited = false; 32 | this._isClosed = false; 33 | this._hostReactor = hostReactor; 34 | this._createServer(); 35 | } 36 | 37 | get logger() { 38 | return this._hostReactor.logger; 39 | } 40 | 41 | get udpPort() { 42 | return this._udpPort; 43 | } 44 | 45 | _createServer() { 46 | this._server = dgram.createSocket({ 47 | type: 'udp4', 48 | }); 49 | this._server.once('error', err => { 50 | this._server.close(); 51 | err.name = 'PushReceiverError'; 52 | this.emit('error', err); 53 | }); 54 | this._server.once('close', () => { 55 | if (!this._isClosed) { 56 | this._createServer(); 57 | } 58 | }); 59 | this._server.once('listening', () => { 60 | const address = this._server.address(); 61 | this._udpPort = address.port; 62 | if (!this._inited) { 63 | this.ready(true); 64 | } 65 | this.logger.info('[PushReceiver] udp server listen on %s:%s', address.address, address.port); 66 | }); 67 | this._server.on('message', (msg, rinfo) => this._handlePushMessage(msg, rinfo)); 68 | // 随机绑定一个端口 69 | this._server.bind({port: 0, exclusive: true}, null); 70 | } 71 | 72 | _handlePushMessage(msg, rinfo) { 73 | try { 74 | const jsonStr = util.tryDecompress(msg).toString(); 75 | const pushPacket = JSON.parse(jsonStr); 76 | this.logger.info('[PushReceiver] received push data: %s from %s:%s', jsonStr, rinfo.address, rinfo.port); 77 | let ack; 78 | if (pushPacket.type === 'dom') { 79 | this._hostReactor.processServiceJSON(pushPacket.data); 80 | ack = JSON.stringify({ 81 | type: 'push-ack', 82 | lastRefTime: pushPacket.lastRefTime, 83 | data: '', 84 | }); 85 | } else if (pushPacket.type === 'dump') { 86 | ack = JSON.stringify({ 87 | type: 'dump-ack', 88 | lastRefTime: pushPacket.lastRefTime, 89 | data: JSON.stringify(this._hostReactor.getServiceInfoMap()), 90 | }); 91 | } else { 92 | ack = JSON.stringify({ 93 | type: 'unknown-ack', 94 | lastRefTime: pushPacket.lastRefTime, 95 | data: '', 96 | }); 97 | } 98 | this._server.send(ack, rinfo.port, rinfo.address); 99 | } catch (err) { 100 | err.name = 'PushReceiverError'; 101 | err.message += ' error while receiving push data'; 102 | this.emit('error', err); 103 | } 104 | } 105 | 106 | close() { 107 | return new Promise((resolve) => { 108 | this._isClosed = true; 109 | if (this._server) { 110 | this._server.close(resolve) 111 | this._server = null; 112 | } 113 | }); 114 | } 115 | } 116 | 117 | module.exports = PushReceiver; 118 | -------------------------------------------------------------------------------- /packages/nacos-naming/lib/naming/service_info.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const Constants = require('../const'); 21 | const EMPTY = ''; 22 | const SPLITER = '@@'; 23 | // const ALL_IPS = '000--00-ALL_IPS--00--000'; 24 | 25 | class ServiceInfo { 26 | constructor(data) { 27 | this.name = data.name || data.dom; 28 | this.groupName = data.groupName; 29 | this.clusters = data.clusters; 30 | this.isAllIPs = data.allIPs || false; 31 | this.cacheMillis = data.cacheMillis || Constants.DEFAULT_DELAY; 32 | this.hosts = data.hosts; 33 | this.lastRefTime = data.lastRefTime || 0; 34 | this.checksum = data.checksum || ''; 35 | this.jsonFromServer = EMPTY; 36 | } 37 | 38 | get ipCount() { 39 | return this.hosts.length; 40 | } 41 | 42 | get isValid() { 43 | const valid = !!this.hosts; 44 | // 如果 this.hosts 是空数组要返回 false 45 | if (valid && Array.isArray(this.hosts)) { 46 | return this.hosts.length > 0; 47 | } 48 | return valid; 49 | } 50 | 51 | getKey() { 52 | return ServiceInfo.getKey(this.name, this.clusters); 53 | } 54 | 55 | toString() { 56 | return this.getKey(); 57 | } 58 | 59 | static getKey(name, clusters) { 60 | if (clusters) { 61 | return name + SPLITER + clusters; 62 | } 63 | return name; 64 | } 65 | 66 | static fromKey(key) { 67 | let name; 68 | let clusters; 69 | let groupName; 70 | 71 | const segs = key.split(SPLITER); 72 | if (segs.length === 2) { 73 | groupName = segs[0]; 74 | name = segs[1]; 75 | } else if (segs.length === 3) { 76 | groupName = segs[0]; 77 | name = segs[1]; 78 | clusters = segs[2]; 79 | } 80 | return new ServiceInfo({ 81 | name, 82 | clusters, 83 | groupName, 84 | }); 85 | } 86 | } 87 | 88 | module.exports = ServiceInfo; 89 | -------------------------------------------------------------------------------- /packages/nacos-naming/lib/util/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const zlib = require('zlib'); 21 | const crypto = require('crypto'); 22 | const Constants = require('../const'); 23 | 24 | const GZIP_MAGIC = 35615; 25 | 26 | /* eslint-disable no-bitwise */ 27 | exports.isGzipStream = buf => { 28 | if (!buf || buf.length < 2) { 29 | return false; 30 | } 31 | return GZIP_MAGIC === ((buf[1] << 8 | buf[0]) & 0xFFFF); 32 | }; 33 | /* eslint-enable no-bitwise */ 34 | 35 | exports.tryDecompress = buf => { 36 | if (!this.isGzipStream(buf)) { 37 | return buf; 38 | } 39 | return zlib.gunzipSync(buf); 40 | }; 41 | 42 | exports.sign = (data, key) => { 43 | return crypto.createHmac('sha1', key).update(data).digest('base64'); 44 | }; 45 | 46 | exports.getGroupedName = (serviceName, groupName) => { 47 | return groupName + Constants.SERVICE_INFO_SPLITER + serviceName; 48 | }; 49 | 50 | exports.getServiceName = serviceNameWithGroup => { 51 | if (!serviceNameWithGroup.includes(Constants.SERVICE_INFO_SPLITER)) { 52 | return serviceNameWithGroup; 53 | } 54 | return serviceNameWithGroup.split(Constants.SERVICE_INFO_SPLITER)[1]; 55 | }; 56 | 57 | exports.getGroupName = serviceNameWithGroup => { 58 | if (!serviceNameWithGroup.includes(Constants.SERVICE_INFO_SPLITER)) { 59 | return Constants.DEFAULT_GROUP; 60 | } 61 | return serviceNameWithGroup.split(Constants.SERVICE_INFO_SPLITER)[0]; 62 | }; 63 | -------------------------------------------------------------------------------- /packages/nacos-naming/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nacos-naming", 3 | "version": "2.6.0", 4 | "description": "nacos (https://nacos.io/en-us/) nodejs sdk", 5 | "main": "index.js", 6 | "files": [ 7 | "lib", 8 | "index.js", 9 | "index.d.ts" 10 | ], 11 | "scripts": { 12 | "autod": "autod", 13 | "lint": "eslint . --ext .js", 14 | "cov": "TEST_TIMEOUT=20000 egg-bin cov", 15 | "test": "npm run lint && npm run test-local", 16 | "test-local": "egg-bin test", 17 | "pkgfiles": "egg-bin pkgfiles --check", 18 | "ci": "npm run autod -- --check && npm run pkgfiles && npm run lint && npm run cov", 19 | "contributors": "contributors -f plain -o AUTHORS" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/nacos-group/nacos-sdk-nodejs.git" 24 | }, 25 | "keywords": [ 26 | "nacos", 27 | "cloud", 28 | "native", 29 | "eggjs" 30 | ], 31 | "author": "gxcsoccer ", 32 | "license": "Apache", 33 | "bugs": { 34 | "url": "https://github.com/nacos-group/nacos-sdk-nodejs/issues" 35 | }, 36 | "homepage": "https://github.com/nacos-group/nacos-sdk-nodejs#readme", 37 | "engines": { 38 | "node": ">= 8.0.0" 39 | }, 40 | "ci": { 41 | "type": "travis", 42 | "version": "8, 10, 12" 43 | }, 44 | "dependencies": { 45 | "address": "^1.1.0", 46 | "equals": "^1.0.5", 47 | "mz-modules": "^2.1.0", 48 | "sdk-base": "^3.6.0", 49 | "urllib": "^2.33.3", 50 | "utility": "^1.16.1", 51 | "uuid": "^3.3.2" 52 | }, 53 | "devDependencies": { 54 | "autod": "^3.1.0", 55 | "await-event": "^2.1.0", 56 | "contributors": "^0.5.1", 57 | "egg-bin": "^4.13.1", 58 | "egg-ci": "^1.11.0", 59 | "eslint": "^5.16.0", 60 | "eslint-config-egg": "^7.3.1", 61 | "mm": "^2.5.0", 62 | "mz-modules": "^2.1.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/nacos-naming/test/naming/client.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const mm = require('mm'); 21 | const assert = require('assert'); 22 | const sleep = require('mz-modules/sleep'); 23 | const NacosNamingClient = require('../../lib/naming/client'); 24 | 25 | const logger = console; 26 | 27 | const serviceName = 'nodejs.test.' + process.versions.node; 28 | 29 | describe('test/naming/client.test.js', () => { 30 | let client; 31 | before(async function() { 32 | client = new NacosNamingClient({ 33 | logger, 34 | serverList: '127.0.0.1:8848', 35 | }); 36 | await client.ready(); 37 | }); 38 | afterEach(mm.restore); 39 | after(async () => { 40 | await client.close(); 41 | }); 42 | 43 | it('should registerInstance & deregisterInstance ok', async function() { 44 | client.subscribe({ 45 | serviceName, 46 | clusters: 'NODEJS', 47 | }, hosts => { 48 | console.log(hosts); 49 | client.emit('update', hosts); 50 | }); 51 | 52 | const instance_1 = { 53 | ip: '1.1.1.1', 54 | port: 8080, 55 | clusterName: 'NODEJS', 56 | }; 57 | const instance_2 = { 58 | ip: '2.2.2.2', 59 | port: 8080, 60 | clusterName: 'NODEJS', 61 | }; 62 | 63 | client.registerInstance(serviceName, instance_1); 64 | client.registerInstance(serviceName, instance_2); 65 | 66 | let hosts = []; 67 | 68 | while (hosts.length !== 2) { 69 | hosts = await client.await('update'); 70 | } 71 | 72 | assert(hosts.find(host => host.ip === '1.1.1.1' && host.port === 8080)); 73 | assert(hosts.find(host => host.ip === '2.2.2.2' && host.port === 8080)); 74 | 75 | client.deregisterInstance(serviceName, instance_1); 76 | 77 | while (hosts.length !== 1) { 78 | hosts = await client.await('update'); 79 | } 80 | 81 | assert(hosts.find(host => host.ip === '2.2.2.2' && host.port === 8080)); 82 | 83 | client.deregisterInstance(serviceName, instance_2); 84 | 85 | while (hosts.length !== 0) { 86 | hosts = await client.await('update'); 87 | } 88 | 89 | client.unSubscribe({ 90 | serviceName, 91 | clusters: 'NODEJS', 92 | }); 93 | }); 94 | 95 | it('should registerInstance & deregisterInstance with default cluster ok', async function() { 96 | client.subscribe(serviceName, hosts => { 97 | console.log(hosts); 98 | client.emit('update', hosts); 99 | }); 100 | 101 | const instance_1 = { 102 | ip: '1.1.1.1', 103 | port: 8080, 104 | }; 105 | const instance_2 = { 106 | ip: '2.2.2.2', 107 | port: 8080, 108 | }; 109 | 110 | client.registerInstance(serviceName, instance_1); 111 | client.registerInstance(serviceName, instance_2); 112 | 113 | let hosts = []; 114 | 115 | while (hosts.length !== 2) { 116 | hosts = await client.await('update'); 117 | } 118 | 119 | assert(hosts.find(host => host.ip === '1.1.1.1' && host.port === 8080)); 120 | assert(hosts.find(host => host.ip === '2.2.2.2' && host.port === 8080)); 121 | 122 | client.deregisterInstance(serviceName, instance_1); 123 | 124 | while (hosts.length !== 1) { 125 | hosts = await client.await('update'); 126 | } 127 | 128 | assert(hosts.find(host => host.ip === '2.2.2.2' && host.port === 8080)); 129 | 130 | client.deregisterInstance(serviceName, instance_2); 131 | 132 | while (hosts.length !== 0) { 133 | hosts = await client.await('update'); 134 | } 135 | 136 | client.unSubscribe(serviceName); 137 | }); 138 | 139 | it('should support registerInstance(serviceName, instance)', async function() { 140 | const serviceName = 'nodejs.test.xxx.' + process.versions.node; 141 | await client.registerInstance(serviceName, { 142 | ip: '1.1.1.1', 143 | port: 8888, 144 | metadata: { 145 | xxx: 'yyy', 146 | foo: 'bar', 147 | }, 148 | }); 149 | await sleep(2000); 150 | let hosts = await client.getAllInstances(serviceName); 151 | console.log(hosts); 152 | const host = hosts.find(host => { 153 | return host.ip === '1.1.1.1' && host.port === 8888; 154 | }); 155 | assert.deepEqual(host.metadata, { foo: 'bar', xxx: 'yyy' }); 156 | 157 | hosts = null; 158 | client.subscribe(serviceName, val => { 159 | hosts = val; 160 | }); 161 | 162 | await sleep(10000); 163 | assert(hosts && hosts.length === 1); 164 | assert(hosts[0].ip === '1.1.1.1'); 165 | }); 166 | 167 | it('should getAllInstances ok', async function() { 168 | const serviceName = 'nodejs.test.getAllInstances' + process.versions.node; 169 | const instance_1 = { 170 | ip: '1.1.1.1', 171 | port: 8080, 172 | clusterName: 'NODEJS', 173 | }; 174 | const instance_2 = { 175 | ip: '2.2.2.2', 176 | port: 8080, 177 | clusterName: 'OTHERS', 178 | }; 179 | await client.registerInstance(serviceName, instance_1); 180 | await client.registerInstance(serviceName, instance_2); 181 | await sleep(2000); 182 | 183 | let hosts = await client.getAllInstances(serviceName); 184 | assert(hosts.length === 2); 185 | assert(hosts.find(host => host.ip === '1.1.1.1' && host.port === 8080)); 186 | assert(hosts.find(host => host.ip === '2.2.2.2' && host.port === 8080)); 187 | 188 | hosts = await client.getAllInstances(serviceName, 'DEFAULT_GROUP', 'NODEJS,OTHERS'); 189 | assert(hosts.length === 2); 190 | assert(hosts.find(host => host.ip === '1.1.1.1' && host.port === 8080)); 191 | assert(hosts.find(host => host.ip === '2.2.2.2' && host.port === 8080)); 192 | 193 | hosts = await client.getAllInstances(serviceName, 'DEFAULT_GROUP', 'NODEJS,OTHERS', false); 194 | assert(hosts.length === 2); 195 | assert(hosts.find(host => host.ip === '1.1.1.1' && host.port === 8080)); 196 | assert(hosts.find(host => host.ip === '2.2.2.2' && host.port === 8080)); 197 | 198 | hosts = await client.getAllInstances(serviceName, 'DEFAULT_GROUP', 'NODEJS'); 199 | assert(hosts.length === 1); 200 | assert(hosts.find(host => host.ip === '1.1.1.1' && host.port === 8080)); 201 | 202 | await client.deregisterInstance(serviceName, instance_1); 203 | await client.deregisterInstance(serviceName, instance_2); 204 | 205 | await sleep(2000); 206 | 207 | hosts = await client.getAllInstances(serviceName); 208 | assert(hosts.length === 0); 209 | }); 210 | 211 | it('should selectInstances ok', async function() { 212 | const serviceName = 'nodejs.test.selectInstance' + process.versions.node; 213 | const instance_1 = { 214 | ip: '11.11.11.11', 215 | port: 8080, 216 | healthy: true, 217 | clusterName: 'NODEJS', 218 | ephemeral: false, 219 | }; 220 | const instance_2 = { 221 | ip: '22.22.22.22', 222 | port: 8080, 223 | healthy: false, 224 | clusterName: 'OTHERS', 225 | ephemeral: false, 226 | }; 227 | const instance_3 = { 228 | ip: '33.33.33.33', 229 | port: 8080, 230 | healthy: true, 231 | clusterName: 'OTHERS', 232 | ephemeral: false, 233 | }; 234 | await client.registerInstance(serviceName, instance_1); 235 | await client.registerInstance(serviceName, instance_2); 236 | await client.registerInstance(serviceName, instance_3); 237 | await sleep(2000); 238 | 239 | let hosts = await client.selectInstances(serviceName); 240 | assert(hosts.length === 2); 241 | assert(hosts.find(host => host.ip === '11.11.11.11' && host.port === 8080)); 242 | assert(hosts.find(host => host.ip === '33.33.33.33' && host.port === 8080)); 243 | 244 | hosts = await client.selectInstances(serviceName, 'DEFAULT_GROUP', 'NODEJS,OTHERS'); 245 | assert(hosts.length === 2); 246 | assert(hosts.find(host => host.ip === '11.11.11.11' && host.port === 8080)); 247 | assert(hosts.find(host => host.ip === '33.33.33.33' && host.port === 8080)); 248 | 249 | hosts = await client.selectInstances(serviceName, 'DEFAULT_GROUP', 'NODEJS,OTHERS', false, false); 250 | assert(hosts.length === 1); 251 | // assert(hosts.find(host => host.ip === '1.1.1.1' && host.port === 8080)); 252 | assert(hosts.find(host => host.ip === '22.22.22.22' && host.port === 8080)); 253 | 254 | hosts = await client.selectInstances(serviceName, 'DEFAULT_GROUP', 'OTHERS'); 255 | assert(hosts.length === 1); 256 | assert(hosts.find(host => host.ip === '33.33.33.33' && host.port === 8080)); 257 | 258 | hosts = await client.selectInstances(serviceName, 'DEFAULT_GROUP', 'OTHERS', false); 259 | assert(hosts.length === 1); 260 | assert(hosts.find(host => host.ip === '22.22.22.22' && host.port === 8080)); 261 | 262 | await client.deregisterInstance(serviceName, instance_1); 263 | await client.deregisterInstance(serviceName, instance_2); 264 | await client.deregisterInstance(serviceName, instance_3); 265 | 266 | await sleep(2000); 267 | 268 | hosts = await client.selectInstances(serviceName); 269 | assert(hosts.length === 0); 270 | }); 271 | 272 | it('should getServerStatus ok', async function() { 273 | let status = await client.getServerStatus(); 274 | assert(status === 'UP'); 275 | mm(client._serverProxy, 'serverHealthy', () => false); 276 | status = await client.getServerStatus(); 277 | assert(status === 'DOWN'); 278 | }); 279 | }); 280 | -------------------------------------------------------------------------------- /packages/nacos-naming/test/naming/host_reactor.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const mm = require('mm'); 21 | const assert = require('assert'); 22 | const NameProxy = require('../../lib/naming/proxy'); 23 | const Instance = require('../../lib/naming/instance'); 24 | const HostReactor = require('../../lib/naming/host_reactor'); 25 | 26 | const logger = console; 27 | const serviceName = 'nodejs.test.' + process.versions.node; 28 | const groupName = 'DEFAULT_GROUP'; 29 | const serviceNameWithGroup = groupName + '@@' + serviceName; 30 | 31 | describe('test/naming/host_reactor.test.js', () => { 32 | let serverProxy; 33 | before(async () => { 34 | serverProxy = new NameProxy({ 35 | logger, 36 | serverList: '127.0.0.1:8848', 37 | }); 38 | await serverProxy.ready(); 39 | }); 40 | after(async () => { 41 | await serverProxy.close(); 42 | }); 43 | afterEach(mm.restore); 44 | 45 | it('should ok', async function() { 46 | const hostReactor = new HostReactor({ 47 | logger, 48 | serverProxy, 49 | }); 50 | await hostReactor.ready(); 51 | 52 | hostReactor.subscribe({ 53 | serviceName: serviceNameWithGroup, 54 | clusters: 'NODEJS', 55 | }, hosts => { 56 | hostReactor.emit('update', hosts); 57 | }); 58 | 59 | const instance = new Instance({ 60 | ip: '1.1.1.1', 61 | port: 8080, 62 | serviceName, 63 | clusterName: 'NODEJS', 64 | weight: 1.0, 65 | valid: true, 66 | enabled: true, 67 | }); 68 | serverProxy.registerService(serviceNameWithGroup, groupName, instance); 69 | 70 | let hosts = []; 71 | 72 | while (hosts.length !== 1) { 73 | hosts = await hostReactor.await('update'); 74 | } 75 | assert(hosts.some(host => host.ip === '1.1.1.1' && host.port === 8080)); 76 | 77 | const key = serviceNameWithGroup + '@@NODEJS'; 78 | console.log(hostReactor.getServiceInfoMap); 79 | assert(hostReactor.getServiceInfoMap && hostReactor.getServiceInfoMap[key]); 80 | 81 | hostReactor.processServiceJSON(JSON.stringify({ 82 | metadata: {}, 83 | dom: serviceNameWithGroup, 84 | cacheMillis: 10000, 85 | useSpecifiedURL: false, 86 | hosts: null, 87 | name: serviceNameWithGroup, 88 | checksum: 'cc4e0ff13773c6d443d9ba0532b32810', 89 | lastRefTime: 1556603044852, 90 | env: '', 91 | clusters: 'NODEJS', 92 | })); 93 | assert(hostReactor.getServiceInfoMap[key].hosts.length === 1); 94 | 95 | hostReactor.processServiceJSON(JSON.stringify({ 96 | metadata: {}, 97 | dom: serviceNameWithGroup, 98 | cacheMillis: 10000, 99 | useSpecifiedURL: false, 100 | hosts: hostReactor.getServiceInfoMap[key].hosts, 101 | name: serviceNameWithGroup, 102 | checksum: 'cc4e0ff13773c6d443d9ba0532b32811', 103 | lastRefTime: 1556603044852, 104 | env: '', 105 | clusters: 'NODEJS', 106 | })); 107 | assert(hostReactor.getServiceInfoMap[key].hosts.length === 1); 108 | 109 | hostReactor.processServiceJSON(JSON.stringify({ 110 | metadata: {}, 111 | dom: serviceNameWithGroup, 112 | cacheMillis: 10000, 113 | useSpecifiedURL: false, 114 | hosts: hostReactor.getServiceInfoMap[key].hosts.map(host => { 115 | return Object.assign({}, host, { enabled: false }); 116 | }), 117 | name: serviceNameWithGroup, 118 | checksum: 'cc4e0ff13773c6d443d9ba0532b32812', 119 | lastRefTime: 1556603044852, 120 | env: '', 121 | clusters: 'NODEJS', 122 | })); 123 | assert(hostReactor.getServiceInfoMap[key].hosts.length === 1); 124 | assert(!hostReactor.getServiceInfoMap[key].hosts[0].enabled); 125 | 126 | hostReactor.processServiceJSON(JSON.stringify({ 127 | metadata: {}, 128 | dom: 'mock_dom', 129 | cacheMillis: 10000, 130 | useSpecifiedURL: false, 131 | hosts: hostReactor.getServiceInfoMap[key].hosts, 132 | name: 'mock_dom', 133 | checksum: 'cc4e0ff13773c6d443d9ba0532b32813', 134 | lastRefTime: 1556603044852, 135 | env: '', 136 | clusters: 'NODEJS', 137 | })); 138 | assert(hostReactor.getServiceInfoMap['mock_dom@@NODEJS']); 139 | 140 | serverProxy.deregisterService(serviceName, instance); 141 | 142 | while (hosts.length !== 0) { 143 | hosts = await hostReactor.await('update'); 144 | } 145 | 146 | const listener = hosts => { 147 | assert(hosts.length === 0); 148 | }; 149 | hostReactor.subscribe({ 150 | serviceName: serviceNameWithGroup, 151 | clusters: 'NODEJS', 152 | }, listener); 153 | hostReactor.unSubscribe({ 154 | serviceName: serviceNameWithGroup, 155 | clusters: 'NODEJS', 156 | }, listener); 157 | 158 | await hostReactor.close(); 159 | }); 160 | 161 | it('should updateServiceNow ok', async () => { 162 | const hostReactor = new HostReactor({ 163 | logger, 164 | serverProxy, 165 | }); 166 | await hostReactor.ready(); 167 | 168 | const arr = await Promise.all([ 169 | hostReactor.getServiceInfo(serviceNameWithGroup, 'NODEJS'), 170 | hostReactor.getServiceInfo(serviceNameWithGroup, 'NODEJS'), 171 | ]); 172 | assert(arr && arr.length === 2); 173 | assert(arr.every(item => !!item)); 174 | 175 | await hostReactor.close(); 176 | }); 177 | 178 | it('should emit error if serverProxy.queryList failed', async () => { 179 | mm.error(serverProxy, 'queryList', 'mock error'); 180 | 181 | const hostReactor = new HostReactor({ 182 | logger, 183 | serverProxy, 184 | }); 185 | await hostReactor.ready(); 186 | 187 | hostReactor.updateServiceNow(serviceNameWithGroup, 'NODEJS'); 188 | 189 | await assert.rejects(async () => { 190 | await hostReactor.await('error'); 191 | }, /failed to update serviceName/); 192 | 193 | hostReactor.refreshOnly(serviceNameWithGroup, 'NODEJS'); 194 | 195 | await assert.rejects(async () => { 196 | await hostReactor.await('error'); 197 | }, /failed to update serviceName/); 198 | 199 | await hostReactor.close(); 200 | }); 201 | }); 202 | -------------------------------------------------------------------------------- /packages/nacos-naming/test/naming/instance.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const assert = require('assert'); 21 | const Instance = require('../../lib/naming/instance'); 22 | 23 | describe('test/naming/instance.test.js', () => { 24 | it('should new instance ok', () => { 25 | const instance1 = new Instance({ 26 | ip: '1.1.1.1', 27 | port: 8888, 28 | valid: true, 29 | enabled: false, 30 | }); 31 | assert(instance1.toString() === '{"ip":"1.1.1.1","port":8888,"weight":1,"healthy":true,"enabled":false,"ephemeral":true,"clusterName":"DEFAULT","metadata":{}}'); 32 | assert(instance1.toInetAddr() === '1.1.1.1:8888'); 33 | 34 | const instance2 = new Instance({ 35 | ip: '1.1.1.1', 36 | port: 8888, 37 | healthy: true, 38 | enabled: false, 39 | }); 40 | assert(instance2.toString() === '{"ip":"1.1.1.1","port":8888,"weight":1,"healthy":true,"enabled":false,"ephemeral":true,"clusterName":"DEFAULT","metadata":{}}'); 41 | assert(instance2.toInetAddr() === '1.1.1.1:8888'); 42 | 43 | assert(instance1.equal(instance2)); 44 | 45 | const instance3 = new Instance({ 46 | ip: '1.1.1.1', 47 | port: 8888, 48 | }); 49 | assert(instance3.toString() === '{"ip":"1.1.1.1","port":8888,"weight":1,"healthy":true,"enabled":true,"ephemeral":true,"clusterName":"DEFAULT","metadata":{}}'); 50 | assert(instance3.toInetAddr() === '1.1.1.1:8888'); 51 | 52 | assert(!instance1.equal(instance3)); 53 | assert(!instance3.equal(instance2)); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/nacos-naming/test/naming/proxy.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const mm = require('mm'); 21 | const http = require('http'); 22 | const assert = require('assert'); 23 | const sleep = require('mz-modules/sleep'); 24 | const NameProxy = require('../../lib/naming/proxy'); 25 | const Instance = require('../../lib/naming/instance'); 26 | 27 | const logger = console; 28 | const serviceName = 'nodejs.test.' + process.versions.node; 29 | 30 | describe('test/naming/proxy.test.js', () => { 31 | afterEach(mm.restore); 32 | 33 | it('should ok', async function() { 34 | const proxy = new NameProxy({ 35 | logger, 36 | serverList: '127.0.0.1', 37 | }); 38 | await proxy.ready(); 39 | const groupName = 'DEFAULT_GROUP'; 40 | const instance = new Instance({ 41 | ip: '1.1.1.1', 42 | port: 8080, 43 | clusterName: 'NODEJS', 44 | weight: 1.0, 45 | metadata: {}, 46 | serviceName, 47 | }); 48 | let result = await proxy.registerService(serviceName, groupName, instance); 49 | assert(result === 'ok'); 50 | await sleep(1000); 51 | 52 | let jsonStr = await proxy.queryList(serviceName, 'NODEJS', '', 'false'); 53 | let serviceInfo = JSON.parse(jsonStr); 54 | 55 | assert(serviceInfo && serviceInfo.dom === 'DEFAULT_GROUP@@' + serviceName); 56 | assert(serviceInfo.hosts && serviceInfo.hosts.length === 1); 57 | assert(serviceInfo.hosts[0].ip === '1.1.1.1'); 58 | assert(serviceInfo.hosts[0].port === 8080); 59 | 60 | result = await proxy.deregisterService(serviceName, instance); 61 | assert(result === 'ok'); 62 | 63 | jsonStr = await proxy.queryList(serviceName, 'NODEJS', '', 'false'); 64 | serviceInfo = JSON.parse(jsonStr); 65 | 66 | assert(serviceInfo && serviceInfo.dom === 'DEFAULT_GROUP@@' + serviceName); 67 | assert(serviceInfo.hosts && serviceInfo.hosts.length === 0); 68 | 69 | await proxy.close(); 70 | }); 71 | 72 | it('should serverHealthy ok', async function() { 73 | const proxy = new NameProxy({ 74 | logger, 75 | endpoint: '127.0.0.1:8849', 76 | serverList: '127.0.0.1:8848', 77 | }); 78 | await proxy.ready(); 79 | 80 | let isHealthy = await proxy.serverHealthy(); 81 | assert(isHealthy); 82 | 83 | mm.http.request(/\/nacos\/v1\/ns\/operator\/metrics/, '{"status": "DOWN"}', { 84 | statusCode: 200, 85 | }); 86 | 87 | isHealthy = await proxy.serverHealthy(); 88 | assert(!isHealthy); 89 | 90 | mm.http.request(/\/nacos\/v1\/ns\/operator\/metrics/, '', { 91 | statusCode: 304, 92 | }); 93 | 94 | isHealthy = await proxy.serverHealthy(); 95 | assert(!isHealthy); 96 | 97 | mm.http.request(/\/nacos\/v1\/ns\/operator\/metrics/, '', { 98 | statusCode: 500, 99 | }); 100 | 101 | isHealthy = await proxy.serverHealthy(); 102 | assert(!isHealthy); 103 | 104 | await proxy.close(); 105 | }); 106 | 107 | it('should failed if no server available', async function() { 108 | const proxy = new NameProxy({ 109 | logger, 110 | serverList: '', 111 | }); 112 | await proxy.ready(); 113 | 114 | const isHealthy = await proxy.serverHealthy(); 115 | assert(!isHealthy); 116 | 117 | await proxy.close(); 118 | }); 119 | 120 | it('should support naocsDomain', async function() { 121 | const proxy = new NameProxy({ 122 | logger, 123 | serverList: '', 124 | }); 125 | await proxy.ready(); 126 | proxy.nacosDomain = '127.0.0.1:8848'; 127 | 128 | let isHealthy = await proxy.serverHealthy(); 129 | assert(isHealthy); 130 | 131 | mm.http.request(/\/nacos\/v1\/ns\/operator\/metrics/, '', { 132 | statusCode: 500, 133 | }); 134 | 135 | isHealthy = await proxy.serverHealthy(); 136 | assert(!isHealthy); 137 | 138 | await proxy.close(); 139 | }); 140 | 141 | it('should sendBeat ok', async () => { 142 | const proxy = new NameProxy({ 143 | logger, 144 | serverList: '127.0.0.1:8848', 145 | }); 146 | await proxy.ready(); 147 | 148 | const beatInfo = { 149 | serviceName: 'DEFAULT_GROUP@@' + serviceName, 150 | ip: '1.1.1.1', 151 | port: 8080, 152 | cluster: 'NODEJS', 153 | weight: 1, 154 | metadata: {}, 155 | scheduled: false, 156 | }; 157 | let result = await proxy.sendBeat(beatInfo); 158 | console.log(result); 159 | assert(typeof result === 'number' && result > 0); 160 | 161 | mm.error(proxy, '_reqAPI', 'mock error'); 162 | 163 | result = await proxy.sendBeat(beatInfo); 164 | assert(result === 0); 165 | 166 | await proxy.close(); 167 | }); 168 | 169 | it('should getServiceList ok', async () => { 170 | const proxy = new NameProxy({ 171 | logger, 172 | serverList: '127.0.0.1:8848', 173 | }); 174 | await proxy.ready(); 175 | 176 | let data = await proxy.getServiceList(0, 10, 'DEFAULT_GROUP'); 177 | console.log(data); 178 | 179 | const groupName = 'DEFAULT_GROUP'; 180 | const instance = new Instance({ 181 | ip: '1.1.1.1', 182 | port: 8080, 183 | clusterName: 'NODEJS', 184 | weight: 1.0, 185 | metadata: {}, 186 | serviceName, 187 | }); 188 | let result = await proxy.registerService(serviceName, groupName, instance); 189 | assert(result === 'ok'); 190 | await sleep(1000); 191 | 192 | data = await proxy.getServiceList(0, 10, 'DEFAULT_GROUP'); 193 | console.log(data); 194 | 195 | result = await proxy.deregisterService(serviceName, instance); 196 | assert(result === 'ok'); 197 | 198 | await proxy.close(); 199 | }); 200 | 201 | describe('endpoint', () => { 202 | let server; 203 | before(done => { 204 | server = http.createServer((req, res) => { 205 | res.writeHead(200, { 'Content-Type': 'application/text' }); 206 | res.end('127.0.0.1:8848'); 207 | }); 208 | server.listen(8849, done); 209 | }); 210 | after(done => { 211 | server.once('close', done); 212 | server.close(); 213 | }); 214 | 215 | it('should get serverList from endpoint', async () => { 216 | const proxy = new NameProxy({ 217 | logger, 218 | endpoint: '127.0.0.1:8849', 219 | vipSrvRefInterMillis: 5000, 220 | }); 221 | await proxy.ready(); 222 | 223 | assert(proxy.serverList && proxy.serverList.length === 0); 224 | assert(proxy.serversFromEndpoint && proxy.serversFromEndpoint.length === 1); 225 | 226 | assert(proxy.lastSrvRefTime > 0); 227 | 228 | const isHealthy = await proxy.serverHealthy(); 229 | assert(isHealthy); 230 | 231 | await sleep(6000); 232 | 233 | const lastSrvRefTime = proxy.lastSrvRefTime; 234 | assert(Date.now() - lastSrvRefTime < 5000); 235 | await proxy._refreshSrvIfNeed(); 236 | assert(proxy.lastSrvRefTime === lastSrvRefTime); 237 | 238 | await proxy.close(); 239 | }); 240 | 241 | it('should not healthy', async () => { 242 | const proxy = new NameProxy({ 243 | logger, 244 | endpoint: 'unknown.com', 245 | }); 246 | await proxy.ready(); 247 | 248 | const isHealthy = await proxy.serverHealthy(); 249 | assert(!isHealthy); 250 | 251 | await proxy.close(); 252 | }); 253 | }); 254 | }); 255 | -------------------------------------------------------------------------------- /packages/nacos-naming/test/naming/push_receiver.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const dgram = require('dgram'); 21 | const assert = require('assert'); 22 | const sleep = require('mz-modules/sleep'); 23 | const awaitEvent = require('await-event'); 24 | const PushReceiver = require('../../lib/naming/push_receiver'); 25 | 26 | const logger = console; 27 | 28 | describe('test/naming/push_receiver.test.js', () => { 29 | it('should push message ok', async function() { 30 | const pushReceiver = new PushReceiver({ 31 | logger, 32 | processServiceJSON(json) { 33 | const data = JSON.parse(json); 34 | assert.deepEqual(data, { 35 | cacheMillis: 20000, 36 | checksum: 'd10785d4cc80a8319578b92c4164902b1486619344219', 37 | clusters: '', 38 | dom: 'xxx', 39 | env: '', 40 | hosts: [{ 41 | appUseType: '', 42 | doubleWeight: 1, 43 | hostname: 'xxx', 44 | ip: '1.1.1.1', 45 | marked: false, 46 | port: 80, 47 | site: 'xxx', 48 | unit: 'CENTER', 49 | valid: true, 50 | weight: 1, 51 | }], 52 | lastRefTime: 1486619344219, 53 | useSpecifiedURL: true, 54 | }); 55 | }, 56 | getServiceInfoMap() { 57 | return { foo: 'bar' }; 58 | }, 59 | }); 60 | await pushReceiver.ready(); 61 | assert(pushReceiver.udpPort); 62 | 63 | const client = dgram.createSocket('udp4'); 64 | 65 | const domMsg = '{"data":"{\\"cacheMillis\\":20000,\\"checksum\\":\\"d10785d4cc80a8319578b92c4164902b1486619344219\\",\\"clusters\\":\\"\\",\\"dom\\":\\"xxx\\",\\"env\\":\\"\\",\\"hosts\\":[{\\"appUseType\\":\\"\\",\\"doubleWeight\\":1,\\"hostname\\":\\"xxx\\",\\"ip\\":\\"1.1.1.1\\",\\"marked\\":false,\\"port\\":80,\\"site\\":\\"xxx\\",\\"unit\\":\\"CENTER\\",\\"valid\\":true,\\"weight\\":1}],\\"lastRefTime\\":1486619344219,\\"useSpecifiedURL\\":true}","lastRefTime":26084107962357333,"type":"dom"}'; 66 | client.send(domMsg, pushReceiver.udpPort, 'localhost'); 67 | 68 | let msg = await awaitEvent(client, 'message'); 69 | assert(msg.toString() === '{"type":"push-ack","lastRefTime":26084107962357332,"data":""}'); 70 | 71 | const dumpMsg = '{"data":"","lastRefTime":26084107962357334,"type":"dump"}'; 72 | client.send(dumpMsg, pushReceiver.udpPort, 'localhost'); 73 | msg = await awaitEvent(client, 'message'); 74 | assert(msg.toString() === '{"type":"dump-ack","lastRefTime":26084107962357336,"data":"{\\"foo\\":\\"bar\\"}"}'); 75 | 76 | const unknowMsg = '{"data":"","lastRefTime":26084107962357335,"type":"unknow"}'; 77 | client.send(unknowMsg, pushReceiver.udpPort, 'localhost'); 78 | msg = await awaitEvent(client, 'message'); 79 | assert(msg.toString() === '{"type":"unknown-ack","lastRefTime":26084107962357336,"data":""}'); 80 | 81 | const errorMsg = '{'; 82 | client.send(errorMsg, pushReceiver.udpPort, 'localhost'); 83 | 84 | try { 85 | await pushReceiver.await('error'); 86 | } catch (err) { 87 | console.log(err); 88 | } 89 | 90 | await pushReceiver.close(); 91 | }); 92 | 93 | it('should auto recover from exception', async function() { 94 | const pushReceiver = new PushReceiver({ 95 | logger, 96 | processServiceJSON() {}, 97 | getServiceInfoMap() { return null; }, 98 | }); 99 | await pushReceiver.ready(); 100 | const udpPort1 = pushReceiver.udpPort; 101 | assert(udpPort1); 102 | 103 | pushReceiver._server.emit('error', new Error('mock error')); 104 | 105 | await sleep(100); 106 | 107 | const udpPort2 = pushReceiver.udpPort; 108 | assert(udpPort2); 109 | assert(udpPort1 !== udpPort2); 110 | 111 | await pushReceiver.close(); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /packages/nacos-naming/test/naming/service_info.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const mm = require('mm'); 21 | const assert = require('assert'); 22 | const ServiceInfo = require('../../lib/naming/service_info'); 23 | 24 | describe('test/naming/service_info.test.js', () => { 25 | afterEach(mm.restore); 26 | 27 | it('should new ServiceInfo', () => { 28 | const serviceInfo = new ServiceInfo({ 29 | name: 'xxx', 30 | clusters: 'clusters', 31 | allIPs: true, 32 | hosts: [{ 33 | instanceId: 'instanceId', 34 | ip: '1.1.1.1', 35 | port: 80, 36 | weight: 1.0, 37 | valid: true, 38 | enabled: true, 39 | ephemeral: true, 40 | metadata: {}, 41 | }], 42 | }); 43 | assert(serviceInfo.ipCount === 1); 44 | assert(serviceInfo.isValid); 45 | assert(serviceInfo.getKey() === 'xxx@@clusters'); 46 | 47 | mm(serviceInfo, 'isAllIPs', false); 48 | assert(serviceInfo.getKey() === 'xxx@@clusters'); 49 | 50 | mm(serviceInfo, 'env', 'test'); 51 | assert(serviceInfo.getKey() === 'xxx@@clusters'); 52 | 53 | mm(serviceInfo, 'isAllIPs', true); 54 | assert(serviceInfo.getKey() === 'xxx@@clusters'); 55 | 56 | mm(serviceInfo, 'clusters', null); 57 | assert(serviceInfo.getKey() === 'xxx'); 58 | 59 | mm(serviceInfo, 'isAllIPs', false); 60 | assert(serviceInfo.getKey() === 'xxx'); 61 | 62 | mm(serviceInfo, 'isAllIPs', true); 63 | assert(serviceInfo.getKey() === 'xxx'); 64 | assert(serviceInfo.toString() === 'xxx'); 65 | }); 66 | 67 | it('should parse from string', () => { 68 | let data = ServiceInfo.fromKey('DEFAULT_GROUP@@xxx'); 69 | assert(data.name === 'xxx'); 70 | assert(data.groupName === 'DEFAULT_GROUP'); 71 | assert(!data.clusters); 72 | assert(!data.isAllIPs); 73 | 74 | data = ServiceInfo.fromKey('DEFAULT_GROUP@@xxx@@clusters'); 75 | assert(data.name === 'xxx'); 76 | assert(data.clusters === 'clusters'); 77 | assert(!data.isAllIPs); 78 | assert(data.groupName === 'DEFAULT_GROUP'); 79 | 80 | data = ServiceInfo.fromKey('xxx'); 81 | assert(!data.name); 82 | assert(!data.clusters); 83 | assert(!data.isAllIPs); 84 | assert(!data.groupName); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /packages/nacos-naming/test/util/index.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const zlib = require('zlib'); 21 | const assert = require('assert'); 22 | const util = require('../../lib/util'); 23 | 24 | describe('test/util/index.test.js', () => { 25 | it('should tryDecompress ok', () => { 26 | const buf = Buffer.from('hello world'); 27 | assert.deepEqual(util.tryDecompress(buf), buf); 28 | 29 | const zipped = zlib.gzipSync(buf); 30 | assert.deepEqual(util.tryDecompress(zipped), buf); 31 | }); 32 | 33 | it('should getGroupedName ok', () => { 34 | const serviceWithGroupName = util.getGroupedName('serviceName', 'groupName'); 35 | assert(serviceWithGroupName === 'groupName@@serviceName'); 36 | }); 37 | 38 | it('should getServiceName ok', () => { 39 | assert(util.getServiceName('groupName@@serviceName') === 'serviceName'); 40 | assert(util.getServiceName('serviceName') === 'serviceName'); 41 | }); 42 | 43 | it('should getGroupName ok', () => { 44 | assert(util.getGroupName('groupName@@serviceName') === 'groupName'); 45 | assert(util.getGroupName('serviceName') === 'DEFAULT_GROUP'); 46 | }); 47 | 48 | it('should sign ok', () => { 49 | const result = util.sign('1556606455782@@nodejs.test', 'xxxxxx'); 50 | assert(result === 'hhmW6gWCqR0g8dctGZXQclYomYg='); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/nacos/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.0.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v2.0.0...v2.0.1) (2021-01-26) 7 | 8 | **Note:** Version bump only for package nacos 9 | 10 | 11 | 12 | 13 | 14 | ## [1.1.2](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.1.1...v1.1.2) (2019-03-31) 15 | 16 | **Note:** Version bump only for package nacos 17 | 18 | 19 | 20 | 21 | 22 | ## [1.1.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.1.0...v1.1.1) (2019-01-17) 23 | 24 | **Note:** Version bump only for package nacos 25 | 26 | 27 | 28 | 29 | 30 | # [1.1.0](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.0.2...v1.1.0) (2019-01-15) 31 | 32 | 33 | ### Features 34 | 35 | * support registerInstance(serviceName, instance) ([32d2670](https://github.com/nacos-group/nacos-sdk-nodejs/commit/32d2670)) 36 | 37 | 38 | 39 | 40 | 41 | ## [1.0.2](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.0.1...v1.0.2) (2018-12-12) 42 | 43 | **Note:** Version bump only for package nacos 44 | 45 | 46 | 47 | 48 | 49 | ## [1.0.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v1.0.0...v1.0.1) (2018-12-10) 50 | 51 | **Note:** Version bump only for package nacos 52 | 53 | 54 | 55 | 56 | 57 | ## [0.2.1](https://github.com/nacos-group/nacos-sdk-nodejs/compare/v0.2.0...v0.2.1) (2018-12-03) 58 | 59 | **Note:** Version bump only for package nacos 60 | 61 | 62 | 63 | 64 | 65 | # 0.2.0 (2018-12-03) 66 | 67 | 68 | ### Features 69 | 70 | * export in one package ([fa747bc](https://github.com/nacos-group/nacos-sdk-nodejs/commit/fa747bc)) 71 | -------------------------------------------------------------------------------- /packages/nacos/README.md: -------------------------------------------------------------------------------- 1 | # nacos-sdk-nodejs 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![build status][travis-image]][travis-url] 5 | [![David deps][david-image]][david-url] 6 | [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lernajs.io/) 7 | 8 | [npm-image]: https://img.shields.io/npm/v/nacos.svg?style=flat-square 9 | [npm-url]: https://npmjs.org/package/nacos 10 | [travis-image]: https://img.shields.io/travis/nacos-group/nacos-sdk-nodejs.svg?style=flat-square 11 | [travis-url]: https://travis-ci.org/nacos-group/nacos-sdk-nodejs 12 | [david-image]: https://img.shields.io/david/nacos-group/nacos-sdk-nodejs.svg?style=flat-square 13 | [david-url]: https://david-dm.org/nacos-group/nacos-sdk-nodejs 14 | 15 | 16 | [Nacos](https://nacos.io/en-us/) Node.js SDK 17 | 18 | ## Install 19 | 20 | ```bash 21 | npm install nacos --save 22 | ``` 23 | 24 | ## Version Mapping 25 | 26 | Node.js SDK \ Nacos Server | 0.x.0 | 1.0.0 | 27 | --- | --- | --- | 28 | 1.x | √ | | 29 | 2.x | | √ | 30 | 31 | ## Usage 32 | 33 | ### Service Discovery 34 | 35 | ```js 36 | 'use strict'; 37 | 38 | const NacosNamingClient = require('nacos').NacosNamingClient; 39 | const logger = console; 40 | 41 | const client = new NacosNamingClient({ 42 | logger, 43 | serverList: '127.0.0.1:8848', // replace to real nacos serverList 44 | namespace: 'public', 45 | }); 46 | await client.ready(); 47 | 48 | const serviceName = 'nodejs.test.domain'; 49 | 50 | // registry instance 51 | await client.registerInstance(serviceName, { 52 | ip: '1.1.1.1', 53 | port: 8080, 54 | }); 55 | await client.registerInstance(serviceName, { 56 | ip: '2.2.2.2', 57 | port: 8080, 58 | }); 59 | 60 | // subscribe instance 61 | client.subscribe(serviceName, hosts => { 62 | console.log(hosts); 63 | }); 64 | 65 | // deregister instance 66 | await client.deregisterInstance(serviceName, { 67 | ip: '1.1.1.1', 68 | port: 8080, 69 | }); 70 | ``` 71 | 72 | ### Config Service 73 | 74 | ```js 75 | import {NacosConfigClient} from 'nacos'; // ts 76 | const NacosConfigClient = require('nacos').NacosConfigClient; // js 77 | 78 | // for find address mode 79 | const configClient = new NacosConfigClient({ 80 | endpoint: 'acm.aliyun.com', 81 | namespace: '***************', 82 | accessKey: '***************', 83 | secretKey: '***************', 84 | requestTimeout: 6000, 85 | }); 86 | 87 | // for direct mode 88 | const configClient = new NacosConfigClient({ 89 | serverAddr: '127.0.0.1:8848', 90 | }); 91 | 92 | // get config once 93 | const content= await configClient.getConfig('test', 'DEFAULT_GROUP'); 94 | console.log('getConfig = ',content); 95 | 96 | // listen data changed 97 | configClient.subscribe({ 98 | dataId: 'test', 99 | group: 'DEFAULT_GROUP', 100 | }, content => { 101 | console.log(content); 102 | }); 103 | 104 | // publish config 105 | const content= await configClient.publishSingle('test', 'DEFAULT_GROUP', '测试'); 106 | console.log('getConfig = ',content); 107 | 108 | // remove config 109 | await configClient.remove('test', 'DEFAULT_GROUP'); 110 | ``` 111 | 112 | NacosConfigClient options: [ClientOptions](https://github.com/nacos-group/nacos-sdk-nodejs/blob/master/packages/nacos-config/src/interface.ts#L247) 113 | 114 | default value: [ClientOptions default value](https://github.com/nacos-group/nacos-sdk-nodejs/blob/master/packages/nacos-config/src/const.ts#L34) 115 | 116 | ## APIs 117 | 118 | ### Service Discovery 119 | 120 | - `registerInstance(serviceName, instance, [groupName])` Register an instance to service. 121 | - serviceName {String} Service name 122 | - instance {Instance} 123 | - ip {String} IP of instance 124 | - port {Number} Port of instance 125 | - [weight] {Number} weight of the instance, default is 1.0 126 | - [ephemeral] {Boolean} active until the client is alive, default is true 127 | - [clusterName] {String} Virtual cluster name 128 | - [groupName] {String} group name, default is `DEFAULT_GROUP` 129 | - `deregisterInstance(serviceName, ip, port, [cluster])` Delete instance from service. 130 | - serviceName {String} Service name 131 | - instance {Instance} 132 | - ip {String} IP of instance 133 | - port {Number} Port of instance 134 | - [weight] {Number} weight of the instance, default is 1.0 135 | - [ephemeral] {Boolean} active until the client is alive, default is true 136 | - [clusterName] {String} Virtual cluster name 137 | - [groupName] {String} group name, default is `DEFAULT_GROUP` 138 | - `getAllInstances(serviceName, [groupName], [clusters], [subscribe])` Query instance list of service. 139 | - serviceName {String} Service name 140 | - [groupName] {String} group name, default is `DEFAULT_GROUP` 141 | - [clusters] {String} Cluster names 142 | - [subscribe] {Boolean} whether subscribe the service, default is true 143 | - `getServerStatus()` Get the status of nacos server, 'UP' or 'DOWN'. 144 | - `subscribe(info, listener)` Subscribe the instances of the service 145 | - info {Object}|{String} service info, if type is string, it's the serviceName 146 | - listener {Function} the listener function 147 | - `unSubscribe(info, [listener])` Unsubscribe the instances of the service 148 | - info {Object}|{String} service info, if type is string, it's the serviceName 149 | - listener {Function} the listener function, if not provide, will unSubscribe all listeners under this service 150 | 151 | ### Config Service 152 | 153 | - `async function getConfig(dataId, group)` 154 | - {String} dataId - data id 155 | - {String} group - group name 156 | - `async function publishSingle(dataId, group, content)` 157 | - {String} dataId - data id 158 | - {String} group - group name 159 | - {String} content - content you want to publish 160 | - `async function remove(dataId, group)` 161 | - {String} dataId - data id 162 | - {String} group - group name 163 | - `function subscribe(info, listener)` 164 | - {Object} info 165 | - {String} dataId - data id 166 | - {String} group - group name 167 | - {Function} listener - callback handler 168 | - `function unSubscribe(info, [listener])` 169 | - {Object} info 170 | - {String} dataId - data id 171 | - {String} group - group 172 | - {Function} listener - callback handler(optional,remove all listener when it is null) 173 | 174 | ## Questions & Suggestions 175 | 176 | Please let us know how can we help. Do check out [issues](https://github.com/nacos-group/nacos-sdk-nodejs/issues) for bug reports or suggestions first. 177 | 178 | PR is welcome. 179 | 180 | nacos-sdk-nodejs ding group : 44654232 181 | ![image](https://user-images.githubusercontent.com/17695352/172582005-c661e2a0-49fa-425c-bf99-785bb7cd4dc1.png) 182 | 183 | 184 | ## License 185 | 186 | [Apache License V2](LICENSE) 187 | -------------------------------------------------------------------------------- /packages/nacos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nacos", 3 | "version": "2.6.0", 4 | "description": "nacos client main package", 5 | "keywords": [ 6 | "nacos", 7 | "cloud", 8 | "native", 9 | "eggjs", 10 | "midwayjs" 11 | ], 12 | "main": "dist/index.js", 13 | "author": "czy88840616@gmail.com", 14 | "dependencies": { 15 | "nacos-config": "^2.6.0", 16 | "nacos-naming": "^2.6.0" 17 | }, 18 | "devDependencies": { 19 | "@types/mocha": "^5.2.5", 20 | "@types/node": "^10.9.4", 21 | "contributors": "^0.5.1", 22 | "midway-bin": "^0.3.2", 23 | "mm": "^2.4.1", 24 | "pedding": "^1.1.0", 25 | "tslint": "^5.11.0", 26 | "typescript": "^3.2.2" 27 | }, 28 | "engines": { 29 | "node": ">= 8.0.0" 30 | }, 31 | "scripts": { 32 | "autod": "midway-bin autod", 33 | "lint": "tslint . --ext .ts", 34 | "test": "npm run test-local", 35 | "test-local": "TEST_REPORTER=spec midway-bin test --ts", 36 | "cov": "TEST_REPORTER=spec midway-bin cov --ts", 37 | "contributors": "contributors", 38 | "ci": "npm run lint && npm run cov", 39 | "build": "midway-bin build --ts" 40 | }, 41 | "files": [ 42 | "d.ts", 43 | "dist" 44 | ], 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/nacos-group/nacos-sdk-nodejs.git" 48 | }, 49 | "license": "Apache", 50 | "bugs": { 51 | "url": "https://github.com/nacos-group/nacos-sdk-nodejs/issues" 52 | }, 53 | "homepage": "https://github.com/nacos-group/nacos-sdk-nodejs#readme" 54 | } 55 | -------------------------------------------------------------------------------- /packages/nacos/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | export * from 'nacos-config'; 19 | export * from 'nacos-naming'; 20 | -------------------------------------------------------------------------------- /packages/nacos/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "experimentalDecorators": true, 7 | "noImplicitThis": true, 8 | "noUnusedLocals": true, 9 | "stripInternal": true, 10 | "pretty": true, 11 | "declaration": true, 12 | "outDir": "dist", 13 | "lib": ["es2017"], 14 | "sourceMap": true 15 | }, 16 | "exclude": [ 17 | "dist", 18 | "node_modules", 19 | "test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /scripts/cov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cwd=`pwd` 4 | lerna run cov 5 | rm -rf "${cwd}/.nyc_output" || true 6 | mkdir "${cwd}/.nyc_output" 7 | # cp -r ./packages/*/.nyc_output/* $cwd/.nyc_output/ || true 8 | # cp -r ./packages/*/node_modules/.nyc_output/* $cwd/.nyc_output/ || true 9 | # ./node_modules/.bin/nyc report 10 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm run build 4 | lerna publish $* 5 | -------------------------------------------------------------------------------- /scripts/tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pkgs=`find packages -maxdepth 1 -mindepth 1` 3 | cwd=`pwd` 4 | TAG=$1; 5 | 6 | if [ -z "$TAG" ]; then 7 | echo Please provide a valid tag name!; 8 | exit 1; 9 | fi 10 | 11 | for pkg in $pkgs 12 | do 13 | cd $cwd 14 | if [ -f "${pkg}/package.json" ]; then 15 | cd $pkg 16 | NAME=$(node -pe "require('./package.json').name") 17 | VERSION=$(node -pe "require('./package.json').version") 18 | SHH="npm dist-tag add ${NAME}@${VERSION} $TAG" 19 | echo $SHH 20 | $SHH 21 | fi 22 | done 23 | cd $cwd 24 | --------------------------------------------------------------------------------