├── .clippy.toml ├── .github └── workflows │ ├── ci.yml │ └── codecov.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── aliyun_ram_app.rs ├── lazy_app.rs └── simple_app.rs ├── nacos-macro ├── Cargo.toml └── src │ ├── lib.rs │ └── message │ ├── mod.rs │ ├── request.rs │ └── response.rs ├── proto └── nacos_grpc_service.proto ├── src ├── _.rs ├── api │ ├── config.rs │ ├── constants.rs │ ├── error.rs │ ├── mod.rs │ ├── naming.rs │ ├── plugin │ │ ├── auth │ │ │ ├── auth_by_aliyun_ram.rs │ │ │ ├── auth_by_http.rs │ │ │ └── mod.rs │ │ ├── config_filter.rs │ │ ├── encryption.rs │ │ └── mod.rs │ └── props.rs ├── common │ ├── cache │ │ ├── disk.rs │ │ └── mod.rs │ ├── executor │ │ └── mod.rs │ ├── mod.rs │ ├── remote │ │ ├── grpc │ │ │ ├── config.rs │ │ │ ├── handlers │ │ │ │ ├── client_detection_request_handler.rs │ │ │ │ ├── default_handler.rs │ │ │ │ └── mod.rs │ │ │ ├── message │ │ │ │ ├── mod.rs │ │ │ │ ├── request │ │ │ │ │ ├── client_detection_request.rs │ │ │ │ │ ├── health_check_request.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── server_check_request.rs │ │ │ │ │ └── set_up_request.rs │ │ │ │ └── response │ │ │ │ │ ├── client_detection_response.rs │ │ │ │ │ ├── error_response.rs │ │ │ │ │ ├── health_check_response.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── server_check_response.rs │ │ │ ├── mod.rs │ │ │ ├── nacos_grpc_client.rs │ │ │ ├── nacos_grpc_connection.rs │ │ │ ├── nacos_grpc_service.rs │ │ │ ├── server_address.rs │ │ │ ├── server_list_service.rs │ │ │ └── tonic.rs │ │ └── mod.rs │ └── util.rs ├── config │ ├── cache.rs │ ├── handler.rs │ ├── message │ │ ├── mod.rs │ │ ├── request │ │ │ ├── config_batch_listen_request.rs │ │ │ ├── config_change_notify_request.rs │ │ │ ├── config_publish_request.rs │ │ │ ├── config_query_request.rs │ │ │ ├── config_remove_request.rs │ │ │ └── mod.rs │ │ └── response │ │ │ ├── config_batch_listen_response.rs │ │ │ ├── config_change_notify_response.rs │ │ │ ├── config_publish_response.rs │ │ │ ├── config_query_response.rs │ │ │ ├── config_remove_response.rs │ │ │ └── mod.rs │ ├── mod.rs │ ├── util.rs │ └── worker.rs ├── lib.rs └── naming │ ├── chooser │ └── mod.rs │ ├── dto │ ├── mod.rs │ └── service_info.rs │ ├── handler │ ├── mod.rs │ └── naming_push_request_handler.rs │ ├── message │ ├── mod.rs │ ├── request │ │ ├── batch_instance_request.rs │ │ ├── instance_request.rs │ │ ├── mod.rs │ │ ├── notify_subscriber_request.rs │ │ ├── persistent_instance_request.rs │ │ ├── service_list_request.rs │ │ ├── service_query_request.rs │ │ └── subscribe_service_request.rs │ └── response │ │ ├── batch_instance_response.rs │ │ ├── instance_response.rs │ │ ├── mod.rs │ │ ├── notify_subscriber_response.rs │ │ ├── query_service_response.rs │ │ ├── service_list_response.rs │ │ └── subscribe_service_response.rs │ ├── mod.rs │ ├── observable │ ├── mod.rs │ └── service_info_observable.rs │ ├── redo │ ├── automatic_request │ │ ├── batch_instance_request.rs │ │ ├── instance_request.rs │ │ ├── mod.rs │ │ ├── persistent_instance_request.rs │ │ └── subscribe_service_request.rs │ └── mod.rs │ └── updater │ └── mod.rs └── tests └── proto_build.rs /.clippy.toml: -------------------------------------------------------------------------------- 1 | type-complexity-threshold = 500 2 | disallowed-methods = [ 3 | # Mutating environment variables in a multi-threaded context can cause data races. 4 | # see https://github.com/rust-lang/rust/issues/90308 for details. 5 | "std::env::set_var", 6 | "std::env::remove_var", 7 | 8 | # Use tokio::time::sleep instead. 9 | "std::time::sleep", 10 | ] 11 | disallowed-types = [ 12 | # Use tokio::time::Instant instead. 13 | "std::time::Instant", 14 | ] 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release-* 8 | tags: 9 | - v* 10 | pull_request: 11 | branches: 12 | - main 13 | - release-* 14 | 15 | env: 16 | CARGO_TERM_COLOR: always 17 | CARGO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 18 | 19 | jobs: 20 | lint: 21 | name: Lint 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: cargo fmt 26 | run: cargo fmt -- --check --color ${{ env.CARGO_TERM_COLOR }} 27 | - name: cargo clippy 28 | run: cargo clippy -- -W warnings 29 | 30 | regression-test: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - run: echo "Get latest nacos from docker hub" 34 | - run: docker --version 35 | - run: sudo docker pull nacos/nacos-server:latest 36 | - run: sudo docker run --name nacos-quick -e MODE=standalone -p 8848:8848 -p 9848:9848 -d nacos/nacos-server:latest 37 | - run: sudo apt install -y protobuf-compiler libprotobuf-dev 38 | - name: Check out repository code 39 | uses: actions/checkout@v3 40 | - run: cargo --version --verbose 41 | - run: rustc --version --verbose 42 | - name: format check 43 | run: cargo fmt --check 44 | - name: unit test 45 | run: cargo test --all-targets 46 | - run: cargo run --example simple_app 47 | 48 | build: 49 | name: Build 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v3 53 | - name: Build 54 | run: cargo build 55 | - name: Build examples 56 | run: cargo build --examples 57 | - name: Run Tests 58 | run: cargo test --all-targets 59 | 60 | # publish: 61 | # name: Publish 62 | # runs-on: ubuntu-latest 63 | # needs: [lint, build] 64 | # if: startswith(github.ref, 'refs/tags/v') 65 | # 66 | # steps: 67 | # - uses: actions/checkout@v3 68 | # - name: cargo publish 69 | # run: cargo publish --token ${{ env.CARGO_TOKEN }} 70 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: codecov 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release-* 8 | tags: 9 | - v* 10 | pull_request: 11 | branches: 12 | - main 13 | - release-* 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | with: 22 | submodules: true 23 | - uses: actions-rs/toolchain@v1 24 | with: 25 | profile: minimal 26 | toolchain: stable 27 | override: true 28 | - uses: actions-rs/tarpaulin@v0.1 29 | with: 30 | version: 0.22.0 31 | - uses: codecov/codecov-action@v2.1.0 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # IDE 13 | .idea 14 | .vscode 15 | .history 16 | .DS_Store 17 | 18 | # Customize 19 | ### google.protobuf.rs build by prost-build, exclude it because no content. 20 | **/google.protobuf.rs 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 变更日志 | Change log 2 | 3 | ### 0.5.0 4 | 5 | - 增强: rust-edition=2024, MSRV=1.80 6 | - 增强: ConfigService / NamingService 改为 struct 而无需 Box 包裹 7 | 8 | --- 9 | 10 | - Enhance: rust-edition=2024, MSRV=1.80 11 | - Enhance: ConfigService / NamingService changed to struct without Box wrapping 12 | 13 | 14 | ### 0.4.3 15 | 16 | - 功能: 提供 Aliyun ram AuthPlugin (#245),通过 `features = ["auth-by-aliyun"]` 开启 17 | - 文档: Add aliyun ram examples and improve doc by @luoxiner in #248 18 | - 增强: use LazyLock replace lazy_static by @CherishCai in #250 19 | 20 | --- 21 | 22 | - Feature: Support Aliyun ram AuthPlugin (#245), enabled with `features = ["auth-by-aliyun"]` 23 | - Doc: Add aliyun ram examples and improve doc by @luoxiner in #248 24 | - Chore: use LazyLock replace lazy_static by @CherishCai in #250 25 | 26 | 27 | ### 0.4.2 Yank 28 | 29 | - 功能: 提供 Aliyun ram AuthPlugin,通过 `features = ["auth-by-aliyun"]` 开启 30 | 31 | --- 32 | 33 | - Feature: Support Aliyun ram AuthPlugin (#245), enabled with `features = ["auth-by-aliyun"]` 34 | 35 | ### 0.4.1 36 | 37 | - 优化: 在 `auth-plugin-http` 使用 `arc-swap` 替换 unsafe 代码 38 | - 增强: 可以设置参数 `max_retries` 使内部连接 nacos-server 仅重试一定次数(默认无限重连),否则失败抛出 Error 39 | - 增强: 针对 Config/Naming 的 sdk 调用检查参数 40 | - 升级: 升级 `tonic` and `prost` 版本 41 | - 样例: 增加一个使用 LazyLock 的用例 lazy_app 42 | 43 | --- 44 | 45 | - Opt: `auth-plugin-http` unsafe code replace with `arc-swap` by @thynson in #234 46 | - Enhance: Prop `max_retries` for InnerConnection with nacos-server by @451846939 in #242 47 | - Enhance: Check params for Config/Naming by @CherishCai in #240 48 | - Bump: Upgrade `tonic` and `prost` version by @thynson in #233 49 | - Chore: Add an example lazy_app by @CherishCai in #239 50 | 51 | ### 0.4.0 52 | 53 | - 破坏性变更: 使 NamingService 和 ConfigService impl Send + Sync 54 | - 破坏性变更: 默认 async,去掉 sync api,需要的话建议 `futures::executor::block_on(future_fn)` 55 | 56 | --- 57 | 58 | - Change: make NamingService and ConfigService Send + Sync 59 | - Change: all async API; If you need sync, maybe `futures::executor::block_on(future_fn)` 60 | 61 | ### 0.3.6 62 | 63 | - 文档: 补充说明 `NamingService` 和 `ConfigService` 需要全局的生命周期 64 | - 优化: 调整 `connection health check` 日志级别为 `warn` 65 | 66 | --- 67 | 68 | - Doc: supplement that `NamingService` and `ConfigService` need a global lifecycle 69 | - Enhance: adjust the log level of `connection health check` to `warn` 70 | 71 | ### 0.3.5 72 | 73 | - 修复: 磁盘加载持久化数据不触发 `Listener` 回调 74 | - 功能: 新增 `naming_load_cache_at_start` 属性,用于控制是否在启动时加载缓存, 默认 `false` 75 | 76 | --- 77 | 78 | - Fix: load service info from disk not trigger `Listener` callback 79 | - Feature: add `naming_load_cache_at_start` property to control whether to load the cache at startup, default `false` 80 | 81 | ### 0.3.4 82 | 83 | - 增强: 当设置 ephemeral=false 时,注册持久化实例 84 | 85 | --- 86 | 87 | - Enhance: register persistent-instance when instance's ephemeral=false 88 | 89 | ### 0.3.3 90 | 91 | - 增强: Nacos client 公共线程池线程数量默认为1并升级一些依赖版本 92 | 93 | --- 94 | 95 | - Enhance: upgrade some dependencies and nacos common thread pool default thread number is 1 96 | 97 | ### 0.3.2 98 | 99 | - 增强:支持环境变量设置部分参数,默认为环境变量优先 100 | - 增强:提供防推空参数设置,默认 true 101 | - 增强:支持 server_addr 不设置端口,默认 8848 102 | - 测试:Integration Test with nacos-server 103 | 104 | --- 105 | 106 | - Enhance: Read props from environment variables, please see `nacos_sdk::api::constants::ENV_NACOS_CLIENT_*` 107 | - Enhance: The `naming_push_empty_protection` could be set by ClientProps 108 | - Enhance: Support `server-addr` without port, default 8848 109 | - Test:Integration Test with nacos-server 110 | 111 | ### 0.3.1 112 | 113 | - Fix:异步登陆未完成,进行其它调用出现未登陆异常 `user not found` 114 | 115 | --- 116 | 117 | - Fix: Asynchronous login not completed, there is an exception to `user not found` in when making other calls. 118 | 119 | ### 0.3.0 120 | 121 | - 重构:gRPC 连接层使用 tonic 替代 tikv/grpc-rs ,让编译构建更舒适 122 | - 破坏性变更:api 插件 auth/config-filter/config-encryption 都改成 async 函数 123 | 124 | --- 125 | 126 | - Refactor: tonic instead of tikv/grpc-rs 127 | - Change: Break Change api of auth plugin, support async fn 128 | - Change: Break Change api of config-filter plugin, support async fn 129 | - Change: Break Change api of config-encryption plugin, support async fn 130 | 131 | ### 0.2.6 132 | 133 | - 修复 `ServiceInfoUpdateTask` 丢失 auth header 134 | 135 | --- 136 | 137 | - fix lose auth headers in ServiceInfoUpdateTask 138 | 139 | ### 0.2.5 140 | 141 | - 优化重连机制 142 | 143 | --- 144 | 145 | - Enhance: optimize reconnect logic 146 | 147 | ### 0.2.4 148 | 149 | - 清理无用代码 150 | - login url 携带账号密码 151 | - 统一使用变量名占位方式打印日志 152 | - 支持 https login 认证 153 | - 支持自定义 grpc 端口 154 | - 实现 List-Watch 机制 naming 模块 155 | - 设置默认 grpc 请求超时时间 156 | - 修复服务端多次推送服务变更信息 157 | 158 | --- 159 | 160 | - Chore: login with url encode username password. 161 | - Chore: clean code with clippy 162 | - Chore: log macro args into string 163 | - Feature: add https scheme in feathre for auth and custom grpc port support 164 | - Feature: implement List-Watch for naming module 165 | - Enhance: set default timeout 166 | - Fix: service info push many times from server 167 | 168 | ### 0.2.3 169 | 170 | - 提供 async api,可以通过 `features = ["async"]` 来启用 171 | - 优化内部逻辑,减少核心线程数目、去除 tls/openssl 依赖 172 | - 变更 naming api `register_instance/select_instances` 用以替代 `register_service/select_instance` 173 | - 修复 naming 服务变更的日志打印 174 | 175 | --- 176 | 177 | - Api: provides the async API, which can be enabled via `features = ['async"]` 178 | - Chore: optimize internal logic, reduce the number of core threads, remove tls/openssl dependencies 179 | - Change: naming api `register_instance/select_instances` instead of `register_service/select_instance` 180 | - Fix: naming changed service log 181 | 182 | ### 0.2.2 183 | 184 | - 修复 cluster_name 无效 185 | 186 | --- 187 | 188 | - fix cluster_name invalid when the service register 189 | 190 | ### 0.2.1 191 | - 支持设置多服务端地址,形如:`address:port[,address:port],...]` 192 | 193 | --- 194 | 195 | - Support multi server-addr, following format: `address:port[,address:port],...]` 196 | 197 | ### 0.2.0 198 | 199 | - Config/Naming 功能均可用 200 | - 登陆鉴权 username/password 201 | - 配置解密插件 202 | - 底层 grpc 链接健康检测,自动重连 203 | 204 | --- 205 | 206 | - The module of Config and naming are available 207 | - Support Auth Plugin and with props username/password 208 | - Config decryption Plugin 209 | - Core grpc health detection, automatic reconnection 210 | 211 | ### 0.1.1 212 | 213 | - Config 模块基本可用 214 | - 欢迎更多贡献、修复和标准化 api 215 | 216 | --- 217 | 218 | - The module of Config basically available 219 | - Welcome more contributions, fixes and standardized APIs 220 | 221 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with 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, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | # 18 | 19 | [package] 20 | name = "nacos-sdk" 21 | version = "0.5.0" 22 | edition = "2024" 23 | authors = ["nacos-group", "CheirshCai <785427346@qq.com>", "onewe <2583021406@qq.com>"] 24 | license = "Apache-2.0" 25 | readme = "README.md" 26 | documentation = "https://docs.rs/nacos-sdk/latest" 27 | repository = "https://github.com/nacos-group/nacos-sdk-rust" 28 | homepage = "https://nacos.io" 29 | description = "Nacos client in Rust." 30 | categories = ["network-programming", "development-tools"] 31 | keywords = ["microservices", "config", "naming", "service-discovery", "config-management"] 32 | exclude = [".github", "proto", "tests"] 33 | 34 | [workspace] 35 | members = [ 36 | "nacos-macro" 37 | ] 38 | 39 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 40 | [features] 41 | default = ["config", "naming", "auth-by-http"] 42 | config = [] 43 | naming = [] 44 | tls = ["reqwest/default-tls"] 45 | auth-by-http = ["reqwest"] 46 | auth-by-aliyun = ["ring", "base64", "chrono"] 47 | 48 | [dependencies] 49 | arc-swap = "1.7" 50 | nacos-macro = { version = "0.2.0", path = "nacos-macro" } 51 | thiserror = "1.0" 52 | tokio = { version = "1", features = ["full"] } 53 | 54 | futures = "0.3" 55 | prost = "0.13" 56 | prost-types = "0.13" 57 | serde = { version = "1", features = ["derive"] } 58 | serde_json = "1" 59 | 60 | tracing = "0.1" 61 | local_ipaddress = "0.1.3" 62 | rand = "0.8.5" 63 | 64 | # now only for feature="auth-by-http" 65 | reqwest = { version = "0.12", default-features = false, features = [], optional = true } 66 | 67 | # only for aliyun-ram-auth 68 | ring = { version = "0.17.8", default-features = false, optional = true } 69 | base64 = { version = "0.22.1", default-features = false, optional = true } 70 | chrono = { version = "0.4", features = ["now"] ,optional = true } 71 | 72 | async-trait = "0.1" 73 | async-stream = "0.3.5" 74 | tonic = "0.12" 75 | tower = { version = "0.4.13", features = ["filter", "log"] } 76 | futures-util = "0.3.28" 77 | want = "0.3.0" 78 | dashmap = "5.4.0" 79 | home = "0.5.4" 80 | 81 | dotenvy = "0.15" 82 | 83 | [dev-dependencies] 84 | tracing-subscriber = { version = "0.3", features = ["default"] } 85 | tonic-build = "0.12" 86 | mockall = { version = "0.11" } 87 | 88 | 89 | [[example]] 90 | name = "simple_app" 91 | path = "examples/simple_app.rs" 92 | 93 | [[example]] 94 | name = "lazy_app" 95 | path = "examples/lazy_app.rs" 96 | 97 | [[example]] 98 | name = "aliyun_ram_app" 99 | path = "examples/aliyun_ram_app.rs" 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nacos-sdk-rust 2 | Nacos client in Rust 3 | 4 | ### Extra 5 | > [nacos-sdk-rust-binding-node](https://github.com/opc-source/nacos-sdk-rust-binding-node.git) : nacos-sdk-rust binding for NodeJs with napi. 6 | > 7 | > Tip: nacos-sdk-nodejs 仓库暂未提供 2.x gRPC 交互模式,为了能升级它,故而通过 node addon 方式调用 nacos-sdk-rust 8 | 9 | > [nacos-sdk-rust-binding-py](https://github.com/opc-source/nacos-sdk-rust-binding-py.git) : nacos-sdk-rust binding for Python with PyO3. 10 | > 11 | > Tip: nacos-sdk-python 仓库暂未提供 2.x gRPC 交互模式,为了能升级它,故而通过 ffi 方式调用 nacos-sdk-rust 12 | 13 | 14 | # Proposal 15 | 16 | https://github.com/alibaba/nacos/issues/8443#issuecomment-1248227587 17 | 18 | ## Quickstart 19 | 20 | ### Add Dependency 21 | Add the dependency in `Cargo.toml`: 22 | 23 | ```toml 24 | [dependencies] 25 | # If you need sync API, maybe `futures::executor::block_on(future_fn)` 26 | nacos-sdk = { version = "0.5", features = ["default"] } 27 | ``` 28 | 29 | ### Usage of Config 30 | ```rust 31 | // 请注意!一般情况下,应用下仅需一个 Config 客户端,而且需要长期持有直至应用停止。 32 | // 因为它内部会初始化与服务端的长链接,后续的数据交互及变更订阅,都是实时地通过长链接告知客户端的。 33 | let config_service = ConfigServiceBuilder::new( 34 | ClientProps::new() 35 | .server_addr("127.0.0.1:8848") 36 | // Attention! "public" is "", it is recommended to customize the namespace with clear meaning. 37 | .namespace("") 38 | .app_name("simple_app"), 39 | .auth_username("username") 40 | .auth_password("password") 41 | ) 42 | .enable_auth_plugin_http() 43 | .build()?; 44 | 45 | // example get a config 46 | let config_resp = config_service.get_config("todo-data-id".to_string(), "todo-group".to_string()).await; 47 | match config_resp { 48 | Ok(config_resp) => tracing::info!("get the config {}", config_resp), 49 | Err(err) => tracing::error!("get the config {:?}", err), 50 | } 51 | 52 | struct ExampleConfigChangeListener; 53 | 54 | impl ConfigChangeListener for ExampleConfigChangeListener { 55 | fn notify(&self, config_resp: ConfigResponse) { 56 | tracing::info!("listen the config={:?}", config_resp); 57 | } 58 | } 59 | 60 | // example add a listener 61 | let _listen = config_service.add_listener( 62 | "todo-data-id".to_string(), 63 | "todo-group".to_string(), 64 | Arc::new(ExampleConfigChangeListener {}), 65 | ).await; 66 | match _listen { 67 | Ok(_) => tracing::info!("listening the config success"), 68 | Err(err) => tracing::error!("listen config error {:?}", err), 69 | } 70 | ``` 71 | 72 | ### Usage of Naming 73 | ```rust 74 | // 请注意!一般情况下,应用下仅需一个 Naming 客户端,而且需要长期持有直至应用停止。 75 | // 因为它内部会初始化与服务端的长链接,后续的数据交互及变更订阅,都是实时地通过长链接告知客户端的。 76 | let naming_service = NamingServiceBuilder::new( 77 | ClientProps::new() 78 | .server_addr("127.0.0.1:8848") 79 | // Attention! "public" is "", it is recommended to customize the namespace with clear meaning. 80 | .namespace("") 81 | .app_name("simple_app"), 82 | .auth_username("username") 83 | .auth_password("password") 84 | ) 85 | .enable_auth_plugin_http() 86 | .build()?; 87 | 88 | pub struct ExampleInstanceChangeListener; 89 | 90 | impl NamingEventListener for ExampleInstanceChangeListener { 91 | fn event(&self, event: std::sync::Arc) { 92 | tracing::info!("subscriber notify event={:?}", event); 93 | } 94 | } 95 | 96 | // example naming subscriber 97 | let subscriber = Arc::new(ExampleInstanceChangeListener); 98 | let _subscribe_ret = naming_service.subscribe( 99 | "test-service".to_string(), 100 | Some(constants::DEFAULT_GROUP.to_string()), 101 | Vec::default(), 102 | subscriber, 103 | ).await; 104 | 105 | // example naming register instances 106 | let service_instance1 = ServiceInstance { 107 | ip: "127.0.0.1".to_string(), 108 | port: 9090, 109 | ..Default::default() 110 | }; 111 | let _register_instance_ret = naming_service.batch_register_instance( 112 | "test-service".to_string(), 113 | Some(constants::DEFAULT_GROUP.to_string()), 114 | vec![service_instance1], 115 | ).await; 116 | ``` 117 | 118 | ### Props 119 | Props count be set by `ClientProps`, or Environment variables (Higher priority). 120 | See them in `nacos_sdk::api::props::ClientProps` or `nacos_sdk::api::constants::ENV_NACOS_CLIENT_*`. 121 | e.g. 122 | - env `NACOS_CLIENT_COMMON_THREAD_CORES` to set nacos-client-thread-pool num, default 1 123 | - env `NACOS_CLIENT_NAMING_PUSH_EMPTY_PROTECTION` for naming empty data notify protection, default true 124 | - env `NACOS_CLIENT_USERNAME` to set http auth username 125 | - env `NACOS_CLIENT_PASSWORD` to set http auth password 126 | - env `NACOS_CLIENT_ACCESS_KEY` to set Aliyun ram access-key 127 | - env `NACOS_CLIENT_SECRET_KEY` to set Aliyun ram access-secret 128 | 129 | ### AuthPlugin Features 130 | - > Set access-key, access-secret via Environment variables are recommended. 131 | - auth-by-http 132 | - support http auth via username and password 133 | - how to use 134 | - enable auth-by-http(default is enabled) 135 | ```toml 136 | [dependencies] 137 | nacos-sdk = { version = "0.4", features = ["default"] } 138 | ``` 139 | - Set username and password via environment variables 140 | ```shell 141 | export NACOS_CLIENT_USERNAME=you_username 142 | export NACOS_CLIENT_PASSWORD=you_password 143 | ``` 144 | - Enable auth-by-http in your code 145 | ```rust 146 | ConfigServiceBuilder::new( 147 | ClientProps::new() 148 | .server_addr("localhost:8848")) 149 | .enable_auth_plugin_http() 150 | 151 | NamingServiceBuilder::new( 152 | ClientProps::new() 153 | .server_addr("localhost:8848")) 154 | .enable_auth_plugin_http() 155 | .build() 156 | ``` 157 | - auth-by-aliyun 158 | - support aliyun ram auth via access-key and access-secret 159 | - how to use 160 | - enable auth-by-aliyun feature in toml 161 | ```toml 162 | [dependencies] 163 | nacos-sdk = { version = "0.4", features = ["default", "auth-by-aliyun"] } 164 | ``` 165 | - Set accessKey and secretKey via environment variables 166 | ```shell 167 | export NACOS_CLIENT_ACCESS_KEY=you_access_key 168 | export NACOS_CLIENT_SECRET_KEY=you_secret_key 169 | ``` 170 | - Enable aliyun ram auth plugin in code by enable_auth_plugin_aliyun 171 | ```rust 172 | ConfigServiceBuilder::new( 173 | ClientProps::new() 174 | .server_addr("localhost:8848")) 175 | .enable_auth_plugin_aliyun() 176 | 177 | NamingServiceBuilder::new( 178 | ClientProps::new() 179 | .server_addr("localhost:8848")) 180 | .enable_auth_plugin_aliyun() 181 | .build() 182 | ``` 183 | 184 | ## 开发说明 185 | - Build with `cargo build` 186 | - Test with `cargo test` 187 | 188 | - 请 `cargo clippy --all` 根据提示优化代码 189 | > Run `cargo clippy --all` - this will catch common mistakes and improve your Rust code. 190 | 191 | - 请 `cargo fmt --all` 格式化代码再提交 192 | > Run `cargo fmt --all` - this will find and fix code formatting issues. 193 | 194 | - 测试用例暂未能实现自动化,开发过程需本地启动 [nacos server](https://github.com/alibaba/nacos) `-Dnacos.standalone=true` 195 | 196 | ### 主要依赖包 197 | 在 nacos-sdk-rust 工程里,为主要功能的实现,将会引入以下依赖包。 198 | 199 | - serde-rs/serde 一个超高性能的通用序列化/反序列化框架,可以跟多种协议的库联合使用,实现统一编解码格式 200 | - serde-rs/json 快到上天的 JSON 库,也是 Rust 事实上的标准 JSON 201 | - hyperium/tonic 一个 Rust 版的 gRPC 客户端和服务器端 202 | - tokio-rs/prost tokio 出品的 Protocol Buffers 工具,简单易用,文档详细 203 | - tokio-rs/tokio 最火的异步网络库,除了复杂上手难度高一些外,没有其它大的问题。同时 tokio 团队提供了多个非常优秀的 Rust 库,整个生态欣欣向荣,用户认可度很高 204 | - tokio-rs/tracing 强大的日志框架,同时还支持 OpenTelemetry 格式,无缝打通未来的监控 205 | 206 | *Tip:Rust 入门推荐 [Rust语言圣经(Rust Course)](https://course.rs/about-book.html)* 207 | 208 | ### 简要描述 client & server 的交互 209 | 210 | 请关注 `proto/nacos_grpc_service.proto` 并知晓构建出客户端侧的 stub,实现同步调用 `service Request.request()`,流式交互 `service BiRequestStream.requestBiStream()`。 211 | 212 | `hyperium/tonic` 创建与 Nacos-server 的 gRPC 双工长链接,`serde/json` 适配与 server 的交互序列化; 213 | 214 | gRPC 交互的 Payload 和 Metadata 由 `Protocol Buffers` 序列化,具体的 Request/Response 实体 json 格式二进制数据维护于 Payload.body,类型名字符串维护于 Metadata.type 。 215 | 216 | 有了 gRPC 双工长链接,也有了数据序列化方式,那么就是对 Request/Response 的处理逻辑啦; 217 | 而 client 会接受 server 的主动调用,故可以实现一个通用的 RequestHandler 接受 server 的请求,根据 Request 类型分发到具体的处理实现并返回对应的 Response。 218 | 219 | 而 client 请求 server 的部分,则 do it ... 220 | 221 | 以上交互务必参考 java nacos-client 和 nacos-server 的实现。 222 | 223 | #### Config 配置管理模块 224 | - [x] 客户端创建 api 225 | - [x] 发布配置 api 与实现 226 | - [x] 删除配置 api 与实现 227 | - [x] 获取配置 api 与实现 228 | - [x] 监听配置 api 与实现,List-Watch 机制,具备 list 兜底逻辑 229 | - [x] 配置 Filter,提供配置解密默认实现;配置获取后,内存缓存,磁盘缓存均是原文,仅返回到用户时经过配置 Filter 230 | 231 | #### Naming 服务注册模块 232 | - [x] 客户端创建 api 233 | - [x] 注册服务 api 与实现 234 | - [x] 反注册服务 api 与实现 235 | - [x] 批量注册服务 api 与实现 236 | - [x] 获取服务 api 与实现 237 | - [x] 订阅服务 api 与实现,List-Watch 机制,具备 list 兜底逻辑 238 | - [x] 服务防推空,默认开启,可选关闭。 239 | 240 | #### Common 通用能力 241 | - [x] 创建参数,自定义传参 + ENV 环境变量读取,后者优先级高;ENV 统一前缀,例如 `NACOS_CLIENT_CONFIG_*` 于配置管理, `NACOS_CLIENT_NAMING_*` 于服务注册 242 | - [x] 通用客户端请求交互,Request/Response 通用 gRPC 逻辑,提供给 Config/Naming 243 | - [x] Auth 鉴权;账密登陆 username/password,阿里云RAM鉴权 accessKey/secretKey 244 | - [x] 通用日志,`tracing::info!()` 245 | - [ ] Monitor,`opentelemetry` 246 | - [ ] 数据落盘与加载(用于服务端宕机弱依赖) 247 | 248 | # License 249 | [Apache License Version 2.0](LICENSE) 250 | -------------------------------------------------------------------------------- /examples/aliyun_ram_app.rs: -------------------------------------------------------------------------------- 1 | use nacos_sdk::api::config::ConfigServiceBuilder; 2 | use nacos_sdk::api::naming::{NamingServiceBuilder, ServiceInstance}; 3 | use nacos_sdk::api::props::ClientProps; 4 | use std::time::Duration; 5 | use tokio::time::sleep; 6 | 7 | /// Aliyun Ram plugin support 8 | /// 9 | /// Notice: 10 | /// accessKey and secretKey are sensitive data, don't encode them in you code 11 | /// directly, inject it via environment variables are recommended. 12 | /// 13 | /// Example run preparations: 14 | /// 1. inject you accessKey and secretKey via environment variables by following command 15 | /// export NACOS_CLIENT_ACCESS_KEY=you_access_key 16 | /// export NACOS_CLIENT_SECRET_KEY=you_secret_key 17 | /// 18 | /// 2. run command 19 | /// cargo run --example aliyun_ram_app --features default,auth-by-aliyun 20 | 21 | #[tokio::main] 22 | async fn main() -> Result<(), Box> { 23 | #[cfg(feature = "auth-by-aliyun")] 24 | run_config_demo().await; 25 | 26 | #[cfg(feature = "auth-by-aliyun")] 27 | run_naming_demo().await; 28 | Ok(()) 29 | } 30 | 31 | #[cfg(feature = "auth-by-aliyun")] 32 | async fn run_naming_demo() { 33 | let server_addr = "localhost:8848"; 34 | 35 | /// NamingService 36 | let naming_client = NamingServiceBuilder::new(ClientProps::new().server_addr(server_addr)) 37 | .enable_auth_plugin_aliyun() 38 | .build() 39 | .unwrap(); 40 | 41 | let mut instance = ServiceInstance::default(); 42 | instance.ip = "localhost".to_string(); 43 | instance.port = 8080; 44 | 45 | println!("Register localhost:8080 to service(name: test, group: test)"); 46 | naming_client 47 | .register_instance("test".to_owned(), Some("test".to_owned()), instance) 48 | .await 49 | .unwrap(); 50 | 51 | println!("Get All instance from service(name:test, group: test)"); 52 | let instances = naming_client 53 | .get_all_instances( 54 | "test".to_string(), 55 | Some("test".to_string()), 56 | Vec::new(), 57 | false, 58 | ) 59 | .await 60 | .unwrap(); 61 | assert_eq!(1, instances.len()); 62 | assert_eq!("localhost", instances[0].ip); 63 | assert_eq!(8080, instances[0].port); 64 | } 65 | 66 | #[cfg(feature = "auth-by-aliyun")] 67 | async fn run_config_demo() { 68 | let server_addr = "localhost:8848"; 69 | 70 | /// Config service 71 | let config_client = ConfigServiceBuilder::new(ClientProps::new().server_addr(server_addr)) 72 | .enable_auth_plugin_aliyun() 73 | .build() 74 | .unwrap(); 75 | 76 | println!( 77 | "Publish config dataId = {}, group = {}, content = {}", 78 | "test", "test", "test=test" 79 | ); 80 | config_client 81 | .publish_config( 82 | "test".to_string(), 83 | "test".to_string(), 84 | "test=test".to_string(), 85 | Some("properties".to_string()), 86 | ) 87 | .await 88 | .unwrap(); 89 | 90 | println!("Waiting..."); 91 | sleep(Duration::from_secs(5)).await; 92 | 93 | let response = config_client 94 | .get_config("test".to_string(), "test".to_string()) 95 | .await 96 | .unwrap(); 97 | println!( 98 | "Get config from nacos for dataId = {}, group = {}, content = {}", 99 | "test", 100 | "test", 101 | response.content() 102 | ); 103 | assert_eq!("test=test", response.content()); 104 | assert_eq!("properties", response.content_type()); 105 | assert_eq!("test", response.group()); 106 | assert_eq!("test", response.data_id()); 107 | } 108 | -------------------------------------------------------------------------------- /examples/lazy_app.rs: -------------------------------------------------------------------------------- 1 | use nacos_sdk::api::config::{ 2 | ConfigChangeListener, ConfigResponse, ConfigService, ConfigServiceBuilder, 3 | }; 4 | use nacos_sdk::api::constants; 5 | use nacos_sdk::api::naming::{ 6 | NamingChangeEvent, NamingEventListener, NamingService, NamingServiceBuilder, ServiceInstance, 7 | }; 8 | use nacos_sdk::api::props::ClientProps; 9 | 10 | use std::sync::LazyLock; 11 | 12 | static CLIENT_PROPS: LazyLock = LazyLock::new(|| { 13 | ClientProps::new() 14 | .server_addr(constants::DEFAULT_SERVER_ADDR) 15 | // .remote_grpc_port(9838) 16 | // Attention! "public" is "", it is recommended to customize the namespace with clear meaning. 17 | .namespace("") 18 | .app_name("lazy_app") 19 | .auth_username("nacos") // TODO You can choose not to enable auth 20 | .auth_password("nacos") // TODO You can choose not to enable auth 21 | }); 22 | 23 | // 请注意!一般情况下,应用下仅需一个 Config 客户端,而且需要长期持有直至应用停止。 24 | // 因为它内部会初始化与服务端的长链接,后续的数据交互及变更订阅,都是实时地通过长链接告知客户端的。 25 | static CONFIG_SERVICE: LazyLock = LazyLock::new(|| { 26 | let config_service = ConfigServiceBuilder::new(CLIENT_PROPS.clone()) 27 | .enable_auth_plugin_http() // TODO You can choose not to enable auth 28 | .build() 29 | .unwrap(); 30 | config_service 31 | }); 32 | 33 | // 请注意!一般情况下,应用下仅需一个 Naming 客户端,而且需要长期持有直至应用停止。 34 | // 因为它内部会初始化与服务端的长链接,后续的数据交互及变更订阅,都是实时地通过长链接告知客户端的。 35 | static NAMING_SERVICE: LazyLock = LazyLock::new(|| { 36 | let naming_service = NamingServiceBuilder::new(CLIENT_PROPS.clone()) 37 | .enable_auth_plugin_http() // TODO You can choose not to enable auth 38 | .build() 39 | .unwrap(); 40 | naming_service 41 | }); 42 | 43 | /// enable https auth run with command: 44 | /// cargo run --example lazy_app --features default,tls 45 | #[tokio::main] 46 | async fn main() -> Result<(), Box> { 47 | tracing_subscriber::fmt() 48 | // all spans/events with a level higher than TRACE (e.g, info, warn, etc.) 49 | // will be written to stdout. 50 | .with_max_level(tracing::Level::INFO) 51 | .with_thread_names(true) 52 | .with_thread_ids(true) 53 | // sets this to be the default, global collector for this application. 54 | .init(); 55 | 56 | // ---------- Config ------------- 57 | let config_resp = CONFIG_SERVICE 58 | .get_config("todo-data-id".to_string(), "LOVE".to_string()) 59 | .await; 60 | match config_resp { 61 | Ok(config_resp) => tracing::info!("get the config {}", config_resp), 62 | Err(err) => tracing::error!("get the config {:?}", err), 63 | } 64 | 65 | let _listen = CONFIG_SERVICE 66 | .add_listener( 67 | "todo-data-id".to_string(), 68 | "LOVE".to_string(), 69 | std::sync::Arc::new(SimpleConfigChangeListener {}), 70 | ) 71 | .await; 72 | match _listen { 73 | Ok(_) => tracing::info!("listening the config success"), 74 | Err(err) => tracing::error!("listen config error {:?}", err), 75 | } 76 | 77 | // ---------- Naming ------------- 78 | let listener = std::sync::Arc::new(SimpleInstanceChangeListener); 79 | let _subscribe_ret = NAMING_SERVICE 80 | .subscribe( 81 | "test-service".to_string(), 82 | Some(constants::DEFAULT_GROUP.to_string()), 83 | Vec::default(), 84 | listener, 85 | ) 86 | .await; 87 | 88 | let service_instance1 = ServiceInstance { 89 | ip: "127.0.0.1".to_string(), 90 | port: 9090, 91 | ..Default::default() 92 | }; 93 | let _register_instance_ret = NAMING_SERVICE 94 | .batch_register_instance( 95 | "test-service".to_string(), 96 | Some(constants::DEFAULT_GROUP.to_string()), 97 | vec![service_instance1], 98 | ) 99 | .await; 100 | tokio::time::sleep(tokio::time::Duration::from_millis(666)).await; 101 | 102 | let instances_ret = NAMING_SERVICE 103 | .get_all_instances( 104 | "test-service".to_string(), 105 | Some(constants::DEFAULT_GROUP.to_string()), 106 | Vec::default(), 107 | false, 108 | ) 109 | .await; 110 | match instances_ret { 111 | Ok(instances) => tracing::info!("get_all_instances {:?}", instances), 112 | Err(err) => tracing::error!("naming get_all_instances error {:?}", err), 113 | } 114 | 115 | tokio::time::sleep(tokio::time::Duration::from_secs(300)).await; 116 | 117 | Ok(()) 118 | } 119 | 120 | struct SimpleConfigChangeListener; 121 | 122 | impl ConfigChangeListener for SimpleConfigChangeListener { 123 | fn notify(&self, config_resp: ConfigResponse) { 124 | tracing::info!("listen the config={}", config_resp); 125 | } 126 | } 127 | 128 | pub struct SimpleInstanceChangeListener; 129 | 130 | impl NamingEventListener for SimpleInstanceChangeListener { 131 | fn event(&self, event: std::sync::Arc) { 132 | tracing::info!("subscriber notify: {:?}", event); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /examples/simple_app.rs: -------------------------------------------------------------------------------- 1 | use nacos_sdk::api::config::{ConfigChangeListener, ConfigResponse, ConfigServiceBuilder}; 2 | use nacos_sdk::api::constants; 3 | use nacos_sdk::api::naming::{ 4 | NamingChangeEvent, NamingEventListener, NamingServiceBuilder, ServiceInstance, 5 | }; 6 | use nacos_sdk::api::props::ClientProps; 7 | 8 | /// enable https auth run with command: 9 | /// cargo run --example simple_app --features default,tls 10 | #[tokio::main] 11 | async fn main() -> Result<(), Box> { 12 | tracing_subscriber::fmt() 13 | // all spans/events with a level higher than TRACE (e.g, info, warn, etc.) 14 | // will be written to stdout. 15 | .with_max_level(tracing::Level::DEBUG) 16 | .with_thread_names(true) 17 | .with_thread_ids(true) 18 | // sets this to be the default, global collector for this application. 19 | .init(); 20 | 21 | let client_props = ClientProps::new() 22 | .server_addr(constants::DEFAULT_SERVER_ADDR) 23 | // .remote_grpc_port(9838) 24 | // Attention! "public" is "", it is recommended to customize the namespace with clear meaning. 25 | .namespace("") 26 | .app_name("simple_app") 27 | .auth_username("nacos") // TODO You can choose not to enable auth 28 | .auth_password("nacos") // TODO You can choose not to enable auth 29 | ; 30 | 31 | // ---------- Config ------------- 32 | // 请注意!一般情况下,应用下仅需一个 Config 客户端,而且需要长期持有直至应用停止。 33 | // 因为它内部会初始化与服务端的长链接,后续的数据交互及变更订阅,都是实时地通过长链接告知客户端的。 34 | let config_service = ConfigServiceBuilder::new(client_props.clone()) 35 | .enable_auth_plugin_http() // TODO You can choose not to enable auth 36 | .build()?; 37 | let config_resp = config_service 38 | .get_config("todo-data-id".to_string(), "LOVE".to_string()) 39 | .await; 40 | match config_resp { 41 | Ok(config_resp) => tracing::info!("get the config {}", config_resp), 42 | Err(err) => tracing::error!("get the config {:?}", err), 43 | } 44 | 45 | let _listen = config_service 46 | .add_listener( 47 | "todo-data-id".to_string(), 48 | "LOVE".to_string(), 49 | std::sync::Arc::new(SimpleConfigChangeListener {}), 50 | ) 51 | .await; 52 | match _listen { 53 | Ok(_) => tracing::info!("listening the config success"), 54 | Err(err) => tracing::error!("listen config error {:?}", err), 55 | } 56 | 57 | // ---------- Naming ------------- 58 | // 请注意!一般情况下,应用下仅需一个 Naming 客户端,而且需要长期持有直至应用停止。 59 | // 因为它内部会初始化与服务端的长链接,后续的数据交互及变更订阅,都是实时地通过长链接告知客户端的。 60 | let naming_service = NamingServiceBuilder::new(client_props) 61 | .enable_auth_plugin_http() // TODO You can choose not to enable auth 62 | .build()?; 63 | 64 | let listener = std::sync::Arc::new(SimpleInstanceChangeListener); 65 | let _subscribe_ret = naming_service 66 | .subscribe( 67 | "test-service".to_string(), 68 | Some(constants::DEFAULT_GROUP.to_string()), 69 | Vec::default(), 70 | listener, 71 | ) 72 | .await; 73 | 74 | let service_instance1 = ServiceInstance { 75 | ip: "127.0.0.1".to_string(), 76 | port: 9090, 77 | ..Default::default() 78 | }; 79 | let _register_instance_ret = naming_service 80 | .batch_register_instance( 81 | "test-service".to_string(), 82 | Some(constants::DEFAULT_GROUP.to_string()), 83 | vec![service_instance1], 84 | ) 85 | .await; 86 | tokio::time::sleep(tokio::time::Duration::from_millis(666)).await; 87 | 88 | let instances_ret = naming_service 89 | .get_all_instances( 90 | "test-service".to_string(), 91 | Some(constants::DEFAULT_GROUP.to_string()), 92 | Vec::default(), 93 | false, 94 | ) 95 | .await; 96 | match instances_ret { 97 | Ok(instances) => tracing::info!("get_all_instances {:?}", instances), 98 | Err(err) => tracing::error!("naming get_all_instances error {:?}", err), 99 | } 100 | 101 | tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; 102 | 103 | Ok(()) 104 | } 105 | 106 | struct SimpleConfigChangeListener; 107 | 108 | impl ConfigChangeListener for SimpleConfigChangeListener { 109 | fn notify(&self, config_resp: ConfigResponse) { 110 | tracing::info!("listen the config={}", config_resp); 111 | } 112 | } 113 | 114 | pub struct SimpleInstanceChangeListener; 115 | 116 | impl NamingEventListener for SimpleInstanceChangeListener { 117 | fn event(&self, event: std::sync::Arc) { 118 | tracing::info!("subscriber notify: {:?}", event); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /nacos-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nacos-macro" 3 | version = "0.2.0" 4 | edition = "2021" 5 | authors = ["nacos-group", "CheirshCai <785427346@qq.com>", "onewe <2583021406@qq.com>"] 6 | license = "Apache-2.0" 7 | documentation = "https://docs.rs/nacos-sdk/latest" 8 | repository = "https://github.com/nacos-group/nacos-sdk-rust" 9 | homepage = "https://nacos.io" 10 | description = """ 11 | Nacos's proc macros. 12 | """ 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | proc-macro2 = "1.0.47" 19 | quote = "1.0.21" 20 | syn = { version = "1.0.103", features = ["extra-traits", "parsing"] } 21 | darling = "0.14.2" 22 | -------------------------------------------------------------------------------- /nacos-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod message; 2 | 3 | #[proc_macro_attribute] 4 | pub fn request( 5 | args: proc_macro::TokenStream, 6 | input: proc_macro::TokenStream, 7 | ) -> proc_macro::TokenStream { 8 | message::request(args, input) 9 | } 10 | 11 | #[proc_macro_attribute] 12 | pub fn response( 13 | args: proc_macro::TokenStream, 14 | input: proc_macro::TokenStream, 15 | ) -> proc_macro::TokenStream { 16 | message::response(args, input) 17 | } 18 | -------------------------------------------------------------------------------- /nacos-macro/src/message/mod.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use syn::{parse_macro_input, parse_quote, AttributeArgs, ItemStruct, Path}; 3 | 4 | use self::{request::grpc_request, response::grpc_response}; 5 | 6 | pub(crate) mod request; 7 | pub(crate) mod response; 8 | 9 | #[derive(Debug, FromMeta)] 10 | pub(crate) struct MacroArgs { 11 | identity: String, 12 | 13 | #[darling(default)] 14 | crates: Crates, 15 | 16 | module: Module, 17 | } 18 | 19 | #[derive(Debug, FromMeta)] 20 | enum Module { 21 | Config, 22 | Naming, 23 | Internal, 24 | } 25 | 26 | impl Module { 27 | fn to_string(&self) -> &str { 28 | match self { 29 | Module::Config => "config", 30 | Module::Naming => "naming", 31 | Module::Internal => "internal", 32 | } 33 | } 34 | } 35 | 36 | #[derive(Debug, FromMeta)] 37 | struct Crates { 38 | #[darling(default = "Self::default_serde")] 39 | serde: Path, 40 | 41 | #[darling(default = "Self::default_std")] 42 | std: Path, 43 | } 44 | 45 | impl Default for Crates { 46 | fn default() -> Self { 47 | Self::from_list(&[]).unwrap() 48 | } 49 | } 50 | 51 | impl Crates { 52 | fn default_serde() -> Path { 53 | parse_quote! { ::serde } 54 | } 55 | 56 | fn default_std() -> Path { 57 | parse_quote! { ::std } 58 | } 59 | } 60 | 61 | pub fn request( 62 | args: proc_macro::TokenStream, 63 | input: proc_macro::TokenStream, 64 | ) -> proc_macro::TokenStream { 65 | let item_struct = parse_macro_input!(input as ItemStruct); 66 | 67 | let attr_args = parse_macro_input!(args as AttributeArgs); 68 | let macro_args = MacroArgs::from_list(&attr_args).unwrap(); 69 | 70 | grpc_request(macro_args, item_struct).into() 71 | } 72 | 73 | pub fn response( 74 | args: proc_macro::TokenStream, 75 | input: proc_macro::TokenStream, 76 | ) -> proc_macro::TokenStream { 77 | let item_struct = parse_macro_input!(input as ItemStruct); 78 | 79 | let attr_args = parse_macro_input!(args as AttributeArgs); 80 | let macro_args = MacroArgs::from_list(&attr_args).unwrap(); 81 | 82 | grpc_response(macro_args, item_struct).into() 83 | } 84 | -------------------------------------------------------------------------------- /nacos-macro/src/message/request.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::Parser; 2 | use syn::{parse_quote, ItemStruct, Path}; 3 | 4 | use quote::quote; 5 | 6 | use super::{Crates, MacroArgs}; 7 | 8 | pub(crate) fn grpc_request( 9 | macro_args: MacroArgs, 10 | mut item_struct: ItemStruct, 11 | ) -> proc_macro2::TokenStream { 12 | let module = macro_args.module.to_string(); 13 | let identity = macro_args.identity; 14 | let name = &item_struct.ident; 15 | 16 | let Crates { serde, std } = macro_args.crates; 17 | 18 | // add derive GrpcMessageData 19 | let grpc_message_body = quote! { 20 | impl crate::common::remote::grpc::message::GrpcMessageData for #name { 21 | fn identity<'a>() -> std::borrow::Cow<'a, str> { 22 | #identity.into() 23 | } 24 | } 25 | }; 26 | 27 | let into_request_resource = match macro_args.module { 28 | super::Module::Naming => { 29 | quote! { 30 | fn request_resource(&self) -> Option { 31 | Some(crate::api::plugin::RequestResource { 32 | request_type: "Naming".to_string(), 33 | namespace: self.namespace.clone(), 34 | group: self.group_name.clone(), 35 | resource: self.service_name.clone() 36 | }) 37 | } 38 | } 39 | } 40 | super::Module::Config => { 41 | quote! { 42 | fn request_resource(&self) -> Option { 43 | Some(crate::api::plugin::RequestResource { 44 | request_type: "Config".to_string(), 45 | namespace: self.namespace.clone(), 46 | group: self.group.clone(), 47 | resource: self.data_id.clone() 48 | }) 49 | } 50 | } 51 | } 52 | _ => { 53 | quote! { 54 | fn request_resource(&self) -> Option { 55 | None 56 | } 57 | } 58 | } 59 | }; 60 | 61 | // add derive GrpcRequestMessage 62 | let grpc_message_request = quote! { 63 | 64 | impl crate::common::remote::grpc::message::GrpcRequestMessage for #name { 65 | 66 | fn header(&self, key: &str) -> Option<&String>{ 67 | self.headers.get(key) 68 | } 69 | 70 | fn headers(&self) -> &#std::collections::HashMap { 71 | &self.headers 72 | } 73 | 74 | fn take_headers(&mut self) -> #std::collections::HashMap { 75 | #std::mem::take(&mut self.headers) 76 | } 77 | 78 | fn add_headers(&mut self, map: #std::collections::HashMap) { 79 | self.headers.extend(map.into_iter()); 80 | } 81 | 82 | fn request_id(&self) -> Option<&String> { 83 | self.request_id.as_ref() 84 | } 85 | 86 | fn module(&self) -> &str { 87 | #module 88 | } 89 | 90 | #into_request_resource 91 | } 92 | }; 93 | 94 | // add field 95 | if let syn::Fields::Named(ref mut fields) = item_struct.fields { 96 | let headers_field = syn::Field::parse_named 97 | .parse2(quote! { 98 | pub headers: #std::collections::HashMap 99 | }) 100 | .unwrap(); 101 | let request_id_field = syn::Field::parse_named 102 | .parse2(quote! { 103 | pub request_id: Option 104 | }) 105 | .unwrap(); 106 | 107 | fields.named.push(headers_field); 108 | fields.named.push(request_id_field); 109 | } 110 | 111 | let derive_paths: Vec = vec![ 112 | syn::parse_quote! { #serde::Deserialize }, 113 | syn::parse_quote! { #serde::Serialize }, 114 | syn::parse_quote! { Clone }, 115 | syn::parse_quote! { Debug }, 116 | syn::parse_quote! { Default }, 117 | ]; 118 | 119 | // add derive 120 | item_struct.attrs.push(parse_quote! { 121 | #[derive(#(#derive_paths),*)] 122 | 123 | }); 124 | item_struct.attrs.push(parse_quote!( 125 | #[serde(rename_all = "camelCase")] 126 | )); 127 | 128 | match macro_args.module { 129 | super::Module::Naming => naming_request(&mut item_struct), 130 | super::Module::Config => config_request(&mut item_struct), 131 | _ => {} 132 | } 133 | 134 | quote! { 135 | #item_struct 136 | #grpc_message_body 137 | #grpc_message_request 138 | } 139 | } 140 | 141 | /// Naming request fields 142 | fn naming_request(item_struct: &mut ItemStruct) { 143 | // add fields 144 | if let syn::Fields::Named(ref mut fields) = item_struct.fields { 145 | let namespace_field = syn::Field::parse_named 146 | .parse2(quote! { 147 | pub namespace: Option 148 | }) 149 | .unwrap(); 150 | let service_name_field = syn::Field::parse_named 151 | .parse2(quote! { 152 | pub service_name: Option 153 | }) 154 | .unwrap(); 155 | 156 | let group_name_field = syn::Field::parse_named 157 | .parse2(quote! { 158 | pub group_name: Option 159 | }) 160 | .unwrap(); 161 | 162 | fields.named.push(namespace_field); 163 | fields.named.push(service_name_field); 164 | fields.named.push(group_name_field); 165 | } 166 | } 167 | 168 | /// Config request fields 169 | fn config_request(item_struct: &mut ItemStruct) { 170 | // add fields 171 | if let syn::Fields::Named(ref mut fields) = item_struct.fields { 172 | let namespace_field = syn::Field::parse_named 173 | .parse2(quote! { 174 | #[serde(rename = "tenant")] 175 | pub namespace: Option 176 | }) 177 | .unwrap(); 178 | let data_id_field = syn::Field::parse_named 179 | .parse2(quote! { 180 | pub data_id: Option 181 | }) 182 | .unwrap(); 183 | 184 | let group_field = syn::Field::parse_named 185 | .parse2(quote! { 186 | pub group: Option 187 | }) 188 | .unwrap(); 189 | 190 | fields.named.push(namespace_field); 191 | fields.named.push(data_id_field); 192 | fields.named.push(group_field); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /nacos-macro/src/message/response.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::Parser; 2 | use syn::{parse_quote, ItemStruct, Path}; 3 | 4 | use quote::quote; 5 | 6 | use super::{Crates, MacroArgs}; 7 | 8 | const SUCCESS_RESPONSE: (i32, &str) = (200, "Response ok"); 9 | const FAIL_RESPONSE: (i32, &str) = (500, "Response fail"); 10 | 11 | pub(crate) fn grpc_response( 12 | macro_args: MacroArgs, 13 | mut item_struct: ItemStruct, 14 | ) -> proc_macro2::TokenStream { 15 | let identity = macro_args.identity; 16 | let name = &item_struct.ident; 17 | 18 | let Crates { serde, .. } = macro_args.crates; 19 | 20 | // add derive GrpcMessageBody 21 | let grpc_message_body = quote! { 22 | impl crate::common::remote::grpc::message::GrpcMessageData for #name { 23 | fn identity<'a>() -> std::borrow::Cow<'a, str> { 24 | #identity.into() 25 | } 26 | } 27 | }; 28 | 29 | let (success_code, success_message) = SUCCESS_RESPONSE; 30 | let (fail_code, fail_message) = FAIL_RESPONSE; 31 | 32 | let impl_message_response = quote! { 33 | 34 | impl #name { 35 | 36 | pub fn ok() -> Self { 37 | #name { 38 | result_code: #success_code, 39 | message: Some(#success_message.to_owned()), 40 | ..Default::default() 41 | } 42 | } 43 | 44 | pub fn fail() -> Self { 45 | #name { 46 | result_code: #fail_code, 47 | message: Some(#fail_message.to_owned()), 48 | ..Default::default() 49 | } 50 | } 51 | 52 | } 53 | 54 | }; 55 | 56 | let grpc_message_response = quote! { 57 | 58 | impl crate::common::remote::grpc::message::GrpcResponseMessage for #name { 59 | fn request_id(&self) -> Option<&String> { 60 | self.request_id.as_ref() 61 | } 62 | 63 | fn result_code(&self) -> i32 { 64 | self.result_code 65 | } 66 | 67 | fn error_code(&self) -> i32 { 68 | self.error_code 69 | } 70 | 71 | fn message(&self) -> Option<&String> { 72 | self.message.as_ref() 73 | } 74 | 75 | fn is_success(&self) -> bool { 76 | self.result_code == #success_code 77 | } 78 | } 79 | }; 80 | 81 | if let syn::Fields::Named(ref mut fields) = item_struct.fields { 82 | let result_code_field = syn::Field::parse_named 83 | .parse2(quote! { 84 | pub result_code: i32 85 | }) 86 | .unwrap(); 87 | let error_code_field = syn::Field::parse_named 88 | .parse2(quote! { 89 | pub error_code: i32 90 | }) 91 | .unwrap(); 92 | let message_field = syn::Field::parse_named 93 | .parse2(quote! { 94 | pub message: Option 95 | }) 96 | .unwrap(); 97 | let request_id_field = syn::Field::parse_named 98 | .parse2(quote! { 99 | pub request_id: Option 100 | }) 101 | .unwrap(); 102 | 103 | fields.named.push(result_code_field); 104 | fields.named.push(error_code_field); 105 | fields.named.push(message_field); 106 | fields.named.push(request_id_field); 107 | } 108 | 109 | let derive_paths: Vec = vec![ 110 | syn::parse_quote! { #serde::Deserialize }, 111 | syn::parse_quote! { #serde::Serialize }, 112 | syn::parse_quote! { Clone }, 113 | syn::parse_quote! { Debug }, 114 | syn::parse_quote! { Default }, 115 | ]; 116 | // add derive 117 | item_struct.attrs.push(parse_quote! { 118 | #[derive(#(#derive_paths),*)] 119 | 120 | }); 121 | item_struct.attrs.push(parse_quote!( 122 | #[serde(rename_all = "camelCase")] 123 | )); 124 | 125 | quote! { 126 | #item_struct 127 | #grpc_message_response 128 | #grpc_message_body 129 | #impl_message_response 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /proto/nacos_grpc_service.proto: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * 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 | syntax = "proto3"; 19 | 20 | import "google/protobuf/any.proto"; 21 | import "google/protobuf/timestamp.proto"; 22 | 23 | option java_multiple_files = true; 24 | option java_package = "com.alibaba.nacos.api.grpc.auto"; 25 | 26 | message Metadata { 27 | string type = 3; 28 | string clientIp = 8; 29 | map headers = 7; 30 | } 31 | 32 | message Payload { 33 | Metadata metadata = 2; 34 | google.protobuf.Any body = 3; 35 | } 36 | 37 | service Request { 38 | // Sends a commonRequest 39 | rpc request (Payload) returns (Payload) { 40 | } 41 | } 42 | 43 | service BiRequestStream { 44 | // Sends a biStreamRequest 45 | rpc requestBiStream (stream Payload) returns (stream Payload) { 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/_.rs: -------------------------------------------------------------------------------- 1 | // This file is @generated by prost-build. 2 | #[derive(Clone, PartialEq, ::prost::Message)] 3 | pub struct Metadata { 4 | #[prost(string, tag = "3")] 5 | pub r#type: ::prost::alloc::string::String, 6 | #[prost(string, tag = "8")] 7 | pub client_ip: ::prost::alloc::string::String, 8 | #[prost(map = "string, string", tag = "7")] 9 | pub headers: 10 | ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, 11 | } 12 | #[derive(Clone, PartialEq, ::prost::Message)] 13 | pub struct Payload { 14 | #[prost(message, optional, tag = "2")] 15 | pub metadata: ::core::option::Option, 16 | #[prost(message, optional, tag = "3")] 17 | pub body: ::core::option::Option<::prost_types::Any>, 18 | } 19 | /// Generated client implementations. 20 | pub mod request_client { 21 | #![allow( 22 | unused_variables, 23 | dead_code, 24 | missing_docs, 25 | clippy::wildcard_imports, 26 | clippy::let_unit_value 27 | )] 28 | use tonic::codegen::http::Uri; 29 | use tonic::codegen::*; 30 | #[derive(Debug, Clone)] 31 | pub struct RequestClient { 32 | inner: tonic::client::Grpc, 33 | } 34 | impl RequestClient { 35 | /// Attempt to create a new client by connecting to a given endpoint. 36 | pub async fn connect(dst: D) -> Result 37 | where 38 | D: TryInto, 39 | D::Error: Into, 40 | { 41 | let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 42 | Ok(Self::new(conn)) 43 | } 44 | } 45 | impl RequestClient 46 | where 47 | T: tonic::client::GrpcService, 48 | T::Error: Into, 49 | T::ResponseBody: Body + std::marker::Send + 'static, 50 | ::Error: Into + std::marker::Send, 51 | { 52 | pub fn new(inner: T) -> Self { 53 | let inner = tonic::client::Grpc::new(inner); 54 | Self { inner } 55 | } 56 | pub fn with_origin(inner: T, origin: Uri) -> Self { 57 | let inner = tonic::client::Grpc::with_origin(inner, origin); 58 | Self { inner } 59 | } 60 | pub fn with_interceptor( 61 | inner: T, 62 | interceptor: F, 63 | ) -> RequestClient> 64 | where 65 | F: tonic::service::Interceptor, 66 | T::ResponseBody: Default, 67 | T: tonic::codegen::Service< 68 | http::Request, 69 | Response = http::Response< 70 | >::ResponseBody, 71 | >, 72 | >, 73 | >>::Error: 74 | Into + std::marker::Send + std::marker::Sync, 75 | { 76 | RequestClient::new(InterceptedService::new(inner, interceptor)) 77 | } 78 | /// Compress requests with the given encoding. 79 | /// 80 | /// This requires the server to support it otherwise it might respond with an 81 | /// error. 82 | #[must_use] 83 | pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 84 | self.inner = self.inner.send_compressed(encoding); 85 | self 86 | } 87 | /// Enable decompressing responses. 88 | #[must_use] 89 | pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 90 | self.inner = self.inner.accept_compressed(encoding); 91 | self 92 | } 93 | /// Limits the maximum size of a decoded message. 94 | /// 95 | /// Default: `4MB` 96 | #[must_use] 97 | pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 98 | self.inner = self.inner.max_decoding_message_size(limit); 99 | self 100 | } 101 | /// Limits the maximum size of an encoded message. 102 | /// 103 | /// Default: `usize::MAX` 104 | #[must_use] 105 | pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 106 | self.inner = self.inner.max_encoding_message_size(limit); 107 | self 108 | } 109 | /// Sends a commonRequest 110 | pub async fn request( 111 | &mut self, 112 | request: impl tonic::IntoRequest, 113 | ) -> std::result::Result, tonic::Status> { 114 | self.inner.ready().await.map_err(|e| { 115 | tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 116 | })?; 117 | let codec = tonic::codec::ProstCodec::default(); 118 | let path = http::uri::PathAndQuery::from_static("/Request/request"); 119 | let mut req = request.into_request(); 120 | req.extensions_mut() 121 | .insert(GrpcMethod::new("Request", "request")); 122 | self.inner.unary(req, path, codec).await 123 | } 124 | } 125 | } 126 | /// Generated client implementations. 127 | pub mod bi_request_stream_client { 128 | #![allow( 129 | unused_variables, 130 | dead_code, 131 | missing_docs, 132 | clippy::wildcard_imports, 133 | clippy::let_unit_value 134 | )] 135 | use tonic::codegen::http::Uri; 136 | use tonic::codegen::*; 137 | #[derive(Debug, Clone)] 138 | pub struct BiRequestStreamClient { 139 | inner: tonic::client::Grpc, 140 | } 141 | impl BiRequestStreamClient { 142 | /// Attempt to create a new client by connecting to a given endpoint. 143 | pub async fn connect(dst: D) -> Result 144 | where 145 | D: TryInto, 146 | D::Error: Into, 147 | { 148 | let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 149 | Ok(Self::new(conn)) 150 | } 151 | } 152 | impl BiRequestStreamClient 153 | where 154 | T: tonic::client::GrpcService, 155 | T::Error: Into, 156 | T::ResponseBody: Body + std::marker::Send + 'static, 157 | ::Error: Into + std::marker::Send, 158 | { 159 | pub fn new(inner: T) -> Self { 160 | let inner = tonic::client::Grpc::new(inner); 161 | Self { inner } 162 | } 163 | pub fn with_origin(inner: T, origin: Uri) -> Self { 164 | let inner = tonic::client::Grpc::with_origin(inner, origin); 165 | Self { inner } 166 | } 167 | pub fn with_interceptor( 168 | inner: T, 169 | interceptor: F, 170 | ) -> BiRequestStreamClient> 171 | where 172 | F: tonic::service::Interceptor, 173 | T::ResponseBody: Default, 174 | T: tonic::codegen::Service< 175 | http::Request, 176 | Response = http::Response< 177 | >::ResponseBody, 178 | >, 179 | >, 180 | >>::Error: 181 | Into + std::marker::Send + std::marker::Sync, 182 | { 183 | BiRequestStreamClient::new(InterceptedService::new(inner, interceptor)) 184 | } 185 | /// Compress requests with the given encoding. 186 | /// 187 | /// This requires the server to support it otherwise it might respond with an 188 | /// error. 189 | #[must_use] 190 | pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 191 | self.inner = self.inner.send_compressed(encoding); 192 | self 193 | } 194 | /// Enable decompressing responses. 195 | #[must_use] 196 | pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 197 | self.inner = self.inner.accept_compressed(encoding); 198 | self 199 | } 200 | /// Limits the maximum size of a decoded message. 201 | /// 202 | /// Default: `4MB` 203 | #[must_use] 204 | pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 205 | self.inner = self.inner.max_decoding_message_size(limit); 206 | self 207 | } 208 | /// Limits the maximum size of an encoded message. 209 | /// 210 | /// Default: `usize::MAX` 211 | #[must_use] 212 | pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 213 | self.inner = self.inner.max_encoding_message_size(limit); 214 | self 215 | } 216 | /// Sends a biStreamRequest 217 | pub async fn request_bi_stream( 218 | &mut self, 219 | request: impl tonic::IntoStreamingRequest, 220 | ) -> std::result::Result< 221 | tonic::Response>, 222 | tonic::Status, 223 | > { 224 | self.inner.ready().await.map_err(|e| { 225 | tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 226 | })?; 227 | let codec = tonic::codec::ProstCodec::default(); 228 | let path = http::uri::PathAndQuery::from_static("/BiRequestStream/requestBiStream"); 229 | let mut req = request.into_streaming_request(); 230 | req.extensions_mut() 231 | .insert(GrpcMethod::new("BiRequestStream", "requestBiStream")); 232 | self.inner.streaming(req, path, codec).await 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/api/constants.rs: -------------------------------------------------------------------------------- 1 | pub const DEFAULT_SERVER_ADDR: &str = "127.0.0.1:8848"; 2 | 3 | pub const DEFAULT_SERVER_PORT: u32 = 8848; 4 | 5 | /// Attention! "public" is "", it is recommended to customize the namespace with clear meaning. 6 | pub(crate) const DEFAULT_NAMESPACE: &str = "public"; 7 | 8 | pub const DEFAULT_GROUP: &str = "DEFAULT_GROUP"; 9 | 10 | pub const UNKNOWN: &str = "unknown"; 11 | 12 | /// label AppName 13 | pub(crate) const KEY_LABEL_APP_NAME: &str = "AppName"; 14 | 15 | /// label for crate inner common::remote 16 | pub(crate) mod common_remote { 17 | 18 | pub const LABEL_SOURCE: &str = "source"; 19 | 20 | /// LABEL_SOURCE value sdk 21 | pub const LABEL_SOURCE_SDK: &str = "sdk"; 22 | 23 | pub const LABEL_MODULE: &str = "module"; 24 | 25 | /// LABEL_MODULE value naming 26 | pub const LABEL_MODULE_NAMING: &str = "naming"; 27 | 28 | /// LABEL_MODULE value config 29 | pub const LABEL_MODULE_CONFIG: &str = "config"; 30 | } 31 | 32 | pub(crate) const ENV_NACOS_CLIENT_PROPS_FILE_PATH: &str = "NACOS_CLIENT_PROPS_FILE_PATH"; 33 | 34 | /// env `NACOS_CLIENT_COMMON_THREAD_CORES` to set nacos-client-thread-pool num, default 1 35 | pub const ENV_NACOS_CLIENT_COMMON_THREAD_CORES: &str = "NACOS_CLIENT_COMMON_THREAD_CORES"; 36 | 37 | pub const ENV_NACOS_CLIENT_COMMON_SERVER_ADDRESS: &str = "NACOS_CLIENT_SERVER_ADDRESS"; 38 | 39 | pub const ENV_NACOS_CLIENT_COMMON_SERVER_PORT: &str = "NACOS_CLIENT_SERVER_PORT"; 40 | 41 | pub const ENV_NACOS_CLIENT_COMMON_NAMESPACE: &str = "NACOS_CLIENT_NAMESPACE"; 42 | 43 | pub const ENV_NACOS_CLIENT_COMMON_APP_NAME: &str = "NACOS_CLIENT_APP_NAME"; 44 | 45 | pub const ENV_NACOS_CLIENT_AUTH_USERNAME: &str = "NACOS_CLIENT_USERNAME"; 46 | 47 | pub const ENV_NACOS_CLIENT_AUTH_PASSWORD: &str = "NACOS_CLIENT_PASSWORD"; 48 | 49 | pub const ENV_NACOS_CLIENT_AUTH_ACCESS_KEY: &str = "NACOS_CLIENT_ACCESS_KEY"; 50 | 51 | pub const ENV_NACOS_CLIENT_AUTH_ACCESS_SECRET: &str = "NACOS_CLIENT_SECRET_KEY"; 52 | 53 | pub const ENV_NACOS_CLIENT_SIGN_REGION_ID: &str = "NACOS_CLIENT_SIGN_REGION_ID"; 54 | 55 | /// env `NACOS_CLIENT_NAMING_PUSH_EMPTY_PROTECTION`, default true 56 | pub const ENV_NACOS_CLIENT_NAMING_PUSH_EMPTY_PROTECTION: &str = 57 | "NACOS_CLIENT_NAMING_PUSH_EMPTY_PROTECTION"; 58 | 59 | /// env `NACOS_CLIENT_NAMING_LOAD_CACHE_AT_START`, default false 60 | pub const ENV_NACOS_CLIENT_NAMING_LOAD_CACHE_AT_START: &str = 61 | "NACOS_CLIENT_NAMING_LOAD_CACHE_AT_START"; 62 | -------------------------------------------------------------------------------- /src/api/error.rs: -------------------------------------------------------------------------------- 1 | // Licensed to the Apache Software Foundation (ASF) under one or more 2 | // contributor license agreements. See the NOTICE file distributed with 3 | // this work for additional information regarding copyright ownership. 4 | // The ASF licenses this file to You under the Apache License, Version 2.0 5 | // (the "License"); you may not use this file except in compliance with 6 | // the License. You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | use tower::BoxError; 18 | 19 | /// Nacos Sdk Rust Result. 20 | pub type Result = std::result::Result; 21 | 22 | /// Nacos Sdk Rust Error. 23 | #[derive(Debug, thiserror::Error)] 24 | pub enum Error { 25 | #[error("Serialization failed: {0}")] 26 | Serialization(#[from] serde_json::Error), 27 | 28 | #[error("get result failed: {0}")] 29 | ErrResult(String), 30 | 31 | #[error("param:{0} with error_message:{1}")] 32 | InvalidParam(String, String), 33 | 34 | #[error("request_id:{0:?} ret_code:{1} error_code:{2} message:{3:?}")] 35 | ErrResponse(Option, i32, i32, Option), 36 | 37 | /// Config not found. 38 | #[cfg(feature = "config")] 39 | #[error("config not found: {0}")] 40 | ConfigNotFound(String), 41 | 42 | /// Config query conflict, it is being modified, please try later. 43 | #[cfg(feature = "config")] 44 | #[error("config query conflict: {0}")] 45 | ConfigQueryConflict(String), 46 | 47 | #[error("remote client shutdown failed: {0}")] 48 | ClientShutdown(String), 49 | 50 | #[error("remote client unhealthy failed: {0}")] 51 | ClientUnhealthy(String), 52 | 53 | #[error("tonic grpc transport error: {0}")] 54 | TonicGrpcTransport(#[from] tonic::transport::Error), 55 | 56 | #[error("tonic grpc status error: {0}")] 57 | TonicGrpcStatus(#[from] tonic::Status), 58 | 59 | #[error("grpc request error: {0}")] 60 | GrpcBufferRequest(#[from] BoxError), 61 | 62 | #[error("no available server")] 63 | NoAvailableServer, 64 | 65 | #[error("Wrong server address: {0}")] 66 | WrongServerAddress(String), 67 | 68 | #[error("Exceeded maximum retry attempts: {0}")] 69 | MaxRetriesExceeded(u32), 70 | } 71 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | pub mod error; 3 | pub mod props; 4 | 5 | /// Plugin define. 6 | pub mod plugin; 7 | 8 | /// Api of Config 9 | #[cfg(feature = "config")] 10 | pub mod config; 11 | 12 | /// Api of Naming 13 | #[cfg(feature = "naming")] 14 | pub mod naming; 15 | -------------------------------------------------------------------------------- /src/api/plugin/auth/auth_by_http.rs: -------------------------------------------------------------------------------- 1 | use arc_swap::ArcSwap; 2 | use rand::Rng; 3 | use std::ops::{Add, Deref}; 4 | use std::sync::Arc; 5 | use tokio::time::{Duration, Instant}; 6 | 7 | use crate::api::plugin::{AuthContext, AuthPlugin, LoginIdentityContext}; 8 | 9 | use super::RequestResource; 10 | 11 | pub const USERNAME: &str = "username"; 12 | 13 | pub const PASSWORD: &str = "password"; 14 | 15 | pub(crate) const ACCESS_TOKEN: &str = "accessToken"; 16 | 17 | #[allow(dead_code)] 18 | pub(crate) const TOKEN_TTL: &str = "tokenTtl"; 19 | 20 | /// Http login AuthPlugin. 21 | pub struct HttpLoginAuthPlugin { 22 | login_identity: ArcSwap, 23 | next_login_refresh: ArcSwap, 24 | } 25 | 26 | impl Default for HttpLoginAuthPlugin { 27 | fn default() -> Self { 28 | Self { 29 | login_identity: ArcSwap::from_pointee(LoginIdentityContext::default()), 30 | next_login_refresh: ArcSwap::from_pointee(Instant::now()), 31 | } 32 | } 33 | } 34 | 35 | #[async_trait::async_trait] 36 | impl AuthPlugin for HttpLoginAuthPlugin { 37 | async fn login(&self, server_list: Vec, auth_context: AuthContext) { 38 | let now_instant = Instant::now(); 39 | if now_instant.le(self.next_login_refresh.load().deref()) { 40 | tracing::debug!("Http login return because now_instant lte next_login_refresh."); 41 | return; 42 | } 43 | 44 | let username = auth_context.params.get(USERNAME).unwrap().to_owned(); 45 | let password = auth_context.params.get(PASSWORD).unwrap().to_owned(); 46 | 47 | let server_addr = { 48 | // random one 49 | server_list 50 | .get(rand::thread_rng().gen_range(0..server_list.len())) 51 | .unwrap() 52 | .to_string() 53 | }; 54 | 55 | let scheme = if cfg!(feature = "tls") { 56 | "https" 57 | } else { 58 | "http" 59 | }; 60 | let login_url = format!("{scheme}://{server_addr}/nacos/v1/auth/login"); 61 | 62 | tracing::debug!("Http login with username={username},password={password}"); 63 | 64 | let login_response = { 65 | let resp = reqwest::Client::new() 66 | .post(login_url) 67 | .query(&[(USERNAME, username), (PASSWORD, password)]) 68 | .send() 69 | .await; 70 | tracing::debug!("Http login resp={resp:?}"); 71 | 72 | match resp { 73 | Err(e) => { 74 | tracing::error!("Http login error, send response failed, err={e:?}"); 75 | None 76 | } 77 | Ok(resp) => { 78 | let resp_text = resp.text().await.unwrap(); 79 | let resp_obj = serde_json::from_str::(&resp_text); 80 | match resp_obj { 81 | Err(e) => { 82 | tracing::error!("Http login error, resp_text={resp_text}, err={e:?}"); 83 | None 84 | } 85 | Ok(resp_obj) => Some(resp_obj), 86 | } 87 | } 88 | } 89 | }; 90 | 91 | if let Some(login_response) = login_response { 92 | let delay_sec = login_response.token_ttl / 10; 93 | let new_login_identity = Arc::new( 94 | LoginIdentityContext::default() 95 | .add_context(ACCESS_TOKEN, login_response.access_token), 96 | ); 97 | self.login_identity.store(new_login_identity); 98 | 99 | self.next_login_refresh 100 | .store(Arc::new(Instant::now().add(Duration::from_secs(delay_sec)))); 101 | } 102 | } 103 | 104 | fn get_login_identity(&self, _: RequestResource) -> LoginIdentityContext { 105 | self.login_identity.load().deref().deref().to_owned() 106 | } 107 | } 108 | 109 | #[derive(Default, serde::Deserialize)] 110 | #[serde(rename_all = "camelCase")] 111 | struct HttpLoginResponse { 112 | access_token: String, 113 | token_ttl: u64, 114 | } 115 | 116 | #[cfg(test)] 117 | mod tests { 118 | use crate::api::plugin::{AuthContext, AuthPlugin, HttpLoginAuthPlugin, RequestResource}; 119 | 120 | #[tokio::test] 121 | #[ignore] 122 | async fn test_http_login_auth_plugin() { 123 | tracing_subscriber::fmt() 124 | .with_max_level(tracing::Level::DEBUG) 125 | .init(); 126 | 127 | let http_auth_plugin = HttpLoginAuthPlugin::default(); 128 | let server_list = vec!["127.0.0.1:8848".to_string()]; 129 | 130 | let auth_context = AuthContext::default() 131 | .add_param(crate::api::plugin::USERNAME, "nacos") 132 | .add_param(crate::api::plugin::PASSWORD, "nacos"); 133 | 134 | http_auth_plugin 135 | .login(server_list.clone(), auth_context.clone()) 136 | .await; 137 | let login_identity_1 = http_auth_plugin.get_login_identity(RequestResource::default()); 138 | assert_eq!(login_identity_1.contexts.len(), 1); 139 | 140 | tokio::time::sleep(tokio::time::Duration::from_millis(111)).await; 141 | 142 | http_auth_plugin.login(server_list, auth_context).await; 143 | let login_identity_2 = http_auth_plugin.get_login_identity(RequestResource::default()); 144 | assert_eq!(login_identity_1.contexts, login_identity_2.contexts) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/api/plugin/auth/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "auth-by-http")] 2 | mod auth_by_http; 3 | #[cfg(feature = "auth-by-http")] 4 | pub use auth_by_http::*; 5 | 6 | #[cfg(feature = "auth-by-aliyun")] 7 | mod auth_by_aliyun_ram; 8 | #[cfg(feature = "auth-by-aliyun")] 9 | pub use auth_by_aliyun_ram::*; 10 | 11 | use std::{collections::HashMap, sync::Arc, thread, time::Duration}; 12 | use tokio::{sync::oneshot, time::sleep}; 13 | use tracing::{Instrument, debug, debug_span, info}; 14 | 15 | use crate::common::executor; 16 | 17 | /// Auth plugin in Client. 18 | /// This api may change in the future, please forgive me if you customize the implementation. 19 | #[async_trait::async_trait] 20 | pub trait AuthPlugin: Send + Sync { 21 | /// Login with [`AuthContext`], Note that this method will be scheduled continuously. 22 | async fn login(&self, server_list: Vec, auth_context: AuthContext); 23 | 24 | /// Get the [`LoginIdentityContext`]. 25 | fn get_login_identity(&self, resource: RequestResource) -> LoginIdentityContext; 26 | } 27 | 28 | #[derive(Clone, Default)] 29 | pub struct AuthContext { 30 | pub(crate) params: HashMap, 31 | } 32 | 33 | impl AuthContext { 34 | /// Add the param. 35 | pub fn add_param(mut self, key: impl Into, val: impl Into) -> Self { 36 | self.params.insert(key.into(), val.into()); 37 | self 38 | } 39 | 40 | /// Add the params. 41 | pub fn add_params(mut self, map: HashMap) -> Self { 42 | self.params.extend(map); 43 | self 44 | } 45 | } 46 | 47 | #[derive(Clone, Default)] 48 | pub struct LoginIdentityContext { 49 | pub(crate) contexts: HashMap, 50 | } 51 | 52 | impl LoginIdentityContext { 53 | /// Add the context. 54 | pub fn add_context(mut self, key: impl Into, val: impl Into) -> Self { 55 | self.contexts.insert(key.into(), val.into()); 56 | self 57 | } 58 | 59 | /// Add the contexts. 60 | pub fn add_contexts(mut self, map: HashMap) -> Self { 61 | self.contexts.extend(map); 62 | self 63 | } 64 | } 65 | 66 | /// Noop AuthPlugin. 67 | #[derive(Default)] 68 | pub(crate) struct NoopAuthPlugin { 69 | login_identity: LoginIdentityContext, 70 | } 71 | 72 | #[async_trait::async_trait] 73 | impl AuthPlugin for NoopAuthPlugin { 74 | #[allow(unused_variables)] 75 | async fn login(&self, server_list: Vec, auth_context: AuthContext) { 76 | // noop 77 | } 78 | 79 | fn get_login_identity(&self, _: RequestResource) -> LoginIdentityContext { 80 | // noop 81 | self.login_identity.clone() 82 | } 83 | } 84 | 85 | pub fn init_auth_plugin( 86 | auth_plugin: Arc, 87 | server_list: Vec, 88 | auth_params: HashMap, 89 | id: String, 90 | ) { 91 | let (tx, rx) = oneshot::channel::<()>(); 92 | executor::spawn( 93 | async move { 94 | info!("init auth task"); 95 | let auth_context = AuthContext::default().add_params(auth_params); 96 | auth_plugin 97 | .login(server_list.clone(), auth_context.clone()) 98 | .in_current_span() 99 | .await; 100 | info!("init auth finish"); 101 | let _ = tx.send(()); 102 | 103 | info!("auth plugin task start."); 104 | loop { 105 | auth_plugin 106 | .login(server_list.clone(), auth_context.clone()) 107 | .in_current_span() 108 | .await; 109 | debug!("auth_plugin schedule at fixed delay"); 110 | sleep(Duration::from_secs(30)).await; 111 | } 112 | } 113 | .instrument(debug_span!("auth_task", id = id)), 114 | ); 115 | 116 | let wait_ret = thread::spawn(move || rx.blocking_recv()); 117 | 118 | let _ = wait_ret.join().unwrap(); 119 | } 120 | 121 | #[derive(Debug, Default)] 122 | pub struct RequestResource { 123 | pub request_type: String, 124 | pub namespace: Option, 125 | pub group: Option, 126 | pub resource: Option, 127 | } 128 | -------------------------------------------------------------------------------- /src/api/plugin/config_filter.rs: -------------------------------------------------------------------------------- 1 | /// ConfigFilter 2 | #[async_trait::async_trait] 3 | pub trait ConfigFilter: Send + Sync { 4 | /// Filter the config_req or config_resp. You can modify their values as needed. 5 | /// 6 | /// [`ConfigReq`] and [`ConfigResp`] will not be [`None`] at the same time. 7 | /// Only one of [`ConfigReq`] and [`ConfigResp`] is [`Some`]. 8 | async fn filter( 9 | &self, 10 | config_req: Option<&mut ConfigReq>, 11 | config_resp: Option<&mut ConfigResp>, 12 | ); 13 | } 14 | 15 | /// ConfigReq for [`ConfigFilter`] 16 | pub struct ConfigReq { 17 | /// DataId 18 | pub data_id: String, 19 | /// Group 20 | pub group: String, 21 | /// Namespace/Tenant 22 | pub namespace: String, 23 | /// Content 24 | pub content: String, 25 | /// Content's Encrypted Data Key. 26 | pub encrypted_data_key: String, 27 | } 28 | 29 | impl ConfigReq { 30 | pub fn new( 31 | data_id: String, 32 | group: String, 33 | namespace: String, 34 | content: String, 35 | encrypted_data_key: String, 36 | ) -> Self { 37 | ConfigReq { 38 | data_id, 39 | group, 40 | namespace, 41 | content, 42 | encrypted_data_key, 43 | } 44 | } 45 | } 46 | 47 | /// ConfigResp for [`ConfigFilter`] 48 | pub struct ConfigResp { 49 | /// DataId 50 | pub data_id: String, 51 | /// Group 52 | pub group: String, 53 | /// Namespace/Tenant 54 | pub namespace: String, 55 | /// Content 56 | pub content: String, 57 | /// Content's Encrypted Data Key. 58 | pub encrypted_data_key: String, 59 | } 60 | 61 | impl ConfigResp { 62 | pub fn new( 63 | data_id: String, 64 | group: String, 65 | namespace: String, 66 | content: String, 67 | encrypted_data_key: String, 68 | ) -> Self { 69 | ConfigResp { 70 | data_id, 71 | group, 72 | namespace, 73 | content, 74 | encrypted_data_key, 75 | } 76 | } 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use crate::api::plugin::ConfigFilter; 82 | use crate::api::plugin::config_filter::{ConfigReq, ConfigResp}; 83 | 84 | struct TestConfigEncryptionFilter; 85 | 86 | impl TestConfigEncryptionFilter { 87 | fn encrypt(&self, secret_key: &String, content: &String) -> String { 88 | secret_key.to_owned() + content 89 | } 90 | 91 | fn decrypt(&self, secret_key: &String, content: &String) -> String { 92 | content.replace(secret_key, "") 93 | } 94 | } 95 | 96 | #[async_trait::async_trait] 97 | impl ConfigFilter for TestConfigEncryptionFilter { 98 | async fn filter( 99 | &self, 100 | config_req: Option<&mut ConfigReq>, 101 | config_resp: Option<&mut ConfigResp>, 102 | ) { 103 | if let Some(config_req) = config_req { 104 | if !config_req.encrypted_data_key.is_empty() { 105 | config_req.content = 106 | self.encrypt(&config_req.encrypted_data_key, &config_req.content); 107 | } 108 | } 109 | 110 | if let Some(config_resp) = config_resp { 111 | if !config_resp.encrypted_data_key.is_empty() { 112 | config_resp.content = 113 | self.decrypt(&config_resp.encrypted_data_key, &config_resp.content); 114 | } 115 | } 116 | } 117 | } 118 | 119 | #[tokio::test] 120 | async fn test_config_filter() { 121 | let config_filter = TestConfigEncryptionFilter {}; 122 | 123 | let (data_id, group, namespace, content, encrypted_data_key) = ( 124 | "D".to_string(), 125 | "G".to_string(), 126 | "N".to_string(), 127 | "C".to_string(), 128 | "E".to_string(), 129 | ); 130 | 131 | let mut config_req = ConfigReq::new( 132 | data_id.clone(), 133 | group.clone(), 134 | namespace.clone(), 135 | content.clone(), 136 | encrypted_data_key.clone(), 137 | ); 138 | config_filter.filter(Some(&mut config_req), None).await; 139 | 140 | assert_eq!(config_req.content, encrypted_data_key + content.as_str()); 141 | 142 | let mut config_resp = ConfigResp::new( 143 | config_req.data_id.clone(), 144 | config_req.group.clone(), 145 | config_req.namespace.clone(), 146 | config_req.content.clone(), 147 | config_req.encrypted_data_key.clone(), 148 | ); 149 | config_filter.filter(None, Some(&mut config_resp)).await; 150 | 151 | assert_eq!(config_resp.content, content); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/api/plugin/encryption.rs: -------------------------------------------------------------------------------- 1 | use crate::api::plugin::{ConfigFilter, ConfigReq, ConfigResp}; 2 | 3 | /** 4 | * For example:cipher-AES-dataId. 5 | */ 6 | pub const DEFAULT_CIPHER_PREFIX: &str = "cipher-"; 7 | 8 | pub const DEFAULT_CIPHER_SPLIT: &str = "-"; 9 | 10 | /// EncryptionPlugin for Config. 11 | #[async_trait::async_trait] 12 | pub trait EncryptionPlugin: Send + Sync { 13 | /** 14 | * Whether need to do cipher. 15 | * 16 | * e.g. data_id = "cipher-AES-dataId" 17 | */ 18 | fn need_cipher(&self, data_id: &str) -> bool { 19 | data_id.starts_with(DEFAULT_CIPHER_PREFIX) 20 | && self 21 | .parse_algorithm_name(data_id) 22 | .eq(&self.algorithm_name()) 23 | } 24 | 25 | /** 26 | * Parse encryption algorithm name. 27 | * 28 | * @param data_id data_id 29 | * @return algorithm name 30 | */ 31 | fn parse_algorithm_name(&self, data_id: &str) -> String { 32 | data_id 33 | .split(DEFAULT_CIPHER_SPLIT) 34 | .nth(1) 35 | .unwrap() 36 | .to_string() 37 | } 38 | 39 | /** 40 | * Encrypt interface. 41 | * 42 | * @param secret_key secret key 43 | * @param content content unencrypted 44 | * @return encrypt value 45 | */ 46 | async fn encrypt(&self, secret_key: &str, content: &str) -> String; 47 | 48 | /** 49 | * Decrypt interface. 50 | * 51 | * @param secret_key secret key 52 | * @param content encrypted 53 | * @return decrypt value 54 | */ 55 | async fn decrypt(&self, secret_key: &str, content: &str) -> String; 56 | 57 | /** 58 | * Generate secret key. It only be known by you. 59 | * 60 | * @return Secret key 61 | */ 62 | async fn generate_secret_key(&self) -> String; 63 | 64 | /** 65 | * Algorithm name. e.g. AES,AES128,AES256,DES,3DES,... 66 | * 67 | * @return name 68 | */ 69 | fn algorithm_name(&self) -> String; 70 | 71 | /** 72 | * Encrypt secret Key. It will be transmitted in the network. 73 | * 74 | * @param secretKey secretKey 75 | * @return encrypted secretKey 76 | */ 77 | async fn encrypt_secret_key(&self, secret_key: &str) -> String; 78 | 79 | /** 80 | * Decrypt secret Key. 81 | * 82 | * @param secret_key secretKey 83 | * @return decrypted secretKey 84 | */ 85 | async fn decrypt_secret_key(&self, secret_key: &str) -> String; 86 | } 87 | 88 | /// ConfigEncryptionFilter handle with [`EncryptionPlugin`] 89 | pub struct ConfigEncryptionFilter { 90 | encryption_plugins: Vec>, 91 | } 92 | 93 | impl ConfigEncryptionFilter { 94 | pub fn new(encryption_plugins: Vec>) -> Self { 95 | Self { encryption_plugins } 96 | } 97 | } 98 | 99 | #[async_trait::async_trait] 100 | impl ConfigFilter for ConfigEncryptionFilter { 101 | async fn filter( 102 | &self, 103 | config_req: Option<&mut ConfigReq>, 104 | config_resp: Option<&mut ConfigResp>, 105 | ) { 106 | // Publish configuration, encrypt 107 | if let Some(config_req) = config_req { 108 | for plugin in &self.encryption_plugins { 109 | if !plugin.need_cipher(&config_req.data_id) { 110 | continue; 111 | } 112 | 113 | let secret_key = plugin.generate_secret_key().await; 114 | let encrypted_content = plugin.encrypt(&secret_key, &config_req.content).await; 115 | let encrypted_secret_key = plugin.encrypt_secret_key(&secret_key).await; 116 | 117 | // set encrypted data. 118 | config_req.encrypted_data_key = encrypted_secret_key; 119 | config_req.content = encrypted_content; 120 | break; 121 | } 122 | } 123 | 124 | // Get configuration, decrypt 125 | if let Some(config_resp) = config_resp { 126 | if !config_resp.encrypted_data_key.is_empty() { 127 | for plugin in &self.encryption_plugins { 128 | if !plugin.need_cipher(&config_resp.data_id) { 129 | continue; 130 | } 131 | 132 | // get encrypted data. 133 | let encrypted_secret_key = &config_resp.encrypted_data_key; 134 | let encrypted_content = &config_resp.content; 135 | 136 | let decrypted_secret_key = 137 | plugin.decrypt_secret_key(encrypted_secret_key).await; 138 | let decrypted_content = plugin 139 | .decrypt(&decrypted_secret_key, encrypted_content) 140 | .await; 141 | 142 | // set decrypted data. 143 | config_resp.content = decrypted_content; 144 | break; 145 | } 146 | } 147 | } 148 | } 149 | } 150 | 151 | #[cfg(test)] 152 | mod tests { 153 | use crate::api::plugin::config_filter::{ConfigReq, ConfigResp}; 154 | use crate::api::plugin::encryption::DEFAULT_CIPHER_PREFIX; 155 | use crate::api::plugin::{ConfigEncryptionFilter, ConfigFilter, EncryptionPlugin}; 156 | 157 | struct TestEncryptionPlugin; 158 | 159 | #[async_trait::async_trait] 160 | impl EncryptionPlugin for TestEncryptionPlugin { 161 | async fn encrypt(&self, secret_key: &str, content: &str) -> String { 162 | secret_key.to_owned() + content 163 | } 164 | 165 | async fn decrypt(&self, secret_key: &str, content: &str) -> String { 166 | content.replace(secret_key, "") 167 | } 168 | 169 | async fn generate_secret_key(&self) -> String { 170 | "secret-key".to_string() 171 | } 172 | 173 | fn algorithm_name(&self) -> String { 174 | "TEST".to_string() 175 | } 176 | 177 | async fn encrypt_secret_key(&self, secret_key: &str) -> String { 178 | "crypt_".to_owned() + secret_key 179 | } 180 | 181 | async fn decrypt_secret_key(&self, secret_key: &str) -> String { 182 | secret_key.replace("crypt_", "") 183 | } 184 | } 185 | 186 | #[tokio::test] 187 | async fn test_config_encryption_filters_empty() { 188 | let config_encryption_filter = ConfigEncryptionFilter::new(vec![]); 189 | 190 | let (data_id, group, namespace, content, encrypted_data_key) = ( 191 | "D".to_string(), 192 | "G".to_string(), 193 | "N".to_string(), 194 | "C".to_string(), 195 | "".to_string(), 196 | ); 197 | 198 | let mut config_req = ConfigReq::new( 199 | data_id.clone(), 200 | group.clone(), 201 | namespace.clone(), 202 | content.clone(), 203 | encrypted_data_key.clone(), 204 | ); 205 | config_encryption_filter 206 | .filter(Some(&mut config_req), None) 207 | .await; 208 | 209 | assert_eq!(config_req.content, encrypted_data_key + content.as_str()); 210 | 211 | let mut config_resp = ConfigResp::new( 212 | config_req.data_id.clone(), 213 | config_req.group.clone(), 214 | config_req.namespace.clone(), 215 | config_req.content.clone(), 216 | config_req.encrypted_data_key.clone(), 217 | ); 218 | config_encryption_filter 219 | .filter(None, Some(&mut config_resp)) 220 | .await; 221 | 222 | assert_eq!(config_resp.content, content); 223 | } 224 | 225 | #[tokio::test] 226 | async fn test_config_encryption_filters() { 227 | let config_encryption_filter = 228 | ConfigEncryptionFilter::new(vec![Box::new(TestEncryptionPlugin {})]); 229 | 230 | let (data_id, group, namespace, content, encrypted_data_key) = ( 231 | DEFAULT_CIPHER_PREFIX.to_owned() + "-TEST-D", 232 | "G".to_string(), 233 | "N".to_string(), 234 | "C".to_string(), 235 | "E".to_string(), 236 | ); 237 | 238 | let mut config_req = ConfigReq::new( 239 | data_id.clone(), 240 | group.clone(), 241 | namespace.clone(), 242 | content.clone(), 243 | encrypted_data_key.clone(), 244 | ); 245 | config_encryption_filter 246 | .filter(Some(&mut config_req), None) 247 | .await; 248 | 249 | let mut config_resp = ConfigResp::new( 250 | config_req.data_id.clone(), 251 | config_req.group.clone(), 252 | config_req.namespace.clone(), 253 | config_req.content.clone(), 254 | config_req.encrypted_data_key.clone(), 255 | ); 256 | config_encryption_filter 257 | .filter(None, Some(&mut config_resp)) 258 | .await; 259 | 260 | assert_eq!(config_resp.content, content); 261 | } 262 | 263 | #[tokio::test] 264 | async fn test_config_encryption_filters_not_need_cipher() { 265 | let config_encryption_filter = 266 | ConfigEncryptionFilter::new(vec![Box::new(TestEncryptionPlugin {})]); 267 | 268 | let (data_id, group, namespace, content, encrypted_data_key) = ( 269 | "D".to_string(), 270 | "G".to_string(), 271 | "N".to_string(), 272 | "C".to_string(), 273 | "E".to_string(), 274 | ); 275 | 276 | let mut config_req = ConfigReq::new( 277 | data_id.clone(), 278 | group.clone(), 279 | namespace.clone(), 280 | content.clone(), 281 | encrypted_data_key.clone(), 282 | ); 283 | config_encryption_filter 284 | .filter(Some(&mut config_req), None) 285 | .await; 286 | 287 | let mut config_resp = ConfigResp::new( 288 | config_req.data_id.clone(), 289 | config_req.group.clone(), 290 | config_req.namespace.clone(), 291 | config_req.content.clone(), 292 | config_req.encrypted_data_key.clone(), 293 | ); 294 | config_encryption_filter 295 | .filter(None, Some(&mut config_resp)) 296 | .await; 297 | 298 | assert_eq!(config_resp.content, content); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/api/plugin/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "config")] 2 | mod config_filter; 3 | #[cfg(feature = "config")] 4 | pub use config_filter::*; 5 | 6 | #[cfg(feature = "config")] 7 | mod encryption; 8 | #[cfg(feature = "config")] 9 | pub use encryption::*; 10 | 11 | /// Auth login plugin. 12 | mod auth; 13 | pub use auth::*; 14 | -------------------------------------------------------------------------------- /src/common/cache/disk.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, collections::HashMap, io::BufReader, path::PathBuf}; 2 | 3 | use async_trait::async_trait; 4 | use serde::de; 5 | use tokio::{ 6 | fs::{OpenOptions, remove_file}, 7 | io::AsyncWriteExt, 8 | }; 9 | use tracing::{debug, instrument, warn}; 10 | 11 | use super::Store; 12 | 13 | pub(crate) struct DiskStore { 14 | disk_path: PathBuf, 15 | } 16 | 17 | impl DiskStore { 18 | pub(crate) fn new(disk_path: PathBuf) -> Self { 19 | Self { disk_path } 20 | } 21 | } 22 | 23 | #[async_trait] 24 | impl Store for DiskStore 25 | where 26 | V: de::DeserializeOwned, 27 | { 28 | fn name(&self) -> Cow<'_, str> { 29 | Cow::from("disk store") 30 | } 31 | 32 | fn load(&mut self) -> HashMap { 33 | let mut default_map = HashMap::default(); 34 | 35 | let disk_path_display = self.disk_path.display(); 36 | 37 | if !self.disk_path.exists() { 38 | warn!("disk path is not exists, trying create it."); 39 | let ret = std::fs::create_dir_all(&self.disk_path); 40 | if let Err(e) = ret { 41 | warn!("create directory {} failed {}.", disk_path_display, e); 42 | return default_map; 43 | } 44 | } 45 | 46 | if !self.disk_path.is_dir() { 47 | warn!("disk path is not a directory. {}", disk_path_display); 48 | return default_map; 49 | } 50 | 51 | let dir_iter = std::fs::read_dir(&self.disk_path); 52 | if let Err(e) = dir_iter { 53 | warn!( 54 | "read directory {} failed {}, trying create a empty directory", 55 | disk_path_display, e 56 | ); 57 | return default_map; 58 | } 59 | 60 | let dir_iter = dir_iter.unwrap(); 61 | 62 | for entry in dir_iter { 63 | if entry.is_err() { 64 | // skip 65 | debug!("entry error"); 66 | continue; 67 | } 68 | 69 | let entry = entry.unwrap(); 70 | let path = entry.path(); 71 | if path.is_dir() { 72 | // directory skip 73 | continue; 74 | } 75 | let file = std::fs::File::open(&path); 76 | 77 | if let Err(e) = file { 78 | warn!("cannot open file {}, {}", path.display(), e); 79 | continue; 80 | } 81 | let file = file.unwrap(); 82 | let reader = std::io::BufReader::new(file); 83 | 84 | let ret = serde_json::from_reader::, V>(reader); 85 | if let Err(e) = ret { 86 | warn!("cannot deserialize {}, {}.", path.display(), e); 87 | continue; 88 | } 89 | 90 | let value = ret.unwrap(); 91 | let key = path.file_name(); 92 | if key.is_none() { 93 | // skip 94 | continue; 95 | } 96 | 97 | let key = key.unwrap(); 98 | let key: String = key.to_string_lossy().into(); 99 | 100 | default_map.insert(key, value); 101 | } 102 | 103 | default_map 104 | } 105 | 106 | #[instrument(fields(key = key), skip_all)] 107 | async fn save(&mut self, key: &str, value: Vec) { 108 | let mut write_path = PathBuf::from(&self.disk_path); 109 | write_path.push(key); 110 | 111 | let write_path_display = write_path.display(); 112 | debug!("save {}", write_path_display); 113 | 114 | let file = OpenOptions::new() 115 | .write(true) 116 | .create(true) 117 | .truncate(true) 118 | .open(write_path.as_path()) 119 | .await; 120 | 121 | if let Err(e) = file { 122 | debug!("open file {} failed {}.", write_path_display, e); 123 | return; 124 | } 125 | 126 | let mut file = file.unwrap(); 127 | let ret = file.write(&value).await; 128 | 129 | if let Err(e) = ret { 130 | let str = String::from_utf8(value); 131 | if str.is_ok() { 132 | warn!( 133 | "the data {} cannot write to file {}, {}.", 134 | str.unwrap(), 135 | write_path_display, 136 | e 137 | ); 138 | } else { 139 | warn!( 140 | "write to file {} failed {} and the data cannot convert to string.", 141 | write_path_display, e 142 | ); 143 | } 144 | return; 145 | } 146 | } 147 | 148 | #[instrument(fields(key = key), skip_all)] 149 | async fn remove(&mut self, key: &str) { 150 | let mut delete_path = PathBuf::from(&self.disk_path); 151 | delete_path.push(key); 152 | 153 | let delete_path_display = delete_path.display(); 154 | debug!("remove {}", delete_path_display); 155 | 156 | if !delete_path.exists() { 157 | return; 158 | } 159 | 160 | let ret = remove_file(&delete_path).await; 161 | 162 | if let Err(e) = ret { 163 | warn!("delete file {} failed {}.", delete_path_display, e); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/common/executor/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::api::error::Result; 2 | use futures::Future; 3 | use tokio::{ 4 | runtime::{Builder, Runtime}, 5 | task::JoinHandle, 6 | time::{Duration, interval, sleep}, 7 | }; 8 | use tracing::{Instrument, error}; 9 | 10 | static COMMON_THREAD_CORES: std::sync::LazyLock = std::sync::LazyLock::new(|| { 11 | std::env::var(crate::api::constants::ENV_NACOS_CLIENT_COMMON_THREAD_CORES) 12 | .ok() 13 | .and_then(|v| v.parse::().ok().filter(|n| *n > 0)) 14 | .unwrap_or(1) 15 | }); 16 | 17 | static RT: std::sync::LazyLock = std::sync::LazyLock::new(|| { 18 | Builder::new_multi_thread() 19 | .enable_all() 20 | .thread_name("nacos-client-thread-pool") 21 | .worker_threads(*COMMON_THREAD_CORES) 22 | .build() 23 | .unwrap() 24 | }); 25 | 26 | pub(crate) fn spawn(future: F) -> JoinHandle 27 | where 28 | F: Future + Send + 'static, 29 | F::Output: Send + 'static, 30 | { 31 | RT.spawn(future) 32 | } 33 | 34 | #[allow(dead_code)] 35 | pub(crate) fn schedule(future: F, delay: Duration) -> JoinHandle 36 | where 37 | F: Future + Send + 'static, 38 | F::Output: Send + 'static, 39 | { 40 | RT.spawn(async move { 41 | sleep(delay).await; 42 | future.await 43 | }) 44 | } 45 | 46 | #[allow(dead_code)] 47 | pub(crate) fn schedule_at_fixed_rate( 48 | task: impl Fn() -> Result<()> + Send + Sync + 'static, 49 | duration: Duration, 50 | ) -> JoinHandle<()> { 51 | RT.spawn( 52 | async move { 53 | loop { 54 | let ret = async { task() }.await; 55 | if let Err(e) = ret { 56 | error!("schedule_at_fixed_rate occur an error: {e}"); 57 | break; 58 | } 59 | sleep(duration).await; 60 | } 61 | } 62 | .in_current_span(), 63 | ) 64 | } 65 | 66 | #[allow(dead_code)] 67 | pub(crate) fn schedule_at_fixed_delay( 68 | task: impl Fn() -> Result<()> + Send + Sync + 'static, 69 | duration: Duration, 70 | ) -> JoinHandle<()> { 71 | RT.spawn( 72 | async move { 73 | let mut interval = interval(duration); 74 | loop { 75 | interval.tick().await; 76 | let ret = async { task() }.await; 77 | if let Err(e) = ret { 78 | error!("schedule_at_fixed_delay occur an error: {e}"); 79 | break; 80 | } 81 | } 82 | } 83 | .in_current_span(), 84 | ) 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | 90 | use super::*; 91 | use crate::api::constants::ENV_NACOS_CLIENT_COMMON_THREAD_CORES; 92 | 93 | #[test] 94 | fn test_common_thread_cores() { 95 | let num_cpus = std::env::var(ENV_NACOS_CLIENT_COMMON_THREAD_CORES) 96 | .ok() 97 | .and_then(|v| v.parse::().ok().filter(|n| *n > 0)) 98 | .unwrap_or(std::thread::available_parallelism().unwrap().get()); 99 | assert!(num_cpus > 0); 100 | 101 | unsafe { 102 | std::env::set_var(ENV_NACOS_CLIENT_COMMON_THREAD_CORES, "4"); 103 | } 104 | let num_cpus = std::env::var(ENV_NACOS_CLIENT_COMMON_THREAD_CORES) 105 | .ok() 106 | .and_then(|v| v.parse::().ok().filter(|n| *n > 0)) 107 | .unwrap_or(std::thread::available_parallelism().unwrap().get()); 108 | assert_eq!(num_cpus, 4); 109 | } 110 | 111 | #[test] 112 | fn test_spawn() { 113 | let handler = spawn(async { 114 | println!("test spawn task"); 115 | 5 116 | }); 117 | let ret = RT.block_on(handler); 118 | let ret = ret.unwrap(); 119 | assert_eq!(ret, 5); 120 | } 121 | 122 | #[test] 123 | fn test_schedule() { 124 | let handler = schedule( 125 | async move { 126 | println!("test schedule task"); 127 | 5 128 | }, 129 | tokio::time::Duration::from_secs(1), 130 | ); 131 | 132 | let ret = RT.block_on(handler); 133 | let ret = ret.unwrap(); 134 | assert_eq!(ret, 5); 135 | } 136 | 137 | #[test] 138 | fn test_schedule_at_fixed_delay() { 139 | let handler = schedule_at_fixed_delay( 140 | || { 141 | println!("test schedule at fixed delay"); 142 | Ok(()) 143 | }, 144 | tokio::time::Duration::from_secs(1), 145 | ); 146 | 147 | std::thread::sleep(core::time::Duration::from_secs(3)); 148 | handler.abort(); 149 | std::thread::sleep(core::time::Duration::from_secs(5)); 150 | println!("task has been canceled!") 151 | } 152 | 153 | #[test] 154 | fn test_schedule_at_fixed_rate() { 155 | let handler = schedule_at_fixed_rate( 156 | || { 157 | println!("test schedule at fixed rate"); 158 | Ok(()) 159 | }, 160 | tokio::time::Duration::from_secs(1), 161 | ); 162 | 163 | std::thread::sleep(core::time::Duration::from_secs(3)); 164 | handler.abort(); 165 | std::thread::sleep(core::time::Duration::from_secs(5)); 166 | println!("task has been canceled!") 167 | } 168 | 169 | #[test] 170 | fn test_spawn_hundred_task() { 171 | for i in 1..100 { 172 | let _ = spawn(async move { 173 | println!("test_spawn_thousand_task spawn {i}"); 174 | }); 175 | } 176 | for j in 1..100 { 177 | let _ = schedule( 178 | async move { 179 | println!("test_spawn_thousand_task schedule {j}"); 180 | }, 181 | Duration::from_millis(j), 182 | ); 183 | } 184 | std::thread::sleep(Duration::from_millis(1010)); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod cache; 2 | pub(crate) mod executor; 3 | pub(crate) mod remote; 4 | pub(crate) mod util; 5 | -------------------------------------------------------------------------------- /src/common/remote/grpc/config.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use reqwest::header::HeaderValue; 4 | use tonic::transport::Uri; 5 | 6 | #[derive(Clone)] 7 | pub(crate) struct GrpcConfiguration { 8 | pub(crate) host: String, 9 | pub(crate) port: Option, 10 | pub(crate) origin: Option, 11 | pub(crate) user_agent: Option, 12 | pub(crate) timeout: Option, 13 | pub(crate) concurrency_limit: Option, 14 | pub(crate) rate_limit: Option<(u64, Duration)>, 15 | pub(crate) init_stream_window_size: Option, 16 | pub(crate) init_connection_window_size: Option, 17 | pub(crate) tcp_keepalive: Option, 18 | pub(crate) tcp_nodelay: bool, 19 | pub(crate) http2_keep_alive_interval: Option, 20 | pub(crate) http2_keep_alive_timeout: Option, 21 | pub(crate) http2_keep_alive_while_idle: Option, 22 | pub(crate) connect_timeout: Option, 23 | pub(crate) http2_adaptive_window: Option, 24 | } 25 | 26 | impl GrpcConfiguration { 27 | pub(crate) fn with_host(mut self, host: String) -> Self { 28 | self.host = host; 29 | self 30 | } 31 | 32 | pub(crate) fn with_port(mut self, port: Option) -> Self { 33 | self.port = port; 34 | self 35 | } 36 | 37 | pub(crate) fn with_origin(mut self, uri: &str) -> Self { 38 | let uri = uri.parse::(); 39 | if uri.is_err() { 40 | return self; 41 | } 42 | self.origin = Some(uri.unwrap()); 43 | self 44 | } 45 | 46 | pub(crate) fn with_user_agent(mut self, ua: String) -> Self { 47 | let ua = HeaderValue::try_from(ua); 48 | if ua.is_err() { 49 | return self; 50 | } 51 | let ua = ua.unwrap(); 52 | self.user_agent = Some(ua); 53 | self 54 | } 55 | 56 | pub(crate) fn with_timeout(mut self, timeout: Duration) -> Self { 57 | self.timeout = Some(timeout); 58 | self 59 | } 60 | 61 | pub(crate) fn with_concurrency_limit(mut self, concurrency_limit: usize) -> Self { 62 | self.concurrency_limit = Some(concurrency_limit); 63 | self 64 | } 65 | 66 | pub(crate) fn with_rate_limit(mut self, rate_limit: (u64, Duration)) -> Self { 67 | self.rate_limit = Some(rate_limit); 68 | self 69 | } 70 | 71 | pub(crate) fn with_init_stream_window_size(mut self, init_stream_window_size: u32) -> Self { 72 | self.init_stream_window_size = Some(init_stream_window_size); 73 | self 74 | } 75 | 76 | pub(crate) fn with_init_connection_window_size( 77 | mut self, 78 | init_connection_window_size: u32, 79 | ) -> Self { 80 | self.init_connection_window_size = Some(init_connection_window_size); 81 | self 82 | } 83 | 84 | pub(crate) fn with_tcp_keepalive(mut self, tcp_keepalive: Duration) -> Self { 85 | self.tcp_keepalive = Some(tcp_keepalive); 86 | self 87 | } 88 | 89 | pub(crate) fn with_tcp_nodelay(mut self, tcp_nodelay: bool) -> Self { 90 | self.tcp_nodelay = tcp_nodelay; 91 | self 92 | } 93 | 94 | pub(crate) fn with_http2_keep_alive_interval( 95 | mut self, 96 | http2_keep_alive_interval: Duration, 97 | ) -> Self { 98 | self.http2_keep_alive_interval = Some(http2_keep_alive_interval); 99 | self 100 | } 101 | 102 | pub(crate) fn with_http2_keep_alive_timeout( 103 | mut self, 104 | http2_keep_alive_timeout: Duration, 105 | ) -> Self { 106 | self.http2_keep_alive_timeout = Some(http2_keep_alive_timeout); 107 | self 108 | } 109 | 110 | pub(crate) fn with_http2_keep_alive_while_idle( 111 | mut self, 112 | http2_keep_alive_while_idle: bool, 113 | ) -> Self { 114 | self.http2_keep_alive_while_idle = Some(http2_keep_alive_while_idle); 115 | self 116 | } 117 | 118 | pub(crate) fn with_connect_timeout(mut self, connect_timeout: Duration) -> Self { 119 | self.connect_timeout = Some(connect_timeout); 120 | self 121 | } 122 | 123 | pub(crate) fn with_http2_adaptive_window(mut self, http2_adaptive_window: bool) -> Self { 124 | self.http2_adaptive_window = Some(http2_adaptive_window); 125 | self 126 | } 127 | } 128 | 129 | impl Default for GrpcConfiguration { 130 | fn default() -> Self { 131 | Self { 132 | host: "127.0.0.1".to_string(), 133 | port: Default::default(), 134 | origin: Default::default(), 135 | user_agent: Default::default(), 136 | timeout: Default::default(), 137 | concurrency_limit: Default::default(), 138 | rate_limit: Default::default(), 139 | init_stream_window_size: Default::default(), 140 | init_connection_window_size: Default::default(), 141 | tcp_keepalive: Default::default(), 142 | tcp_nodelay: Default::default(), 143 | http2_keep_alive_interval: Default::default(), 144 | http2_keep_alive_timeout: Default::default(), 145 | http2_keep_alive_while_idle: Default::default(), 146 | connect_timeout: Default::default(), 147 | http2_adaptive_window: Default::default(), 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/common/remote/grpc/handlers/client_detection_request_handler.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | common::remote::grpc::{ 3 | message::{ 4 | GrpcMessage, GrpcMessageBuilder, request::ClientDetectionRequest, 5 | response::ClientDetectionResponse, 6 | }, 7 | nacos_grpc_service::ServerRequestHandler, 8 | }, 9 | nacos_proto::v2::Payload, 10 | }; 11 | use async_trait::async_trait; 12 | use tracing::{debug, error}; 13 | 14 | pub(crate) struct ClientDetectionRequestHandler; 15 | 16 | #[async_trait] 17 | impl ServerRequestHandler for ClientDetectionRequestHandler { 18 | async fn request_reply(&self, request: Payload) -> Option { 19 | let request_message = GrpcMessage::::from_payload(request); 20 | if let Err(e) = request_message { 21 | error!("convert payload to ClientDetectionRequest error. {e:?}"); 22 | return None; 23 | } 24 | 25 | let request_message = request_message.unwrap(); 26 | let request_message = request_message.into_body(); 27 | debug!("ClientDetectionRequestHandler receive a request: {request_message:?}"); 28 | let request_id = request_message.request_id; 29 | 30 | let mut response_message = ClientDetectionResponse::ok(); 31 | response_message.request_id = request_id; 32 | 33 | let grpc_message = GrpcMessageBuilder::new(response_message).build(); 34 | let payload = grpc_message.into_payload(); 35 | if let Err(e) = payload { 36 | error!("occur an error when handing ClientDetectionRequest. {e:?}"); 37 | return None; 38 | } 39 | let payload = payload.unwrap(); 40 | Some(payload) 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | pub mod tests { 46 | use super::*; 47 | use crate::nacos_proto::v2::Payload; 48 | 49 | #[tokio::test] 50 | pub async fn test_request_reply_when_convert_payload_error() { 51 | let request = Payload::default(); 52 | let handler = ClientDetectionRequestHandler; 53 | let reply = handler.request_reply(request).await; 54 | 55 | assert!(reply.is_none()) 56 | } 57 | 58 | #[tokio::test] 59 | pub async fn test_request_reply() { 60 | let mut request = ClientDetectionRequest::default(); 61 | request.request_id = Some("test-request-id".to_string()); 62 | let request_message = GrpcMessageBuilder::new(request).build(); 63 | let payload = request_message.into_payload().unwrap(); 64 | 65 | let handler = ClientDetectionRequestHandler; 66 | let reply = handler.request_reply(payload).await; 67 | 68 | assert!(reply.is_some()); 69 | 70 | let reply = reply.unwrap(); 71 | 72 | let response = GrpcMessage::::from_payload(reply); 73 | 74 | assert!(response.is_ok()); 75 | 76 | let response = response.unwrap(); 77 | 78 | let response = response.into_body(); 79 | 80 | assert_eq!(response.request_id.unwrap(), "test-request-id".to_string()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/common/remote/grpc/handlers/default_handler.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | common::remote::grpc::nacos_grpc_service::ServerRequestHandler, nacos_proto::v2::Payload, 3 | }; 4 | use async_trait::async_trait; 5 | use tracing::{error, info}; 6 | 7 | pub(crate) struct DefaultHandler; 8 | 9 | #[async_trait] 10 | impl ServerRequestHandler for DefaultHandler { 11 | async fn request_reply(&self, request: Payload) -> Option { 12 | let p_type; 13 | let r_body; 14 | let r_type; 15 | let client_ip; 16 | let headers; 17 | 18 | if let Some(body) = request.body { 19 | p_type = body.type_url; 20 | let body_str = String::from_utf8(body.value); 21 | if let Err(e) = body_str { 22 | error!("unknown payload convert to string failed. {}", e); 23 | r_body = Default::default(); 24 | } else { 25 | r_body = body_str.unwrap(); 26 | } 27 | } else { 28 | r_body = Default::default(); 29 | p_type = Default::default(); 30 | } 31 | 32 | if let Some(meta_data) = request.metadata { 33 | r_type = meta_data.r#type; 34 | client_ip = meta_data.client_ip; 35 | headers = meta_data.headers; 36 | } else { 37 | r_type = Default::default(); 38 | client_ip = Default::default(); 39 | headers = Default::default(); 40 | } 41 | 42 | info!( 43 | "unknown server request. type: {}, client_ip: {}, headers:{:?}, payload: {}, payload_type: {}", 44 | r_type, client_ip, headers, r_body, p_type 45 | ); 46 | 47 | return None; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/common/remote/grpc/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod client_detection_request_handler; 2 | pub(crate) mod default_handler; 3 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/mod.rs: -------------------------------------------------------------------------------- 1 | use prost_types::Any; 2 | use serde::{Serialize, de::DeserializeOwned}; 3 | use std::collections::HashMap; 4 | use tracing::warn; 5 | 6 | use crate::api::error::Error::ErrResponse; 7 | use crate::api::error::Error::ErrResult; 8 | use crate::api::error::Error::Serialization; 9 | use crate::api::error::Result; 10 | use crate::api::plugin::RequestResource; 11 | use crate::common::remote::grpc::message::response::ErrorResponse; 12 | use crate::nacos_proto::v2::{Metadata, Payload}; 13 | use std::fmt::Debug; 14 | use tracing::error; 15 | 16 | pub(crate) mod request; 17 | pub(crate) mod response; 18 | 19 | #[derive(Debug)] 20 | pub(crate) struct GrpcMessage 21 | where 22 | T: GrpcMessageData, 23 | { 24 | headers: HashMap, 25 | body: T, 26 | client_ip: String, 27 | } 28 | 29 | impl GrpcMessage 30 | where 31 | T: GrpcMessageData, 32 | { 33 | pub(crate) fn body(&self) -> &T { 34 | &self.body 35 | } 36 | 37 | pub(crate) fn into_body(self) -> T { 38 | self.body 39 | } 40 | 41 | pub(crate) fn into_payload(self) -> Result { 42 | let mut payload = Payload::default(); 43 | let meta_data = Metadata { 44 | r#type: T::identity().to_string(), 45 | client_ip: self.client_ip.to_string(), 46 | headers: self.headers, 47 | }; 48 | 49 | let body = self.body.to_proto_any(); 50 | 51 | if let Err(error) = body { 52 | error!("Serialize GrpcMessageBody occur an error: {:?}", error); 53 | return Err(error); 54 | } 55 | let body = body.unwrap(); 56 | 57 | payload.metadata = Some(meta_data); 58 | payload.body = Some(body); 59 | Ok(payload) 60 | } 61 | 62 | pub(crate) fn from_payload(payload: Payload) -> Result { 63 | let body = payload.body; 64 | if body.is_none() { 65 | return Err(ErrResult("grpc payload body is empty".to_string())); 66 | } 67 | 68 | let body_any = body.unwrap(); 69 | 70 | let meta_data = payload.metadata.unwrap_or_default(); 71 | let r_type = meta_data.r#type; 72 | let client_ip = meta_data.client_ip; 73 | let headers = meta_data.headers; 74 | 75 | let de_body; 76 | 77 | // try to serialize target type if r_type is not empty 78 | if !r_type.is_empty() { 79 | if T::identity().eq(&r_type) { 80 | let ret: Result = T::from_proto_any(&body_any); 81 | if let Err(error) = ret { 82 | let payload_str = std::str::from_utf8(&body_any.value); 83 | if payload_str.is_err() { 84 | error!( 85 | "can not convert to target type {}, this payload can not convert to string as well", 86 | T::identity() 87 | ); 88 | return Err(error); 89 | } 90 | let payload_str = payload_str.unwrap(); 91 | error!( 92 | "payload {} can not convert to {} occur an error:{:?}", 93 | payload_str, 94 | T::identity(), 95 | error 96 | ); 97 | return Err(error); 98 | } 99 | de_body = ret.unwrap(); 100 | } else { 101 | warn!( 102 | "payload type {}, target type {}, trying convert to ErrorResponse", 103 | &r_type, 104 | T::identity() 105 | ); 106 | // try to convert to Error Response 107 | let ret: Result = ErrorResponse::from_proto_any(&body_any); 108 | if let Err(error) = ret { 109 | let payload_str = std::str::from_utf8(&body_any.value); 110 | if payload_str.is_err() { 111 | error!( 112 | "can not convert to ErrorResponse, this payload can not convert to string as well" 113 | ); 114 | return Err(error); 115 | } 116 | let payload_str = payload_str.unwrap(); 117 | error!( 118 | "payload {} can not convert to ErrorResponse occur an error:{:?}", 119 | payload_str, error 120 | ); 121 | return Err(error); 122 | } 123 | 124 | let error_response = ret.unwrap(); 125 | return Err(ErrResponse( 126 | error_response.request_id, 127 | error_response.result_code, 128 | error_response.error_code, 129 | error_response.message, 130 | )); 131 | } 132 | } else { 133 | warn!("payload type is empty!"); 134 | let ret: Result = T::from_proto_any(&body_any); 135 | if let Err(error) = ret { 136 | let payload_str = std::str::from_utf8(&body_any.value); 137 | if payload_str.is_err() { 138 | error!( 139 | "can not convert to target type {}, this payload can not convert to string as well", 140 | T::identity() 141 | ); 142 | return Err(error); 143 | } 144 | let payload_str = payload_str.unwrap(); 145 | warn!( 146 | "payload {} can not convert to {} occur an error:{:?}", 147 | payload_str, 148 | T::identity(), 149 | error 150 | ); 151 | let ret: Result = ErrorResponse::from_proto_any(&body_any); 152 | if let Err(e) = ret { 153 | error!("trying convert to ErrorResponse occur an error:{:?}", e); 154 | return Err(error); 155 | } 156 | let error_response = ret.unwrap(); 157 | return Err(ErrResponse( 158 | error_response.request_id, 159 | error_response.result_code, 160 | error_response.error_code, 161 | error_response.message, 162 | )); 163 | } 164 | de_body = ret.unwrap(); 165 | } 166 | 167 | Ok(GrpcMessage { 168 | headers, 169 | body: de_body, 170 | client_ip, 171 | }) 172 | } 173 | } 174 | 175 | pub(crate) trait GrpcMessageData: 176 | Debug + Clone + Serialize + DeserializeOwned + Send 177 | { 178 | fn identity<'a>() -> std::borrow::Cow<'a, str>; 179 | 180 | fn to_proto_any(&self) -> Result { 181 | let mut any = Any { 182 | type_url: Self::identity().to_string(), 183 | ..Default::default() 184 | }; 185 | let byte_data = serde_json::to_vec(self); 186 | if let Err(error) = byte_data { 187 | return Err(Serialization(error)); 188 | } 189 | any.value = byte_data.unwrap(); 190 | Ok(any) 191 | } 192 | 193 | fn from_proto_any(any: &Any) -> Result { 194 | let body: serde_json::Result = serde_json::from_slice(&any.value); 195 | if let Err(error) = body { 196 | return Err(Serialization(error)); 197 | }; 198 | let body = body.unwrap(); 199 | Ok(body) 200 | } 201 | } 202 | 203 | pub(crate) trait GrpcRequestMessage: GrpcMessageData { 204 | fn header(&self, key: &str) -> Option<&String>; 205 | 206 | fn headers(&self) -> &HashMap; 207 | 208 | fn take_headers(&mut self) -> HashMap; 209 | 210 | fn add_headers(&mut self, map: HashMap); 211 | 212 | fn request_id(&self) -> Option<&String>; 213 | 214 | fn module(&self) -> &str; 215 | 216 | fn request_resource(&self) -> Option; 217 | } 218 | 219 | pub(crate) trait GrpcResponseMessage: GrpcMessageData { 220 | fn request_id(&self) -> Option<&String>; 221 | 222 | fn result_code(&self) -> i32; 223 | 224 | fn error_code(&self) -> i32; 225 | 226 | fn message(&self) -> Option<&String>; 227 | 228 | fn is_success(&self) -> bool; 229 | } 230 | 231 | pub(crate) struct GrpcMessageBuilder 232 | where 233 | T: GrpcMessageData, 234 | { 235 | headers: HashMap, 236 | body: T, 237 | client_ip: String, 238 | } 239 | 240 | static LOCAL_IP: std::sync::LazyLock = 241 | std::sync::LazyLock::new(|| local_ipaddress::get().unwrap()); 242 | 243 | impl GrpcMessageBuilder 244 | where 245 | T: GrpcMessageData, 246 | { 247 | pub(crate) fn new(body: T) -> Self { 248 | GrpcMessageBuilder { 249 | headers: HashMap::::new(), 250 | body, 251 | client_ip: LOCAL_IP.to_owned(), 252 | } 253 | } 254 | 255 | pub(crate) fn header(mut self, key: String, value: String) -> Self { 256 | self.headers.insert(key, value); 257 | self 258 | } 259 | 260 | pub(crate) fn headers(mut self, headers: HashMap) -> Self { 261 | self.headers.extend(headers); 262 | self 263 | } 264 | 265 | pub(crate) fn build(self) -> GrpcMessage { 266 | GrpcMessage { 267 | headers: self.headers, 268 | body: self.body, 269 | client_ip: self.client_ip, 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/request/client_detection_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | #[request(identity = "ClientDetectionRequest", module = "internal")] 4 | pub(crate) struct ClientDetectionRequest {} 5 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/request/health_check_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | #[request(identity = "HealthCheckRequest", module = "internal")] 4 | pub(crate) struct HealthCheckRequest {} 5 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/request/mod.rs: -------------------------------------------------------------------------------- 1 | mod client_detection_request; 2 | mod health_check_request; 3 | mod server_check_request; 4 | mod set_up_request; 5 | 6 | pub(crate) use client_detection_request::*; 7 | pub(crate) use health_check_request::*; 8 | pub(crate) use server_check_request::*; 9 | pub(crate) use set_up_request::*; 10 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/request/server_check_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | use crate::common::remote::generate_request_id; 4 | 5 | #[request(identity = "ServerCheckRequest", module = "internal")] 6 | pub(crate) struct ServerCheckRequest {} 7 | 8 | impl ServerCheckRequest { 9 | pub(crate) fn new() -> Self { 10 | let request_id = Some(generate_request_id()); 11 | 12 | Self { 13 | request_id, 14 | ..Default::default() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/request/set_up_request.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use nacos_macro::request; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[request(identity = "ConnectionSetupRequest", module = "internal")] 7 | pub(crate) struct ConnectionSetupRequest { 8 | pub(crate) client_version: String, 9 | 10 | pub(crate) abilities: NacosClientAbilities, 11 | 12 | pub(crate) tenant: String, 13 | 14 | pub(crate) labels: HashMap, 15 | } 16 | 17 | #[derive(Clone, Debug, Serialize, Deserialize, Default)] 18 | pub(crate) struct NacosClientAbilities { 19 | #[serde(rename = "remoteAbility")] 20 | remote_ability: NacosClientRemoteAbility, 21 | 22 | #[serde(rename = "configAbility")] 23 | config_ability: NacosClientConfigAbility, 24 | 25 | #[serde(rename = "namingAbility")] 26 | naming_ability: NacosClientNamingAbility, 27 | } 28 | 29 | impl NacosClientAbilities { 30 | pub(crate) fn new() -> Self { 31 | NacosClientAbilities { 32 | remote_ability: NacosClientRemoteAbility::new(), 33 | config_ability: NacosClientConfigAbility::new(), 34 | naming_ability: NacosClientNamingAbility::new(), 35 | } 36 | } 37 | 38 | pub(crate) fn support_remote_connection(&mut self, enable: bool) { 39 | self.remote_ability.support_remote_connection(enable); 40 | } 41 | 42 | pub(crate) fn support_config_remote_metrics(&mut self, enable: bool) { 43 | self.config_ability.support_remote_metrics(enable); 44 | } 45 | 46 | pub(crate) fn support_naming_delta_push(&mut self, enable: bool) { 47 | self.naming_ability.support_delta_push(enable); 48 | } 49 | 50 | pub(crate) fn support_naming_remote_metric(&mut self, enable: bool) { 51 | self.naming_ability.support_remote_metric(enable); 52 | } 53 | } 54 | 55 | #[derive(Clone, Debug, Serialize, Deserialize, Default)] 56 | struct NacosClientRemoteAbility { 57 | #[serde(rename = "supportRemoteConnection")] 58 | support_remote_connection: bool, 59 | } 60 | 61 | impl NacosClientRemoteAbility { 62 | fn new() -> Self { 63 | NacosClientRemoteAbility { 64 | support_remote_connection: false, 65 | } 66 | } 67 | 68 | fn support_remote_connection(&mut self, enable: bool) { 69 | self.support_remote_connection = enable; 70 | } 71 | } 72 | 73 | #[derive(Clone, Debug, Serialize, Deserialize, Default)] 74 | struct NacosClientConfigAbility { 75 | #[serde(rename = "supportRemoteMetrics")] 76 | support_remote_metrics: bool, 77 | } 78 | 79 | impl NacosClientConfigAbility { 80 | fn new() -> Self { 81 | NacosClientConfigAbility { 82 | support_remote_metrics: false, 83 | } 84 | } 85 | 86 | fn support_remote_metrics(&mut self, enable: bool) { 87 | self.support_remote_metrics = enable; 88 | } 89 | } 90 | 91 | #[derive(Clone, Debug, Serialize, Deserialize, Default)] 92 | struct NacosClientNamingAbility { 93 | #[serde(rename = "supportDeltaPush")] 94 | support_delta_push: bool, 95 | 96 | #[serde(rename = "supportRemoteMetric")] 97 | support_remote_metric: bool, 98 | } 99 | 100 | impl NacosClientNamingAbility { 101 | fn new() -> Self { 102 | NacosClientNamingAbility { 103 | support_delta_push: false, 104 | support_remote_metric: false, 105 | } 106 | } 107 | 108 | fn support_delta_push(&mut self, enable: bool) { 109 | self.support_delta_push = enable; 110 | } 111 | 112 | fn support_remote_metric(&mut self, enable: bool) { 113 | self.support_remote_metric = enable; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/response/client_detection_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | #[response(identity = "ClientDetectionResponse", module = "internal")] 4 | pub(crate) struct ClientDetectionResponse {} 5 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/response/error_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | #[response(identity = "ErrorResponse", module = "internal")] 4 | pub(crate) struct ErrorResponse {} 5 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/response/health_check_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | #[response(identity = "HealthCheckResponse", module = "internal")] 4 | pub(crate) struct HealthCheckResponse {} 5 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/response/mod.rs: -------------------------------------------------------------------------------- 1 | mod client_detection_response; 2 | mod error_response; 3 | mod health_check_response; 4 | mod server_check_response; 5 | 6 | pub(crate) use client_detection_response::*; 7 | #[allow(unused_imports)] 8 | pub(crate) use error_response::*; 9 | pub(crate) use health_check_response::*; 10 | pub(crate) use server_check_response::*; 11 | -------------------------------------------------------------------------------- /src/common/remote/grpc/message/response/server_check_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | #[response(identity = "ServerCheckResponse", module = "internal")] 4 | pub(crate) struct ServerCheckResponse { 5 | pub(crate) connection_id: Option, 6 | } 7 | -------------------------------------------------------------------------------- /src/common/remote/grpc/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod config; 2 | pub(crate) mod handlers; 3 | pub(crate) mod message; 4 | pub(crate) mod nacos_grpc_client; 5 | pub(crate) mod nacos_grpc_connection; 6 | pub(crate) mod nacos_grpc_service; 7 | pub(crate) mod server_address; 8 | pub(crate) mod server_list_service; 9 | pub(crate) mod tonic; 10 | 11 | pub(crate) use nacos_grpc_client::*; 12 | -------------------------------------------------------------------------------- /src/common/remote/grpc/server_address.rs: -------------------------------------------------------------------------------- 1 | pub(crate) trait ServerAddress: Sync + Send + 'static { 2 | fn host(&self) -> String; 3 | 4 | fn port(&self) -> u32; 5 | 6 | fn is_available(&self) -> bool; 7 | } 8 | -------------------------------------------------------------------------------- /src/common/remote/grpc/server_list_service.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | pin::Pin, 3 | sync::Arc, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use crate::api::error::Error; 8 | use crate::api::error::Error::NoAvailableServer; 9 | use futures::Future; 10 | use rand::Rng; 11 | use tower::Service; 12 | 13 | use super::server_address::ServerAddress; 14 | 15 | pub(crate) struct PollingServerListService { 16 | server_list: Vec<(String, u32)>, 17 | index: usize, 18 | } 19 | 20 | impl PollingServerListService { 21 | pub(crate) fn new(server_list: Vec) -> Self { 22 | if server_list.is_empty() { 23 | panic!("server list must not empty"); 24 | } 25 | 26 | let server_list: Vec<(String, u32)> = server_list 27 | .into_iter() 28 | .map(|server| server.split(':').map(|data| data.to_string()).collect()) 29 | .filter(|vec: &Vec| { 30 | if vec.len() != 2 { 31 | return false; 32 | } 33 | vec.get(0).is_some() && vec.get(1).is_some() 34 | }) 35 | .filter_map(|vec| { 36 | let address = vec.get(0).unwrap().clone(); 37 | let port = vec.get(1).unwrap().clone(); 38 | 39 | let port = port.parse::(); 40 | 41 | if let Ok(port) = port { 42 | return Some((address, port)); 43 | } 44 | None 45 | }) 46 | .collect(); 47 | if server_list.is_empty() { 48 | panic!("all the server is illegal format!"); 49 | } 50 | 51 | Self { 52 | // random index for load balance the server list 53 | index: rand::thread_rng().gen_range(0..server_list.len()), 54 | server_list, 55 | } 56 | } 57 | } 58 | 59 | impl Service<()> for PollingServerListService { 60 | type Response = Arc; 61 | 62 | type Error = Error; 63 | 64 | type Future = 65 | Pin> + Send + 'static>>; 66 | 67 | fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { 68 | self.index += 1; 69 | if self.index >= self.server_list.len() { 70 | self.index = 0; 71 | } 72 | 73 | Poll::Ready(Ok(())) 74 | } 75 | 76 | fn call(&mut self, _: ()) -> Self::Future { 77 | let server_addr = self.server_list.get(self.index); 78 | let server_addr = if let Some((host, port)) = server_addr { 79 | let server_address = PollingServerAddress { 80 | host: host.clone(), 81 | port: *port, 82 | }; 83 | Ok(Arc::new(server_address) as Arc) 84 | } else { 85 | Err(NoAvailableServer) 86 | }; 87 | Box::pin(async move { server_addr }) 88 | } 89 | } 90 | 91 | struct PollingServerAddress { 92 | host: String, 93 | port: u32, 94 | } 95 | 96 | impl ServerAddress for PollingServerAddress { 97 | fn host(&self) -> String { 98 | self.host.clone() 99 | } 100 | 101 | fn port(&self) -> u32 { 102 | self.port 103 | } 104 | 105 | fn is_available(&self) -> bool { 106 | true 107 | } 108 | } 109 | 110 | #[cfg(test)] 111 | pub mod tests { 112 | 113 | use futures::future::poll_fn; 114 | use tower::Service; 115 | use tracing::debug; 116 | 117 | use crate::test_config; 118 | 119 | use super::PollingServerListService; 120 | 121 | #[test] 122 | #[should_panic(expected = "server list must not empty")] 123 | pub fn test_empty_server_list() { 124 | let _ = PollingServerListService::new(Vec::default()); 125 | } 126 | 127 | #[test] 128 | #[should_panic(expected = "all the server is illegal format!")] 129 | pub fn test_illegal_format() { 130 | let _ = PollingServerListService::new(vec!["127.0.0.1:sd".to_string()]); 131 | } 132 | 133 | fn setup() { 134 | test_config::setup_log(); 135 | } 136 | 137 | fn teardown() {} 138 | 139 | fn run_test(test: F) -> T 140 | where 141 | F: FnOnce() -> T, 142 | { 143 | setup(); 144 | let ret = test(); 145 | teardown(); 146 | ret 147 | } 148 | 149 | #[tokio::test] 150 | pub async fn test_poll_server_list() { 151 | run_test(|| async { 152 | let mut service = PollingServerListService::new(vec![ 153 | "127.0.0.1:8848".to_string(), 154 | "127.0.0.2:8848".to_string(), 155 | "127.0.0.3:8848".to_string(), 156 | ]); 157 | 158 | let _ = poll_fn(|cx| service.poll_ready(cx)).await; 159 | let server1 = service.call(()).await.unwrap(); 160 | debug!("ip:{}, port:{}", server1.host(), server1.port()); 161 | 162 | let _ = poll_fn(|cx| service.poll_ready(cx)).await; 163 | let server2 = service.call(()).await.unwrap(); 164 | debug!("ip:{}, port:{}", server2.host(), server2.port()); 165 | 166 | let _ = poll_fn(|cx| service.poll_ready(cx)).await; 167 | let server3 = service.call(()).await.unwrap(); 168 | debug!("ip:{}, port:{}", server3.host(), server3.port()); 169 | 170 | let _ = poll_fn(|cx| service.poll_ready(cx)).await; 171 | let server4 = service.call(()).await.unwrap(); 172 | debug!("ip:{}, port:{}", server4.host(), server4.port()); 173 | 174 | let _ = poll_fn(|cx| service.poll_ready(cx)).await; 175 | let server5 = service.call(()).await.unwrap(); 176 | debug!("ip:{}, port:{}", server5.host(), server5.port()); 177 | 178 | let _ = poll_fn(|cx| service.poll_ready(cx)).await; 179 | let server6 = service.call(()).await.unwrap(); 180 | debug!("ip:{}, port:{}", server6.host(), server6.port()); 181 | 182 | let _ = poll_fn(|cx| service.poll_ready(cx)).await; 183 | let server7 = service.call(()).await.unwrap(); 184 | debug!("ip:{}, port:{}", server7.host(), server7.port()); 185 | }) 186 | .await; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/common/remote/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod grpc; 2 | 3 | use std::sync::atomic::{AtomicI64, Ordering}; 4 | 5 | // odd by client request id. 6 | const SEQUENCE_INITIAL_VALUE: i64 = 1; 7 | const SEQUENCE_DELTA: i64 = 2; 8 | static ATOMIC_SEQUENCE: AtomicI64 = AtomicI64::new(SEQUENCE_INITIAL_VALUE); 9 | 10 | pub(crate) fn generate_request_id() -> String { 11 | let seq = ATOMIC_SEQUENCE.fetch_add(SEQUENCE_DELTA, Ordering::Relaxed); 12 | if seq > i64::MAX - 1000 { 13 | ATOMIC_SEQUENCE.store(SEQUENCE_INITIAL_VALUE, Ordering::SeqCst); 14 | } 15 | seq.to_string() 16 | } 17 | -------------------------------------------------------------------------------- /src/common/util.rs: -------------------------------------------------------------------------------- 1 | use crate::api::error::Error; 2 | use crate::api::error::Result; 3 | 4 | /// Checks param_val not blank 5 | pub(crate) fn check_not_blank<'a>(param_val: &'a str, param_name: &'a str) -> Result<&'a str> { 6 | if param_val.trim().is_empty() { 7 | Err(Error::InvalidParam( 8 | param_name.into(), 9 | "param must not blank!".into(), 10 | )) 11 | } else { 12 | Ok(param_val) 13 | } 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use crate::common::util::check_not_blank; 19 | 20 | #[test] 21 | fn test_check_not_blank() { 22 | let data_id = "data_id"; 23 | let group = "group"; 24 | let namespace = "namespace"; 25 | 26 | assert_eq!(data_id, check_not_blank(data_id, "data_id").unwrap()); 27 | assert_eq!(group, check_not_blank(group, "group").unwrap()); 28 | assert_eq!(namespace, check_not_blank(namespace, "namespace").unwrap()); 29 | } 30 | 31 | #[test] 32 | fn test_check_not_blank_fail() { 33 | let data_id = ""; 34 | assert!(check_not_blank(data_id, "data_id").is_err()); 35 | 36 | let data_id = " "; 37 | assert!(check_not_blank(data_id, "data_id").is_err()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/config/cache.rs: -------------------------------------------------------------------------------- 1 | use crate::api::config::ConfigResponse; 2 | use crate::api::plugin::ConfigFilter; 3 | use crate::api::plugin::ConfigResp; 4 | use std::ops::Deref; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | /// Cache Data for Config 8 | #[derive(Default)] 9 | pub(crate) struct CacheData { 10 | pub data_id: String, 11 | pub group: String, 12 | pub namespace: String, 13 | /// Default text; text, json, properties, html, xml, yaml ... 14 | pub content_type: String, 15 | pub content: String, 16 | pub md5: String, 17 | /// whether content was encrypted with encryptedDataKey. 18 | pub encrypted_data_key: String, 19 | pub last_modified: i64, 20 | 21 | /// There are some logical differences in the initialization phase, such as no notification of config changed 22 | pub initializing: bool, 23 | 24 | /// who listen of config change. 25 | pub listeners: Arc>>, 26 | 27 | pub config_filters: Arc>>, 28 | } 29 | 30 | impl CacheData { 31 | pub fn new( 32 | config_filters: Arc>>, 33 | data_id: String, 34 | group: String, 35 | namespace: String, 36 | ) -> Self { 37 | Self { 38 | config_filters, 39 | data_id, 40 | group, 41 | namespace, 42 | content_type: "text".to_string(), 43 | initializing: true, 44 | ..Default::default() 45 | } 46 | } 47 | 48 | /// Add listener. 49 | pub fn add_listener(&mut self, listener: Arc) { 50 | if let Ok(mut mutex) = self.listeners.lock() { 51 | if Self::index_of_listener(mutex.deref(), Arc::clone(&listener)).is_some() { 52 | return; 53 | } 54 | mutex.push(ListenerWrapper::new(Arc::clone(&listener))); 55 | } 56 | } 57 | 58 | /// Remove listener. 59 | pub fn remove_listener(&mut self, listener: Arc) { 60 | if let Ok(mut mutex) = self.listeners.lock() { 61 | if let Some(idx) = Self::index_of_listener(mutex.deref(), Arc::clone(&listener)) { 62 | mutex.swap_remove(idx); 63 | } 64 | } 65 | } 66 | 67 | /// fn inner, return idx if existed, else return None. 68 | fn index_of_listener( 69 | listen_warp_vec: &[ListenerWrapper], 70 | listener: Arc, 71 | ) -> Option { 72 | for (idx, listen_warp) in listen_warp_vec.iter().enumerate() { 73 | #[allow(ambiguous_wide_pointer_comparisons)] 74 | if Arc::ptr_eq(&listen_warp.listener, &listener) { 75 | return Some(idx); 76 | } 77 | } 78 | None 79 | } 80 | 81 | /// Notify listener. when last-md5 not equals the-newest-md5 82 | pub async fn notify_listener(&mut self) { 83 | tracing::info!( 84 | "notify_listener, dataId={},group={},namespace={},md5={}", 85 | self.data_id, 86 | self.group, 87 | self.namespace, 88 | self.md5 89 | ); 90 | 91 | let config_resp = self.get_config_resp_after_filter().await; 92 | 93 | if let Ok(mut mutex) = self.listeners.lock() { 94 | for listen_wrap in mutex.iter_mut() { 95 | if listen_wrap.last_md5.eq(&self.md5) { 96 | continue; 97 | } 98 | // Notify when last-md5 not equals the-newest-md5, Notify in independent thread. 99 | let l_clone = listen_wrap.listener.clone(); 100 | let c_clone = config_resp.clone(); 101 | crate::common::executor::spawn(async move { 102 | l_clone.notify(c_clone); 103 | }); 104 | listen_wrap.last_md5 = self.md5.clone(); 105 | } 106 | } 107 | } 108 | 109 | /// inner method, will invoke config_filter 110 | async fn get_config_resp_after_filter(&self) -> ConfigResponse { 111 | let mut conf_resp = ConfigResp::new( 112 | self.data_id.clone(), 113 | self.group.clone(), 114 | self.namespace.clone(), 115 | self.content.clone(), 116 | self.encrypted_data_key.clone(), 117 | ); 118 | for config_filter in self.config_filters.iter() { 119 | config_filter.filter(None, Some(&mut conf_resp)).await; 120 | } 121 | 122 | ConfigResponse::new( 123 | conf_resp.data_id, 124 | conf_resp.group, 125 | conf_resp.namespace, 126 | conf_resp.content, 127 | self.content_type.clone(), 128 | self.md5.clone(), 129 | ) 130 | } 131 | } 132 | 133 | impl std::fmt::Display for CacheData { 134 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 135 | let mut content = self.content.clone(); 136 | if content.len() > 30 { 137 | content.truncate(30); 138 | content.push_str("..."); 139 | } 140 | write!( 141 | f, 142 | "CacheData(namespace={n},data_id={d},group={g},md5={m},encrypted_data_key={k},content_type={t},content={c})", 143 | n = self.namespace, 144 | d = self.data_id, 145 | g = self.group, 146 | m = self.md5, 147 | k = self.encrypted_data_key, 148 | t = self.content_type, 149 | c = content 150 | ) 151 | } 152 | } 153 | 154 | /// The inner Wrapper of ConfigChangeListener 155 | pub(crate) struct ListenerWrapper { 156 | /// last md5 be notified 157 | last_md5: String, 158 | listener: Arc, 159 | } 160 | 161 | impl ListenerWrapper { 162 | fn new(listener: Arc) -> Self { 163 | Self { 164 | last_md5: "".to_string(), 165 | listener, 166 | } 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod tests { 172 | use crate::api::config::{ConfigChangeListener, ConfigResponse}; 173 | use crate::config::cache::CacheData; 174 | use std::sync::Arc; 175 | 176 | #[test] 177 | fn test_cache_data_add_listener() { 178 | let (d, g, n) = ("D".to_string(), "G".to_string(), "N".to_string()); 179 | 180 | let mut cache_data = CacheData::new(Arc::new(Vec::new()), d, g, n); 181 | 182 | // test add listener1 183 | let lis1_arc = Arc::new(TestConfigChangeListener1 {}); 184 | let _listen = cache_data.add_listener(lis1_arc); 185 | 186 | // test add listener2 187 | let lis2_arc = Arc::new(TestConfigChangeListener2 {}); 188 | let _listen = cache_data.add_listener(lis2_arc.clone()); 189 | // test add a listener2 again 190 | let _listen = cache_data.add_listener(lis2_arc); 191 | 192 | let listen_mutex = cache_data.listeners.lock().unwrap(); 193 | assert_eq!(2, listen_mutex.len()); 194 | } 195 | 196 | #[test] 197 | fn test_cache_data_add_listener_then_remove() { 198 | let (d, g, n) = ("D".to_string(), "G".to_string(), "N".to_string()); 199 | 200 | let mut cache_data = CacheData::new(Arc::new(Vec::new()), d, g, n); 201 | 202 | // test add listener1 203 | let lis1_arc = Arc::new(TestConfigChangeListener1 {}); 204 | let lis1_arc2 = Arc::clone(&lis1_arc); 205 | let _listen = cache_data.add_listener(lis1_arc); 206 | 207 | // test add listener2 208 | let lis2_arc = Arc::new(TestConfigChangeListener2 {}); 209 | let lis2_arc2 = Arc::clone(&lis2_arc); 210 | let _listen = cache_data.add_listener(lis2_arc); 211 | { 212 | let listen_mutex = cache_data.listeners.lock().unwrap(); 213 | assert_eq!(2, listen_mutex.len()); 214 | } 215 | 216 | cache_data.remove_listener(lis1_arc2); 217 | { 218 | let listen_mutex = cache_data.listeners.lock().unwrap(); 219 | assert_eq!(1, listen_mutex.len()); 220 | } 221 | cache_data.remove_listener(lis2_arc2); 222 | { 223 | let listen_mutex = cache_data.listeners.lock().unwrap(); 224 | assert_eq!(0, listen_mutex.len()); 225 | } 226 | } 227 | 228 | struct TestConfigChangeListener1; 229 | struct TestConfigChangeListener2; 230 | 231 | impl ConfigChangeListener for TestConfigChangeListener1 { 232 | fn notify(&self, config_resp: ConfigResponse) { 233 | tracing::info!( 234 | "TestConfigChangeListener1 listen the config={}", 235 | config_resp 236 | ); 237 | } 238 | } 239 | 240 | impl ConfigChangeListener for TestConfigChangeListener2 { 241 | fn notify(&self, config_resp: ConfigResponse) { 242 | tracing::info!( 243 | "TestConfigChangeListener2 listen the config={}", 244 | config_resp 245 | ); 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/config/handler.rs: -------------------------------------------------------------------------------- 1 | use crate::common::remote::grpc::message::{GrpcMessage, GrpcMessageBuilder}; 2 | use crate::common::remote::grpc::nacos_grpc_service::ServerRequestHandler; 3 | use crate::config::message::request::ConfigChangeNotifyRequest; 4 | use crate::config::message::response::ConfigChangeNotifyResponse; 5 | use crate::config::util; 6 | use crate::nacos_proto::v2::Payload; 7 | use async_trait::async_trait; 8 | use tokio::sync::mpsc::Sender; 9 | 10 | /// Handler for ConfigChangeNotify 11 | pub(crate) struct ConfigChangeNotifyHandler { 12 | pub(crate) notify_change_tx: Sender, 13 | } 14 | 15 | #[async_trait] 16 | impl ServerRequestHandler for ConfigChangeNotifyHandler { 17 | async fn request_reply(&self, request: Payload) -> Option { 18 | tracing::debug!("[ConfigChangeNotifyHandler] receive config-change, handle start."); 19 | 20 | let request = GrpcMessage::::from_payload(request); 21 | if let Err(e) = request { 22 | tracing::error!("convert payload to ConfigChangeNotifyRequest error. {e:?}"); 23 | return None; 24 | } 25 | let server_req = request.unwrap().into_body(); 26 | 27 | let server_req_id = server_req.request_id.unwrap_or_default(); 28 | let req_namespace = server_req.namespace.unwrap_or_default(); 29 | let req_data_id = server_req.data_id.unwrap(); 30 | let req_group = server_req.group.unwrap(); 31 | tracing::info!( 32 | "receive config-change, dataId={req_data_id},group={req_group},namespace={req_namespace}" 33 | ); 34 | // notify config change 35 | let group_key = util::group_key(&req_data_id, &req_group, &req_namespace); 36 | let _ = self.notify_change_tx.send(group_key).await; 37 | 38 | // bi send resp 39 | let response = ConfigChangeNotifyResponse::ok().request_id(server_req_id); 40 | let grpc_message = GrpcMessageBuilder::new(response).build(); 41 | let resp_payload = grpc_message.into_payload().unwrap(); 42 | 43 | Some(resp_payload) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/config/message/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod request; 2 | pub(crate) mod response; 3 | -------------------------------------------------------------------------------- /src/config/message/request/config_batch_listen_request.rs: -------------------------------------------------------------------------------- 1 | use crate::common::remote::generate_request_id; 2 | use nacos_macro::request; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// ConfigBatchListenRequest from client. 6 | #[request(identity = "ConfigBatchListenRequest", module = "config")] 7 | pub(crate) struct ConfigBatchListenRequest { 8 | /// listen or remove-listen. 9 | pub(crate) listen: bool, 10 | /// context of listen. 11 | pub(crate) config_listen_contexts: Vec, 12 | } 13 | 14 | impl ConfigBatchListenRequest { 15 | pub fn new(listen: bool) -> Self { 16 | Self { 17 | listen, 18 | config_listen_contexts: Vec::new(), 19 | request_id: Some(generate_request_id()), 20 | ..Default::default() 21 | } 22 | } 23 | 24 | /// Set ConfigListenContext. 25 | pub fn config_listen_context(mut self, contexts: Vec) -> Self { 26 | self.config_listen_contexts = contexts; 27 | self 28 | } 29 | 30 | /// Add ConfigListenContext. 31 | pub fn add_config_listen_context(mut self, context: ConfigListenContext) -> Self { 32 | self.config_listen_contexts.push(context); 33 | self 34 | } 35 | } 36 | 37 | /// The Context of config listen. 38 | #[derive(Clone, Debug, Serialize, Deserialize)] 39 | pub(crate) struct ConfigListenContext { 40 | /// DataId 41 | #[serde(rename = "dataId")] 42 | data_id: String, 43 | /// Group 44 | group: String, 45 | /// tenant 46 | tenant: String, 47 | /// Md5 48 | md5: String, 49 | } 50 | 51 | impl ConfigListenContext { 52 | pub fn new(data_id: String, group: String, tenant: String, md5: String) -> Self { 53 | ConfigListenContext { 54 | data_id, 55 | group, 56 | tenant, 57 | md5, 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/config/message/request/config_change_notify_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | /// ConfigChangeNotifyRequest from server. 4 | #[request(identity = "ConfigChangeNotifyRequest", module = "config")] 5 | pub(crate) struct ConfigChangeNotifyRequest {} 6 | -------------------------------------------------------------------------------- /src/config/message/request/config_publish_request.rs: -------------------------------------------------------------------------------- 1 | use crate::common::remote::generate_request_id; 2 | use nacos_macro::request; 3 | use std::collections::HashMap; 4 | 5 | /// ConfigPublishRequest from client. 6 | #[request(identity = "ConfigPublishRequest", module = "config")] 7 | pub(crate) struct ConfigPublishRequest { 8 | /// content 9 | pub(crate) content: String, 10 | /// Cas md5 (prev content's md5) 11 | pub(crate) cas_md5: Option, 12 | /// Addition Map 13 | pub(crate) addition_map: HashMap, 14 | } 15 | 16 | impl ConfigPublishRequest { 17 | pub fn new(data_id: String, group: String, namespace: String, content: String) -> Self { 18 | Self { 19 | request_id: Some(generate_request_id()), 20 | data_id: Some(data_id), 21 | group: Some(group), 22 | namespace: Some(namespace), 23 | content, 24 | cas_md5: None, 25 | addition_map: HashMap::default(), 26 | ..Default::default() 27 | } 28 | } 29 | /// Sets the cas_md5. 30 | pub fn cas_md5(mut self, cas_md5: Option) -> Self { 31 | self.cas_md5 = cas_md5; 32 | self 33 | } 34 | 35 | /// Add into additionMap. 36 | pub fn add_addition_param(&mut self, key: impl Into, val: impl Into) { 37 | self.addition_map.insert(key.into(), val.into()); 38 | } 39 | 40 | /// Add into additionMap. 41 | pub fn add_addition_params(&mut self, addition_params: HashMap) { 42 | self.addition_map.extend(addition_params); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/config/message/request/config_query_request.rs: -------------------------------------------------------------------------------- 1 | use crate::common::remote::generate_request_id; 2 | use nacos_macro::request; 3 | 4 | /// ConfigQueryRequest from client. 5 | #[request(identity = "ConfigQueryRequest", module = "config")] 6 | pub(crate) struct ConfigQueryRequest {} 7 | 8 | impl ConfigQueryRequest { 9 | pub fn new(data_id: String, group: String, namespace: String) -> Self { 10 | Self { 11 | request_id: Some(generate_request_id()), 12 | data_id: Some(data_id), 13 | group: Some(group), 14 | namespace: Some(namespace), 15 | ..Default::default() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/config/message/request/config_remove_request.rs: -------------------------------------------------------------------------------- 1 | use crate::common::remote::generate_request_id; 2 | use nacos_macro::request; 3 | 4 | /// ConfigRemoveRequest from client. 5 | #[request(identity = "ConfigRemoveRequest", module = "config")] 6 | pub(crate) struct ConfigRemoveRequest { 7 | /// tag 8 | pub(crate) tag: Option, 9 | } 10 | 11 | impl ConfigRemoveRequest { 12 | pub fn new(data_id: String, group: String, namespace: String) -> Self { 13 | Self { 14 | request_id: Some(generate_request_id()), 15 | data_id: Some(data_id), 16 | group: Some(group), 17 | namespace: Some(namespace), 18 | tag: None, 19 | ..Default::default() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/config/message/request/mod.rs: -------------------------------------------------------------------------------- 1 | mod config_batch_listen_request; 2 | mod config_change_notify_request; 3 | mod config_publish_request; 4 | mod config_query_request; 5 | mod config_remove_request; 6 | 7 | pub(crate) use config_batch_listen_request::*; 8 | pub(crate) use config_change_notify_request::*; 9 | pub(crate) use config_publish_request::*; 10 | pub(crate) use config_query_request::*; 11 | pub(crate) use config_remove_request::*; 12 | -------------------------------------------------------------------------------- /src/config/message/response/config_batch_listen_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// ConfigChangeBatchListenResponse by server. 5 | #[response(identity = "ConfigChangeBatchListenResponse", module = "config")] 6 | pub(crate) struct ConfigChangeBatchListenResponse { 7 | pub(crate) changed_configs: Option>, 8 | } 9 | 10 | /// The Context of config changed. 11 | #[derive(Clone, Debug, Serialize, Deserialize)] 12 | pub(crate) struct ConfigContext { 13 | /// DataId 14 | #[serde(rename = "dataId")] 15 | pub(crate) data_id: String, 16 | /// Group 17 | pub(crate) group: String, 18 | /// Namespace/Tenant 19 | #[serde(rename = "tenant")] 20 | pub(crate) namespace: String, 21 | } 22 | -------------------------------------------------------------------------------- /src/config/message/response/config_change_notify_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | /// ConfigChangeNotifyResponse by client. 4 | #[response(identity = "ConfigChangeNotifyResponse", module = "config")] 5 | pub(crate) struct ConfigChangeNotifyResponse {} 6 | 7 | impl ConfigChangeNotifyResponse { 8 | /// Set request_id. 9 | pub(crate) fn request_id(mut self, request_id: String) -> Self { 10 | self.request_id = Some(request_id); 11 | self 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/config/message/response/config_publish_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | /// ConfigPublishResponse by server. 4 | #[response(identity = "ConfigPublishResponse", module = "config")] 5 | pub(crate) struct ConfigPublishResponse {} 6 | -------------------------------------------------------------------------------- /src/config/message/response/config_query_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | const CONFIG_NOT_FOUND: i32 = 300; 4 | const CONFIG_QUERY_CONFLICT: i32 = 400; 5 | 6 | /// ConfigQueryResponse by server. 7 | #[response(identity = "ConfigQueryResponse", module = "config")] 8 | pub(crate) struct ConfigQueryResponse { 9 | /// json, properties, txt, html, xml, ... 10 | pub(crate) content_type: Option, 11 | pub(crate) content: Option, 12 | pub(crate) md5: Option, 13 | /// whether content was encrypted with encryptedDataKey. 14 | pub(crate) encrypted_data_key: Option, 15 | 16 | /// now is useless. 17 | pub(crate) tag: Option, 18 | pub(crate) last_modified: i64, 19 | pub(crate) beta: bool, 20 | } 21 | 22 | impl ConfigQueryResponse { 23 | pub(crate) fn is_not_found(&self) -> bool { 24 | self.error_code == CONFIG_NOT_FOUND 25 | } 26 | pub(crate) fn is_query_conflict(&self) -> bool { 27 | self.error_code == CONFIG_QUERY_CONFLICT 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/config/message/response/config_remove_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | /// ConfigRemoveResponse by server. 4 | #[response(identity = "ConfigRemoveResponse", module = "config")] 5 | pub(crate) struct ConfigRemoveResponse {} 6 | -------------------------------------------------------------------------------- /src/config/message/response/mod.rs: -------------------------------------------------------------------------------- 1 | mod config_batch_listen_response; 2 | mod config_change_notify_response; 3 | mod config_publish_response; 4 | mod config_query_response; 5 | mod config_remove_response; 6 | 7 | pub(crate) use config_batch_listen_response::*; 8 | pub(crate) use config_change_notify_response::*; 9 | pub(crate) use config_publish_response::*; 10 | pub(crate) use config_query_response::*; 11 | pub(crate) use config_remove_response::*; 12 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | mod cache; 2 | mod handler; 3 | mod message; 4 | mod util; 5 | mod worker; 6 | 7 | use std::sync::atomic::{AtomicU64, Ordering}; 8 | 9 | use tracing::instrument; 10 | 11 | use crate::api::plugin::{AuthPlugin, ConfigFilter}; 12 | use crate::api::props::ClientProps; 13 | use crate::config::worker::ConfigWorker; 14 | 15 | pub(crate) struct NacosConfigService { 16 | /// config client worker 17 | client_worker: ConfigWorker, 18 | client_id: String, 19 | } 20 | 21 | impl std::fmt::Debug for NacosConfigService { 22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 23 | f.debug_struct("NacosConfigService") 24 | .field( 25 | "namespace", 26 | &self.client_worker.client_props.get_namespace(), 27 | ) 28 | .field("client_id", &self.client_id) 29 | .finish() 30 | } 31 | } 32 | 33 | const MODULE_NAME: &str = "config"; 34 | static SEQ: AtomicU64 = AtomicU64::new(1); 35 | 36 | fn generate_client_id(server_addr: &str, namespace: &str) -> String { 37 | // module_name + server_addr + namespace + [seq] 38 | let client_id = format!( 39 | "{MODULE_NAME}:{server_addr}:{namespace}:{}", 40 | SEQ.fetch_add(1, Ordering::SeqCst) 41 | ); 42 | client_id 43 | } 44 | 45 | impl NacosConfigService { 46 | pub fn new( 47 | client_props: ClientProps, 48 | auth_plugin: std::sync::Arc, 49 | config_filters: Vec>, 50 | ) -> crate::api::error::Result { 51 | let client_id = generate_client_id( 52 | &client_props.get_server_addr(), 53 | &client_props.get_namespace(), 54 | ); 55 | let client_worker = 56 | ConfigWorker::new(client_props, auth_plugin, config_filters, client_id.clone())?; 57 | Ok(Self { 58 | client_worker, 59 | client_id, 60 | }) 61 | } 62 | } 63 | 64 | impl NacosConfigService { 65 | #[instrument(fields(client_id = &self.client_id, group = group, data_id = data_id), skip_all)] 66 | pub(crate) async fn get_config( 67 | &self, 68 | data_id: String, 69 | group: String, 70 | ) -> crate::api::error::Result { 71 | self.client_worker.get_config(data_id, group).await 72 | } 73 | 74 | #[instrument(fields(client_id = &self.client_id, group = group, data_id = data_id), skip_all)] 75 | pub(crate) async fn publish_config( 76 | &self, 77 | data_id: String, 78 | group: String, 79 | content: String, 80 | content_type: Option, 81 | ) -> crate::api::error::Result { 82 | self.client_worker 83 | .publish_config(data_id, group, content, content_type) 84 | .await 85 | } 86 | 87 | #[instrument(fields(client_id = &self.client_id, group = group, data_id = data_id), skip_all)] 88 | pub(crate) async fn publish_config_cas( 89 | &self, 90 | data_id: String, 91 | group: String, 92 | content: String, 93 | content_type: Option, 94 | cas_md5: String, 95 | ) -> crate::api::error::Result { 96 | self.client_worker 97 | .publish_config_cas(data_id, group, content, content_type, cas_md5) 98 | .await 99 | } 100 | 101 | #[instrument(fields(client_id = &self.client_id, group = group, data_id = data_id), skip_all)] 102 | pub(crate) async fn publish_config_beta( 103 | &self, 104 | data_id: String, 105 | group: String, 106 | content: String, 107 | content_type: Option, 108 | beta_ips: String, 109 | ) -> crate::api::error::Result { 110 | self.client_worker 111 | .publish_config_beta(data_id, group, content, content_type, beta_ips) 112 | .await 113 | } 114 | 115 | #[instrument(fields(client_id = &self.client_id, group = group, data_id = data_id), skip_all)] 116 | pub(crate) async fn publish_config_param( 117 | &self, 118 | data_id: String, 119 | group: String, 120 | content: String, 121 | content_type: Option, 122 | cas_md5: Option, 123 | params: std::collections::HashMap, 124 | ) -> crate::api::error::Result { 125 | self.client_worker 126 | .publish_config_param(data_id, group, content, content_type, cas_md5, params) 127 | .await 128 | } 129 | 130 | #[instrument(fields(client_id = &self.client_id, group = group, data_id = data_id), skip_all)] 131 | pub(crate) async fn remove_config( 132 | &self, 133 | data_id: String, 134 | group: String, 135 | ) -> crate::api::error::Result { 136 | self.client_worker.remove_config(data_id, group).await 137 | } 138 | 139 | #[instrument(fields(client_id = &self.client_id, group = group, data_id = data_id), skip_all)] 140 | pub(crate) async fn add_listener( 141 | &self, 142 | data_id: String, 143 | group: String, 144 | listener: std::sync::Arc, 145 | ) -> crate::api::error::Result<()> { 146 | self.client_worker 147 | .add_listener(data_id, group, listener) 148 | .await; 149 | Ok(()) 150 | } 151 | 152 | #[instrument(fields(client_id = &self.client_id, group = group, data_id = data_id), skip_all)] 153 | pub(crate) async fn remove_listener( 154 | &self, 155 | data_id: String, 156 | group: String, 157 | listener: std::sync::Arc, 158 | ) -> crate::api::error::Result<()> { 159 | self.client_worker 160 | .remove_listener(data_id, group, listener) 161 | .await; 162 | Ok(()) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/config/util.rs: -------------------------------------------------------------------------------- 1 | /// A special splitter that reduces user-defined character repetition. 2 | const GROUP_KEY_SPLIT: &str = "+_+"; 3 | 4 | /// group to data_id '+_+' group '+_+' namespace 5 | pub(crate) fn group_key(data_id: &str, group: &str, namespace: &str) -> String { 6 | "".to_string() + data_id + GROUP_KEY_SPLIT + group + GROUP_KEY_SPLIT + namespace 7 | } 8 | 9 | /// parse group_key to (data_id, group, namespace) 10 | #[allow(dead_code, clippy::get_first)] 11 | pub(crate) fn parse_key(group_key: &str) -> (String, String, String) { 12 | let v: Vec<&str> = group_key.split(GROUP_KEY_SPLIT).collect(); 13 | ( 14 | v.get(0).unwrap().to_string(), 15 | v.get(1).unwrap().to_string(), 16 | v.get(2).unwrap().to_string(), 17 | ) 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use crate::config::util; 23 | 24 | #[test] 25 | fn test_group_key_and_parse() { 26 | let data_id = "data_id"; 27 | let group = "group"; 28 | let namespace = "namespace"; 29 | 30 | let group_key = util::group_key(data_id, group, namespace); 31 | let (parse_data_id, parse_group, parse_namespace) = util::parse_key(group_key.as_str()); 32 | 33 | assert_eq!(parse_data_id, data_id); 34 | assert_eq!(parse_group, group); 35 | assert_eq!(parse_namespace, namespace); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Licensed to the Apache Software Foundation (ASF) under one or more 2 | // contributor license agreements. See the NOTICE file distributed with 3 | // this work for additional information regarding copyright ownership. 4 | // The ASF licenses this file to You under the Apache License, Version 2.0 5 | // (the "License"); you may not use this file except in compliance with 6 | // the License. You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #![deny(rust_2018_idioms, clippy::disallowed_methods, clippy::disallowed_types)] 18 | #![cfg_attr(docsrs, feature(doc_cfg))] 19 | #![cfg_attr(docsrs, allow(unused_attributes))] 20 | 21 | //! # Nacos in Rust 22 | //! 23 | //! Thorough examples have been provided in our [repository](https://github.com/nacos-group/nacos-sdk-rust). 24 | //! 25 | //! ## Add Dependency 26 | //! 27 | //! Add the dependency in `Cargo.toml`: 28 | //! - If you need sync API, maybe `futures::executor::block_on(future_fn)` 29 | //! ```toml 30 | //! [dependencies] 31 | //! nacos-sdk = { version = "0.5", features = ["default"] } 32 | //! ``` 33 | //! 34 | //! ## General Configurations and Initialization 35 | //! 36 | //! Nacos needs to be initialized. Please see the `api` module. 37 | //! 38 | //! ### Example of Config 39 | //! 40 | //! ```ignore 41 | //! let config_service = nacos_sdk::api::config::ConfigServiceBuilder::new( 42 | //! nacos_sdk::api::props::ClientProps::new() 43 | //! .server_addr("127.0.0.1:8848") 44 | //! // Attention! "public" is "", it is recommended to customize the namespace with clear meaning. 45 | //! .namespace("") 46 | //! .app_name("todo-your-app-name"), 47 | //! ) 48 | //! .build()?; 49 | //! ``` 50 | //! 51 | //! ### Example of Naming 52 | //! 53 | //! ```ignore 54 | //! let naming_service = nacos_sdk::api::naming::NamingServiceBuilder::new( 55 | //! nacos_sdk::api::props::ClientProps::new() 56 | //! .server_addr("127.0.0.1:8848") 57 | //! // Attention! "public" is "", it is recommended to customize the namespace with clear meaning. 58 | //! .namespace("") 59 | //! .app_name("todo-your-app-name"), 60 | //! ) 61 | //! .build()?; 62 | //! ``` 63 | //! 64 | 65 | /// Nacos API 66 | pub mod api; 67 | 68 | mod common; 69 | #[cfg(feature = "config")] 70 | mod config; 71 | #[cfg(feature = "naming")] 72 | mod naming; 73 | 74 | #[allow(dead_code)] 75 | #[path = ""] 76 | mod nacos_proto { 77 | #[path = "_.rs"] 78 | pub mod v2; 79 | } 80 | 81 | use crate::api::constants::ENV_NACOS_CLIENT_PROPS_FILE_PATH; 82 | use std::collections::HashMap; 83 | 84 | static PROPERTIES: std::sync::LazyLock> = std::sync::LazyLock::new(|| { 85 | let env_file_path = std::env::var(ENV_NACOS_CLIENT_PROPS_FILE_PATH).ok(); 86 | let _ = env_file_path.as_ref().map(|file_path| { 87 | dotenvy::from_path(std::path::Path::new(file_path)).inspect_err(|_e| { 88 | let _ = dotenvy::dotenv(); 89 | }) 90 | }); 91 | dotenvy::dotenv().ok(); 92 | 93 | dotenvy::vars().collect::>() 94 | }); 95 | 96 | pub(crate) mod properties { 97 | use crate::PROPERTIES; 98 | 99 | pub(crate) fn get_value_option(key: Key) -> Option 100 | where 101 | Key: AsRef, 102 | { 103 | PROPERTIES.get(key.as_ref()).cloned() 104 | } 105 | 106 | pub(crate) fn get_value(key: Key, default: Default) -> String 107 | where 108 | Key: AsRef, 109 | Default: AsRef, 110 | { 111 | PROPERTIES 112 | .get(key.as_ref()) 113 | .map_or(default.as_ref().to_string(), |value| value.to_string()) 114 | } 115 | 116 | pub(crate) fn get_value_u32(key: Key, default: u32) -> u32 117 | where 118 | Key: AsRef, 119 | { 120 | PROPERTIES.get(key.as_ref()).map_or(default, |value| { 121 | value.to_string().parse::().unwrap_or(default) 122 | }) 123 | } 124 | 125 | pub(crate) fn get_value_bool(key: Key, default: bool) -> bool 126 | where 127 | Key: AsRef, 128 | { 129 | PROPERTIES.get(key.as_ref()).map_or(default, |value| { 130 | value.to_string().parse::().unwrap_or(default) 131 | }) 132 | } 133 | } 134 | 135 | #[cfg(test)] 136 | mod tests { 137 | use prost_types::Any; 138 | use std::collections::HashMap; 139 | 140 | use crate::nacos_proto::v2::Metadata; 141 | use crate::nacos_proto::v2::Payload; 142 | 143 | #[test] 144 | fn it_works_nacos_proto() { 145 | let body = Any { 146 | type_url: String::new(), 147 | value: Vec::from("{\"cluster\":\"DEFAULT\",\"healthyOnly\":true}"), 148 | }; 149 | let metadata = Metadata { 150 | r#type: String::from("com.alibaba.nacos.api.naming.remote.request.ServiceQueryRequest"), 151 | client_ip: String::from("127.0.0.1"), 152 | headers: HashMap::new(), 153 | }; 154 | let payload = Payload { 155 | metadata: Some(metadata), 156 | body: Some(body), 157 | }; 158 | // println!("{:?}", payload); 159 | assert_eq!( 160 | payload.metadata.unwrap().r#type, 161 | "com.alibaba.nacos.api.naming.remote.request.ServiceQueryRequest" 162 | ); 163 | assert_eq!( 164 | payload.body.unwrap().value, 165 | Vec::from("{\"cluster\":\"DEFAULT\",\"healthyOnly\":true}") 166 | ); 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod test_props { 172 | use crate::api::constants::ENV_NACOS_CLIENT_NAMING_PUSH_EMPTY_PROTECTION; 173 | use crate::properties::{get_value, get_value_bool, get_value_u32}; 174 | 175 | #[test] 176 | fn test_get_value() { 177 | let v = get_value("ENV_TEST", "TEST"); 178 | assert_eq!(v, "TEST"); 179 | } 180 | 181 | #[test] 182 | fn test_get_value_bool() { 183 | let v = get_value_bool(ENV_NACOS_CLIENT_NAMING_PUSH_EMPTY_PROTECTION, true); 184 | assert_eq!(v, true); 185 | } 186 | 187 | #[test] 188 | fn test_get_value_u32() { 189 | let not_exist_key = "MUST_NOT_EXIST"; 190 | let v = get_value_u32(not_exist_key, 91); 191 | assert_eq!(v, 91); 192 | } 193 | } 194 | 195 | #[cfg(test)] 196 | mod test_config { 197 | use std::sync::Once; 198 | 199 | use tracing::metadata::LevelFilter; 200 | 201 | static LOGGER_INIT: Once = Once::new(); 202 | 203 | pub(crate) fn setup_log() { 204 | LOGGER_INIT.call_once(|| { 205 | tracing_subscriber::fmt() 206 | .with_thread_names(true) 207 | .with_file(true) 208 | .with_level(true) 209 | .with_line_number(true) 210 | .with_thread_ids(true) 211 | .with_max_level(LevelFilter::DEBUG) 212 | .init() 213 | }); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/naming/chooser/mod.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | 3 | use crate::api::error::Error::ErrResult; 4 | use crate::api::error::Result; 5 | use crate::api::naming::{InstanceChooser, ServiceInstance}; 6 | 7 | pub(crate) struct RandomWeightChooser { 8 | weights: Vec, 9 | 10 | items: Vec, 11 | } 12 | 13 | impl RandomWeightChooser { 14 | pub fn new(service_name: String, items: Vec) -> Result { 15 | if items.is_empty() { 16 | return Err(ErrResult(format!( 17 | "no available {service_name} service instance can be selected" 18 | ))); 19 | } 20 | let mut init_items: Vec = Vec::with_capacity(items.len()); 21 | let mut origin_weight_sum = 0_f64; 22 | 23 | let mut count = 0; 24 | for instance in items.iter() { 25 | let mut weight = instance.weight; 26 | 27 | if weight <= 0_f64 { 28 | continue; 29 | } 30 | 31 | if weight.is_infinite() { 32 | weight = 10000.0_f64; 33 | } 34 | 35 | if weight.is_nan() { 36 | weight = 1.0_f64; 37 | } 38 | origin_weight_sum += weight; 39 | count += 1; 40 | } 41 | 42 | let mut exact_weights: Vec = Vec::with_capacity(count); 43 | let mut index = 0; 44 | 45 | for instance in items.into_iter() { 46 | let single_weight = instance.weight; 47 | 48 | if single_weight <= 0_f64 { 49 | continue; 50 | } 51 | 52 | init_items.push(instance); 53 | 54 | exact_weights.insert(index, single_weight / origin_weight_sum); 55 | index += 1; 56 | } 57 | 58 | let mut weights: Vec = Vec::with_capacity(count); 59 | 60 | let mut random_range = 0_f64; 61 | 62 | for (i, exact_weights_item) in exact_weights.iter().enumerate().take(index) { 63 | weights.insert(i, random_range + exact_weights_item); 64 | random_range += exact_weights_item; 65 | } 66 | 67 | let double_precision_delta = 0.0001_f64; 68 | 69 | if index == 0 || (weights[index - 1] - 1.0_f64).abs() < double_precision_delta { 70 | return Ok(RandomWeightChooser { 71 | weights, 72 | items: init_items, 73 | }); 74 | } 75 | 76 | Err(ErrResult( 77 | "Cumulative Weight calculate wrong , the sum of probabilities does not equals 1." 78 | .to_string(), 79 | )) 80 | } 81 | } 82 | 83 | impl InstanceChooser for RandomWeightChooser { 84 | fn choose(mut self) -> Option { 85 | let mut rng = rand::thread_rng(); 86 | let random_number = rng.gen_range(0.0..1.0); 87 | let index = self 88 | .weights 89 | .binary_search_by(|d| d.partial_cmp(&random_number).unwrap()); 90 | if let Ok(index) = index { 91 | let instance = self.items.get(index); 92 | if let Some(instance) = instance { 93 | return Some(instance.to_owned()); 94 | } else { 95 | return self.items.pop(); 96 | } 97 | } else { 98 | let index = index.unwrap_err(); 99 | if index < self.weights.len() { 100 | let weight = self.weights.get(index); 101 | if weight.is_none() { 102 | return self.items.pop(); 103 | } 104 | let weight = weight.unwrap().to_owned(); 105 | if random_number < weight { 106 | let instance = self.items.get(index); 107 | if instance.is_none() { 108 | return self.items.pop(); 109 | } 110 | let instance = instance.unwrap(); 111 | return Some(instance.to_owned()); 112 | } 113 | } 114 | } 115 | 116 | self.items.pop() 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/naming/dto/mod.rs: -------------------------------------------------------------------------------- 1 | mod service_info; 2 | pub(crate) use service_info::*; 3 | -------------------------------------------------------------------------------- /src/naming/dto/service_info.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use tracing::error; 3 | 4 | use crate::api::naming::ServiceInstance; 5 | 6 | #[derive(Clone, Debug, Serialize, Deserialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct ServiceInfo { 9 | pub name: String, 10 | 11 | pub group_name: String, 12 | 13 | pub clusters: String, 14 | 15 | pub cache_millis: i64, 16 | 17 | pub last_ref_time: i64, 18 | 19 | pub checksum: String, 20 | 21 | #[serde(rename = "allIPs")] 22 | pub all_ips: bool, 23 | 24 | pub reach_protection_threshold: bool, 25 | 26 | pub hosts: Option>, 27 | } 28 | 29 | const SERVICE_INFO_SEPARATOR: &str = "@@"; 30 | impl ServiceInfo { 31 | pub fn ip_count(&self) -> i32 { 32 | if self.hosts.is_none() { 33 | return 0; 34 | } 35 | self.hosts.as_ref().unwrap().len() as i32 36 | } 37 | 38 | pub fn validate(&self) -> bool { 39 | if self.all_ips { 40 | return true; 41 | } 42 | 43 | if self.hosts.is_none() { 44 | return false; 45 | } 46 | 47 | let hosts = self.hosts.as_ref().unwrap(); 48 | for host in hosts { 49 | if !host.healthy { 50 | continue; 51 | } 52 | 53 | if host.weight > 0 as f64 { 54 | return true; 55 | } 56 | } 57 | 58 | false 59 | } 60 | 61 | pub fn get_grouped_service_name(service_name: &str, group_name: &str) -> String { 62 | if !group_name.is_empty() && !service_name.contains(SERVICE_INFO_SEPARATOR) { 63 | let service_name = format!("{}{}{}", &group_name, SERVICE_INFO_SEPARATOR, service_name); 64 | return service_name; 65 | } 66 | service_name.to_string() 67 | } 68 | 69 | pub fn hosts_to_json(&self) -> String { 70 | if self.hosts.is_none() { 71 | return "".to_string(); 72 | } 73 | let json = serde_json::to_string(self.hosts.as_ref().unwrap()); 74 | if let Err(e) = json { 75 | error!("hosts to json failed. {e:?}"); 76 | return "".to_string(); 77 | } 78 | json.unwrap() 79 | } 80 | 81 | pub fn get_key(name: &str, clusters: &str) -> String { 82 | if !clusters.is_empty() { 83 | let key = format!("{}{}{}", name, SERVICE_INFO_SEPARATOR, clusters); 84 | return key; 85 | } 86 | 87 | name.to_string() 88 | } 89 | } 90 | 91 | impl Default for ServiceInfo { 92 | fn default() -> Self { 93 | Self { 94 | name: Default::default(), 95 | group_name: Default::default(), 96 | clusters: Default::default(), 97 | cache_millis: 1000, 98 | last_ref_time: 0, 99 | checksum: Default::default(), 100 | all_ips: false, 101 | reach_protection_threshold: false, 102 | hosts: Default::default(), 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/naming/handler/mod.rs: -------------------------------------------------------------------------------- 1 | mod naming_push_request_handler; 2 | 3 | pub(crate) use naming_push_request_handler::*; 4 | -------------------------------------------------------------------------------- /src/naming/handler/naming_push_request_handler.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::common::remote::grpc::message::{GrpcMessage, GrpcMessageBuilder}; 4 | use crate::common::remote::grpc::nacos_grpc_service::ServerRequestHandler; 5 | use crate::naming::observable::service_info_observable::ServiceInfoEmitter; 6 | 7 | use async_trait::async_trait; 8 | use tracing::{Instrument, error, info}; 9 | 10 | use crate::{ 11 | nacos_proto::v2::Payload, 12 | naming::message::{request::NotifySubscriberRequest, response::NotifySubscriberResponse}, 13 | }; 14 | 15 | pub(crate) struct NamingPushRequestHandler { 16 | service_info_emitter: Arc, 17 | } 18 | 19 | impl NamingPushRequestHandler { 20 | pub(crate) fn new(service_info_emitter: Arc) -> Self { 21 | Self { 22 | service_info_emitter, 23 | } 24 | } 25 | } 26 | 27 | #[async_trait] 28 | impl ServerRequestHandler for NamingPushRequestHandler { 29 | async fn request_reply(&self, request: Payload) -> Option { 30 | let request = GrpcMessage::::from_payload(request); 31 | if let Err(e) = request { 32 | error!("convert payload to NotifySubscriberRequest error. {e:?}"); 33 | return None; 34 | } 35 | let request = request.unwrap(); 36 | 37 | let body = request.into_body(); 38 | info!("receive NotifySubscriberRequest from nacos server: {body:?}"); 39 | 40 | let request_id = body.request_id; 41 | self.service_info_emitter 42 | .emit(body.service_info) 43 | .in_current_span() 44 | .await; 45 | 46 | let mut response = NotifySubscriberResponse::ok(); 47 | response.request_id = request_id; 48 | 49 | let grpc_message = GrpcMessageBuilder::new(response).build(); 50 | let payload = grpc_message.into_payload(); 51 | if let Err(e) = payload { 52 | error!("occur an error when handing NotifySubscriberRequest. {e:?}"); 53 | return None; 54 | } 55 | let payload = payload.unwrap(); 56 | 57 | return Some(payload); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/naming/message/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod request; 2 | pub(crate) mod response; 3 | -------------------------------------------------------------------------------- /src/naming/message/request/batch_instance_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | use crate::{api::naming::ServiceInstance, common::remote::generate_request_id}; 4 | 5 | #[request(identity = "BatchInstanceRequest", module = "naming")] 6 | pub(crate) struct BatchInstanceRequest { 7 | #[serde(rename = "type")] 8 | pub(crate) r_type: String, 9 | 10 | pub(crate) instances: Vec, 11 | } 12 | 13 | impl BatchInstanceRequest { 14 | pub(crate) fn new( 15 | instances: Vec, 16 | namespace: Option, 17 | service_name: Option, 18 | group_name: Option, 19 | ) -> Self { 20 | let request_id = Some(generate_request_id()); 21 | Self { 22 | r_type: "batchRegisterInstance".to_string(), 23 | instances, 24 | request_id, 25 | namespace, 26 | service_name, 27 | group_name, 28 | ..Default::default() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/naming/message/request/instance_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | use crate::{common::remote::generate_request_id, naming::ServiceInstance}; 4 | 5 | #[request(identity = "InstanceRequest", module = "naming")] 6 | pub(crate) struct InstanceRequest { 7 | #[serde(rename = "type")] 8 | pub(crate) r_type: String, 9 | pub(crate) instance: ServiceInstance, 10 | } 11 | 12 | impl InstanceRequest { 13 | pub(crate) fn register( 14 | instance: ServiceInstance, 15 | service_name: Option, 16 | namespace: Option, 17 | group_name: Option, 18 | ) -> Self { 19 | InstanceRequest::new( 20 | RequestType::Register, 21 | instance, 22 | service_name, 23 | namespace, 24 | group_name, 25 | ) 26 | } 27 | 28 | pub(crate) fn deregister( 29 | instance: ServiceInstance, 30 | service_name: Option, 31 | namespace: Option, 32 | group_name: Option, 33 | ) -> Self { 34 | InstanceRequest::new( 35 | RequestType::Deregister, 36 | instance, 37 | service_name, 38 | namespace, 39 | group_name, 40 | ) 41 | } 42 | 43 | fn new( 44 | request_type: RequestType, 45 | instance: ServiceInstance, 46 | service_name: Option, 47 | namespace: Option, 48 | group_name: Option, 49 | ) -> Self { 50 | let request_id = Some(generate_request_id()); 51 | Self { 52 | r_type: request_type.request_code(), 53 | instance, 54 | request_id, 55 | namespace, 56 | service_name, 57 | group_name, 58 | ..Default::default() 59 | } 60 | } 61 | } 62 | 63 | pub(crate) enum RequestType { 64 | Register, 65 | Deregister, 66 | } 67 | 68 | impl RequestType { 69 | pub(crate) fn request_code(&self) -> String { 70 | match self { 71 | RequestType::Register => "registerInstance".to_string(), 72 | RequestType::Deregister => "deregisterInstance".to_string(), 73 | } 74 | } 75 | } 76 | 77 | impl From<&str> for RequestType { 78 | fn from(code: &str) -> Self { 79 | match code { 80 | "registerInstance" => RequestType::Register, 81 | "deregisterInstance" => RequestType::Deregister, 82 | _ => RequestType::Register, 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/naming/message/request/mod.rs: -------------------------------------------------------------------------------- 1 | mod batch_instance_request; 2 | mod instance_request; 3 | mod notify_subscriber_request; 4 | mod persistent_instance_request; 5 | mod service_list_request; 6 | mod service_query_request; 7 | mod subscribe_service_request; 8 | 9 | pub(crate) use batch_instance_request::*; 10 | pub(crate) use instance_request::*; 11 | pub(crate) use notify_subscriber_request::*; 12 | pub(crate) use persistent_instance_request::*; 13 | pub(crate) use service_list_request::*; 14 | pub(crate) use service_query_request::*; 15 | pub(crate) use subscribe_service_request::*; 16 | -------------------------------------------------------------------------------- /src/naming/message/request/notify_subscriber_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | use crate::naming::dto::ServiceInfo; 4 | 5 | #[request(identity = "NotifySubscriberRequest", module = "naming")] 6 | pub(crate) struct NotifySubscriberRequest { 7 | pub(crate) service_info: ServiceInfo, 8 | } 9 | -------------------------------------------------------------------------------- /src/naming/message/request/persistent_instance_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | use crate::{api::naming::ServiceInstance, common::remote::generate_request_id}; 4 | 5 | #[request(identity = "PersistentInstanceRequest", module = "naming")] 6 | pub(crate) struct PersistentInstanceRequest { 7 | #[serde(rename = "type")] 8 | pub(crate) r_type: String, 9 | pub(crate) instance: ServiceInstance, 10 | } 11 | 12 | impl PersistentInstanceRequest { 13 | pub(crate) fn register( 14 | instance: ServiceInstance, 15 | service_name: Option, 16 | namespace: Option, 17 | group_name: Option, 18 | ) -> Self { 19 | PersistentInstanceRequest::new( 20 | RequestType::Register, 21 | instance, 22 | service_name, 23 | namespace, 24 | group_name, 25 | ) 26 | } 27 | 28 | pub(crate) fn deregister( 29 | instance: ServiceInstance, 30 | service_name: Option, 31 | namespace: Option, 32 | group_name: Option, 33 | ) -> Self { 34 | PersistentInstanceRequest::new( 35 | RequestType::Deregister, 36 | instance, 37 | service_name, 38 | namespace, 39 | group_name, 40 | ) 41 | } 42 | 43 | fn new( 44 | request_type: RequestType, 45 | instance: ServiceInstance, 46 | service_name: Option, 47 | namespace: Option, 48 | group_name: Option, 49 | ) -> Self { 50 | let request_id = Some(generate_request_id()); 51 | Self { 52 | r_type: request_type.request_code(), 53 | instance, 54 | request_id, 55 | namespace, 56 | service_name, 57 | group_name, 58 | ..Default::default() 59 | } 60 | } 61 | } 62 | 63 | pub(crate) enum RequestType { 64 | Register, 65 | Deregister, 66 | } 67 | 68 | impl RequestType { 69 | pub(crate) fn request_code(&self) -> String { 70 | match self { 71 | RequestType::Register => "registerInstance".to_string(), 72 | RequestType::Deregister => "deregisterInstance".to_string(), 73 | } 74 | } 75 | } 76 | 77 | impl From<&str> for RequestType { 78 | fn from(code: &str) -> Self { 79 | match code { 80 | "registerInstance" => RequestType::Register, 81 | "deregisterInstance" => RequestType::Deregister, 82 | _ => RequestType::Register, 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/naming/message/request/service_list_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | #[request(identity = "ServiceListRequest", module = "naming")] 4 | pub(crate) struct ServiceListRequest { 5 | pub(crate) page_no: i32, 6 | 7 | pub(crate) page_size: i32, 8 | 9 | pub(crate) selector: Option, 10 | } 11 | -------------------------------------------------------------------------------- /src/naming/message/request/service_query_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | #[request(identity = "ServiceQueryRequest", module = "naming")] 4 | pub(crate) struct ServiceQueryRequest { 5 | pub(crate) cluster: String, 6 | 7 | pub(crate) healthy_only: bool, 8 | 9 | pub(crate) udp_port: i32, 10 | } 11 | -------------------------------------------------------------------------------- /src/naming/message/request/subscribe_service_request.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::request; 2 | 3 | use crate::common::remote::generate_request_id; 4 | 5 | #[request(identity = "SubscribeServiceRequest", module = "naming")] 6 | pub(crate) struct SubscribeServiceRequest { 7 | pub(crate) subscribe: bool, 8 | 9 | pub(crate) clusters: String, 10 | } 11 | 12 | impl SubscribeServiceRequest { 13 | pub(crate) fn new( 14 | subscribe: bool, 15 | clusters: String, 16 | service_name: Option, 17 | namespace: Option, 18 | group_name: Option, 19 | ) -> Self { 20 | let request_id = Some(generate_request_id()); 21 | Self { 22 | subscribe, 23 | clusters, 24 | request_id, 25 | namespace, 26 | service_name, 27 | group_name, 28 | ..Default::default() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/naming/message/response/batch_instance_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | #[response(identity = "BatchInstanceResponse", module = "naming")] 4 | pub(crate) struct BatchInstanceResponse { 5 | #[serde(rename = "type")] 6 | pub(crate) r_type: Option, 7 | } 8 | -------------------------------------------------------------------------------- /src/naming/message/response/instance_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | #[response(identity = "InstanceResponse", module = "naming")] 4 | pub(crate) struct InstanceResponse { 5 | #[serde(rename = "type")] 6 | pub(crate) r_type: Option, 7 | } 8 | -------------------------------------------------------------------------------- /src/naming/message/response/mod.rs: -------------------------------------------------------------------------------- 1 | mod batch_instance_response; 2 | mod instance_response; 3 | mod notify_subscriber_response; 4 | mod query_service_response; 5 | mod service_list_response; 6 | mod subscribe_service_response; 7 | 8 | pub(crate) use batch_instance_response::*; 9 | pub(crate) use instance_response::*; 10 | pub(crate) use notify_subscriber_response::*; 11 | pub(crate) use query_service_response::*; 12 | pub(crate) use service_list_response::*; 13 | pub(crate) use subscribe_service_response::*; 14 | -------------------------------------------------------------------------------- /src/naming/message/response/notify_subscriber_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | #[response(identity = "NotifySubscriberResponse", module = "naming")] 4 | pub(crate) struct NotifySubscriberResponse {} 5 | -------------------------------------------------------------------------------- /src/naming/message/response/query_service_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | use crate::naming::dto::ServiceInfo; 4 | 5 | #[response(identity = "QueryServiceResponse", module = "naming")] 6 | pub(crate) struct QueryServiceResponse { 7 | pub(crate) service_info: ServiceInfo, 8 | } 9 | -------------------------------------------------------------------------------- /src/naming/message/response/service_list_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | #[response(identity = "ServiceListResponse", module = "naming")] 4 | pub(crate) struct ServiceListResponse { 5 | pub(crate) count: i32, 6 | 7 | pub(crate) service_names: Vec, 8 | } 9 | -------------------------------------------------------------------------------- /src/naming/message/response/subscribe_service_response.rs: -------------------------------------------------------------------------------- 1 | use nacos_macro::response; 2 | 3 | use crate::naming::dto::ServiceInfo; 4 | 5 | #[response(identity = "SubscribeServiceResponse", module = "naming")] 6 | pub(crate) struct SubscribeServiceResponse { 7 | pub(crate) service_info: ServiceInfo, 8 | } 9 | -------------------------------------------------------------------------------- /src/naming/observable/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod service_info_observable; 2 | -------------------------------------------------------------------------------- /src/naming/redo/automatic_request/batch_instance_request.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use std::sync::Arc; 3 | use tracing::Instrument; 4 | use tracing::debug; 5 | use tracing::error; 6 | use tracing::instrument; 7 | 8 | use crate::common::remote::grpc::NacosGrpcClient; 9 | use crate::{ 10 | common::remote::{generate_request_id, grpc::message::GrpcMessageData}, 11 | naming::{ 12 | message::{request::BatchInstanceRequest, response::BatchInstanceResponse}, 13 | redo::AutomaticRequest, 14 | }, 15 | }; 16 | 17 | #[async_trait] 18 | impl AutomaticRequest for BatchInstanceRequest { 19 | #[instrument(skip_all)] 20 | async fn run( 21 | &self, 22 | grpc_client: Arc, 23 | call_back: crate::naming::redo::CallBack, 24 | ) { 25 | let mut request = self.clone(); 26 | request.request_id = Some(generate_request_id()); 27 | debug!("automatically execute batch instance request. {request:?}"); 28 | let ret = grpc_client 29 | .send_request::(request) 30 | .in_current_span() 31 | .await; 32 | if let Err(e) = ret { 33 | error!("automatically execute batch instance request occur an error. {e:?}"); 34 | call_back(Err(e)); 35 | } else { 36 | call_back(Ok(())); 37 | } 38 | } 39 | 40 | fn name(&self) -> String { 41 | let namespace = self.namespace.as_deref().unwrap_or(""); 42 | let service_name = self.service_name.as_deref().unwrap_or(""); 43 | let group_name = self.group_name.as_deref().unwrap_or(""); 44 | 45 | let request_name = format!( 46 | "{}@@{}@@{}@@{}", 47 | namespace, 48 | group_name, 49 | service_name, 50 | BatchInstanceRequest::identity() 51 | ); 52 | request_name 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/naming/redo/automatic_request/instance_request.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use std::sync::Arc; 3 | use tracing::Instrument; 4 | use tracing::debug; 5 | use tracing::error; 6 | use tracing::instrument; 7 | 8 | use crate::common::remote::generate_request_id; 9 | use crate::common::remote::grpc::NacosGrpcClient; 10 | use crate::common::remote::grpc::message::GrpcMessageData; 11 | use crate::naming::message::response::InstanceResponse; 12 | use crate::naming::redo::CallBack; 13 | use crate::naming::{message::request::InstanceRequest, redo::AutomaticRequest}; 14 | 15 | #[async_trait] 16 | impl AutomaticRequest for InstanceRequest { 17 | #[instrument(skip_all)] 18 | async fn run(&self, grpc_client: Arc, call_back: CallBack) { 19 | let mut request = self.clone(); 20 | request.request_id = Some(generate_request_id()); 21 | debug!("automatically execute instance request. {request:?}"); 22 | let ret = grpc_client 23 | .send_request::(request) 24 | .in_current_span() 25 | .await; 26 | if let Err(e) = ret { 27 | error!("automatically execute instance request occur an error. {e:?}"); 28 | call_back(Err(e)); 29 | } else { 30 | call_back(Ok(())); 31 | } 32 | } 33 | 34 | fn name(&self) -> String { 35 | let namespace = self.namespace.as_deref().unwrap_or(""); 36 | let service_name = self.service_name.as_deref().unwrap_or(""); 37 | let group_name = self.group_name.as_deref().unwrap_or(""); 38 | 39 | let request_name = format!( 40 | "{}@@{}@@{}@@{}", 41 | namespace, 42 | group_name, 43 | service_name, 44 | InstanceRequest::identity() 45 | ); 46 | request_name 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/naming/redo/automatic_request/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod batch_instance_request; 2 | pub(crate) mod instance_request; 3 | pub(crate) mod persistent_instance_request; 4 | pub(crate) mod subscribe_service_request; 5 | -------------------------------------------------------------------------------- /src/naming/redo/automatic_request/persistent_instance_request.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use std::sync::Arc; 3 | use tracing::Instrument; 4 | use tracing::debug; 5 | use tracing::error; 6 | use tracing::instrument; 7 | 8 | use crate::common::remote::generate_request_id; 9 | use crate::common::remote::grpc::NacosGrpcClient; 10 | use crate::common::remote::grpc::message::GrpcMessageData; 11 | use crate::naming::message::request::PersistentInstanceRequest; 12 | use crate::naming::message::response::InstanceResponse; 13 | use crate::naming::redo::AutomaticRequest; 14 | use crate::naming::redo::CallBack; 15 | 16 | #[async_trait] 17 | impl AutomaticRequest for PersistentInstanceRequest { 18 | #[instrument(skip_all)] 19 | async fn run(&self, grpc_client: Arc, call_back: CallBack) { 20 | let mut request = self.clone(); 21 | request.request_id = Some(generate_request_id()); 22 | debug!("automatically execute persistent instance request. {request:?}"); 23 | let ret = grpc_client 24 | .send_request::(request) 25 | .in_current_span() 26 | .await; 27 | if let Err(e) = ret { 28 | error!("automatically execute persistent instance request occur an error. {e:?}"); 29 | call_back(Err(e)); 30 | } else { 31 | call_back(Ok(())); 32 | } 33 | } 34 | 35 | fn name(&self) -> String { 36 | let namespace = self.namespace.as_deref().unwrap_or(""); 37 | let service_name = self.service_name.as_deref().unwrap_or(""); 38 | let group_name = self.group_name.as_deref().unwrap_or(""); 39 | 40 | let request_name = format!( 41 | "{}@@{}@@{}@@{}", 42 | namespace, 43 | group_name, 44 | service_name, 45 | PersistentInstanceRequest::identity() 46 | ); 47 | request_name 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/naming/redo/automatic_request/subscribe_service_request.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use async_trait::async_trait; 4 | use tracing::Instrument; 5 | use tracing::debug; 6 | use tracing::error; 7 | use tracing::instrument; 8 | 9 | use crate::common::remote::generate_request_id; 10 | use crate::common::remote::grpc::NacosGrpcClient; 11 | use crate::common::remote::grpc::message::GrpcMessageData; 12 | use crate::naming::message::request::SubscribeServiceRequest; 13 | use crate::naming::message::response::SubscribeServiceResponse; 14 | use crate::naming::redo::AutomaticRequest; 15 | use crate::naming::redo::CallBack; 16 | 17 | #[async_trait] 18 | impl AutomaticRequest for SubscribeServiceRequest { 19 | #[instrument(skip_all)] 20 | async fn run(&self, grpc_client: Arc, call_back: CallBack) { 21 | let mut request = self.clone(); 22 | request.request_id = Some(generate_request_id()); 23 | debug!("automatically execute subscribe service. {request:?}"); 24 | let ret = grpc_client 25 | .send_request::(request) 26 | .in_current_span() 27 | .await; 28 | if let Err(e) = ret { 29 | error!("automatically execute subscribe service occur an error. {e:?}"); 30 | call_back(Err(e)); 31 | } else { 32 | call_back(Ok(())); 33 | } 34 | } 35 | 36 | fn name(&self) -> String { 37 | let namespace = self.namespace.as_deref().unwrap_or(""); 38 | let service_name = self.service_name.as_deref().unwrap_or(""); 39 | let group_name = self.group_name.as_deref().unwrap_or(""); 40 | 41 | let request_name = format!( 42 | "{}@@{}@@{}@@{}", 43 | namespace, 44 | group_name, 45 | service_name, 46 | SubscribeServiceRequest::identity() 47 | ); 48 | request_name 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/naming/redo/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | sync::{ 4 | Arc, 5 | atomic::{AtomicBool, Ordering}, 6 | }, 7 | time::Duration, 8 | }; 9 | 10 | use async_trait::async_trait; 11 | use tokio::{ 12 | sync::RwLock, 13 | time::{self, sleep}, 14 | }; 15 | use tracing::{Instrument, debug, debug_span, instrument}; 16 | 17 | use crate::api::error::Result; 18 | use crate::common::{executor, remote::grpc::NacosGrpcClient}; 19 | 20 | pub(crate) mod automatic_request; 21 | 22 | pub(crate) struct RedoTaskExecutor { 23 | map: Arc>>>, 24 | id: String, 25 | } 26 | 27 | impl RedoTaskExecutor { 28 | pub(crate) fn new(id: String) -> Self { 29 | let executor = Self { 30 | id, 31 | map: Arc::new(RwLock::new(HashMap::new())), 32 | }; 33 | executor.start_schedule(); 34 | executor 35 | } 36 | 37 | fn start_schedule(&self) { 38 | let _span_enter = debug_span!("RedoTaskExecutor-task", id = self.id).entered(); 39 | debug!("start schedule automatic request task."); 40 | let map = self.map.clone(); 41 | executor::spawn( 42 | async move { 43 | sleep(Duration::from_millis(3000)).await; 44 | let mut interval = time::interval(Duration::from_millis(3000)); 45 | loop { 46 | interval.tick().await; 47 | 48 | let map = map.read().await; 49 | let active_tasks: Vec> = map 50 | .iter() 51 | .filter(|(_, v)| v.is_active()) 52 | .map(|(_, v)| v.clone()) 53 | .collect(); 54 | if !active_tasks.is_empty() { 55 | debug!("automatic request task triggered!"); 56 | } 57 | for task in active_tasks { 58 | debug!("automatic request task: {:?}", task.task_key()); 59 | task.run().await; 60 | } 61 | } 62 | } 63 | .in_current_span(), 64 | ); 65 | } 66 | 67 | #[instrument(skip_all)] 68 | pub(crate) async fn add_task(&self, task: Arc) { 69 | let mut map = self.map.write().await; 70 | let task_key = task.task_key(); 71 | map.insert(task_key, task); 72 | } 73 | 74 | #[instrument(fields(task_key = task_key), skip_all)] 75 | pub(crate) async fn remove_task(&self, task_key: &str) { 76 | let mut map = self.map.write().await; 77 | map.remove(task_key); 78 | } 79 | 80 | #[instrument(fields(client_id = &self.id), skip_all)] 81 | pub(crate) async fn on_grpc_client_reconnect(&self) { 82 | let map = self.map.read().await; 83 | for (_, v) in map.iter() { 84 | v.active() 85 | } 86 | } 87 | 88 | #[instrument(fields(client_id = &self.id), skip_all)] 89 | pub(crate) async fn on_grpc_client_disconnect(&self) { 90 | let map = self.map.read().await; 91 | for (_, v) in map.iter() { 92 | v.frozen() 93 | } 94 | } 95 | } 96 | 97 | #[async_trait] 98 | pub(crate) trait RedoTask: Send + Sync + 'static { 99 | fn task_key(&self) -> String; 100 | 101 | fn frozen(&self); 102 | 103 | fn active(&self); 104 | 105 | fn is_active(&self) -> bool; 106 | 107 | async fn run(&self); 108 | } 109 | 110 | type CallBack = Box) + Send + Sync + 'static>; 111 | 112 | #[async_trait] 113 | pub(crate) trait AutomaticRequest: Send + Sync + 'static { 114 | async fn run(&self, grpc_client: Arc, call_back: CallBack); 115 | 116 | fn name(&self) -> String; 117 | } 118 | 119 | pub(crate) struct NamingRedoTask { 120 | active: Arc, 121 | automatic_request: Arc, 122 | grpc_client: Arc, 123 | } 124 | 125 | impl NamingRedoTask { 126 | pub(crate) fn new( 127 | grpc_client: Arc, 128 | automatic_request: Arc, 129 | ) -> Self { 130 | Self { 131 | active: Arc::new(AtomicBool::new(false)), 132 | automatic_request, 133 | grpc_client, 134 | } 135 | } 136 | } 137 | 138 | #[async_trait] 139 | impl RedoTask for NamingRedoTask { 140 | fn task_key(&self) -> String { 141 | self.automatic_request.name() 142 | } 143 | 144 | fn frozen(&self) { 145 | self.active.store(false, Ordering::Release) 146 | } 147 | 148 | fn active(&self) { 149 | self.active.store(true, Ordering::Release) 150 | } 151 | 152 | fn is_active(&self) -> bool { 153 | self.active.load(Ordering::Acquire) 154 | } 155 | 156 | #[instrument(skip_all)] 157 | async fn run(&self) { 158 | let active = self.active.clone(); 159 | self.automatic_request 160 | .run( 161 | self.grpc_client.clone(), 162 | Box::new(move |ret| { 163 | if ret.is_ok() { 164 | active.store(false, Ordering::Release); 165 | } else { 166 | active.store(true, Ordering::Release); 167 | } 168 | }), 169 | ) 170 | .await; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/naming/updater/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | sync::{ 4 | Arc, 5 | atomic::{AtomicBool, Ordering}, 6 | }, 7 | time::Duration, 8 | }; 9 | 10 | use tokio::{sync::Mutex, time::sleep}; 11 | use tracing::{Instrument, debug, error, info, instrument, warn}; 12 | 13 | use crate::common::{ 14 | cache::Cache, 15 | executor, 16 | remote::{ 17 | generate_request_id, 18 | grpc::{NacosGrpcClient, message::GrpcResponseMessage}, 19 | }, 20 | }; 21 | 22 | use super::{ 23 | dto::ServiceInfo, 24 | message::{request::ServiceQueryRequest, response::QueryServiceResponse}, 25 | observable::service_info_observable::ServiceInfoEmitter, 26 | }; 27 | 28 | pub(crate) struct ServiceInfoUpdater { 29 | service_info_emitter: Arc, 30 | cache: Arc>, 31 | nacos_grpc_client: Arc, 32 | task_map: Mutex>, 33 | } 34 | 35 | impl ServiceInfoUpdater { 36 | pub(crate) fn new( 37 | service_info_emitter: Arc, 38 | cache: Arc>, 39 | nacos_grpc_client: Arc, 40 | ) -> Self { 41 | Self { 42 | service_info_emitter, 43 | cache, 44 | nacos_grpc_client, 45 | task_map: Mutex::new(HashMap::default()), 46 | } 47 | } 48 | 49 | #[instrument(fields(group_name = group_name, cluster = cluster), skip_all)] 50 | pub(crate) async fn schedule_update( 51 | &self, 52 | namespace: String, 53 | service_name: String, 54 | group_name: String, 55 | cluster: String, 56 | ) { 57 | let task_key = ServiceInfoUpdater::update_task_key(&service_name, &group_name, &cluster); 58 | let mut lock = self.task_map.lock().await; 59 | 60 | let is_exist = lock.contains_key(&task_key); 61 | if !is_exist { 62 | let update_task = ServiceInfoUpdateTask::new( 63 | service_name, 64 | namespace, 65 | group_name, 66 | cluster, 67 | self.cache.clone(), 68 | self.nacos_grpc_client.clone(), 69 | self.service_info_emitter.clone(), 70 | ); 71 | update_task.start(); 72 | 73 | let _ = lock.insert(task_key, update_task); 74 | } 75 | } 76 | 77 | #[instrument(fields(group_name = group_name, cluster = cluster), skip_all)] 78 | pub(crate) async fn stop_update( 79 | &self, 80 | service_name: String, 81 | group_name: String, 82 | cluster: String, 83 | ) { 84 | let task_key = ServiceInfoUpdater::update_task_key(&service_name, &group_name, &cluster); 85 | let mut lock = self.task_map.lock().await; 86 | let ret = lock.remove(&task_key); 87 | if let Some(task) = ret { 88 | task.stop(); 89 | } 90 | } 91 | 92 | fn update_task_key(service_name: &str, group_name: &str, cluster: &str) -> String { 93 | let group_service_name = ServiceInfo::get_grouped_service_name(service_name, group_name); 94 | ServiceInfo::get_key(&group_service_name, cluster) 95 | } 96 | } 97 | 98 | struct ServiceInfoUpdateTask { 99 | running: Arc, 100 | service_name: String, 101 | namespace: String, 102 | group_name: String, 103 | cluster: String, 104 | cache: Arc>, 105 | nacos_grpc_client: Arc, 106 | service_info_emitter: Arc, 107 | } 108 | 109 | impl ServiceInfoUpdateTask { 110 | const DEFAULT_DELAY: u64 = 1000; 111 | const DEFAULT_UPDATE_CACHE_TIME_MULTIPLE: u8 = 6; 112 | const MAX_FAILED: u8 = 6; 113 | 114 | fn new( 115 | service_name: String, 116 | namespace: String, 117 | group_name: String, 118 | cluster: String, 119 | cache: Arc>, 120 | nacos_grpc_client: Arc, 121 | service_info_emitter: Arc, 122 | ) -> Self { 123 | Self { 124 | running: Arc::new(AtomicBool::new(false)), 125 | service_name, 126 | namespace, 127 | group_name, 128 | cluster, 129 | cache, 130 | nacos_grpc_client, 131 | service_info_emitter, 132 | } 133 | } 134 | 135 | fn start(&self) { 136 | let running = self.running.clone(); 137 | if self.running.load(Ordering::Acquire) { 138 | return; 139 | } 140 | self.running.store(true, Ordering::Release); 141 | 142 | let cluster = self.cluster.clone(); 143 | let group_name = self.group_name.clone(); 144 | let namespace = self.namespace.clone(); 145 | let service_name = self.service_name.clone(); 146 | 147 | let cache = self.cache.clone(); 148 | let grpc_client = self.nacos_grpc_client.clone(); 149 | let service_info_emitter = self.service_info_emitter.clone(); 150 | 151 | executor::spawn(async move { 152 | let mut delay_time = ServiceInfoUpdateTask::DEFAULT_DELAY; 153 | 154 | let mut last_refresh_time = u64::MAX; 155 | 156 | let mut failed_count = 0; 157 | 158 | let request = ServiceQueryRequest { 159 | cluster, 160 | group_name: Some(group_name), 161 | healthy_only: false, 162 | udp_port: 0, 163 | namespace: Some(namespace), 164 | service_name: Some(service_name), 165 | ..Default::default() 166 | }; 167 | 168 | let log_tag = format!( 169 | "{}:{}:{}:{}", 170 | request.namespace.as_deref().unwrap_or_default(), 171 | request.group_name.as_deref().unwrap_or_default(), 172 | request.service_name.as_deref().unwrap_or_default(), 173 | request.cluster 174 | ); 175 | 176 | info!("{log_tag}:ServiceInfoUpdateTask started"); 177 | 178 | while running.load(Ordering::Acquire) { 179 | let delay_time_millis = Duration::from_millis( 180 | (delay_time << failed_count).min(ServiceInfoUpdateTask::DEFAULT_DELAY * 60), 181 | ); 182 | debug!("{log_tag}:ServiceInfoUpdateTask delay sleep {delay_time_millis:?}"); 183 | sleep(delay_time_millis).await; 184 | 185 | if !running.load(Ordering::Acquire) { 186 | warn!("{log_tag}:ServiceInfoUpdateTask has been already stopped!"); 187 | break; 188 | } 189 | 190 | debug!("{log_tag}:ServiceInfoUpdateTask refreshing"); 191 | 192 | let service_info = { 193 | let group_name = request.group_name.as_deref().unwrap_or_default(); 194 | let service_name = request.service_name.as_deref().unwrap_or_default(); 195 | let cluster = &request.cluster; 196 | 197 | let grouped_name = 198 | ServiceInfo::get_grouped_service_name(service_name, group_name); 199 | let key = ServiceInfo::get_key(&grouped_name, cluster); 200 | let ret = cache.get(&key).map(|data| data.clone()); 201 | ret 202 | }; 203 | 204 | let mut need_query_service_info = true; 205 | 206 | if let Some(service_info) = service_info { 207 | let is_outdate = last_refresh_time >= (service_info.last_ref_time as u64); 208 | if is_outdate { 209 | need_query_service_info = true; 210 | } else { 211 | need_query_service_info = false; 212 | last_refresh_time = service_info.last_ref_time as u64; 213 | } 214 | } 215 | 216 | if !need_query_service_info { 217 | warn!("{log_tag}:ServiceInfoUpdateTask don't need to refresh service info"); 218 | continue; 219 | } 220 | 221 | let mut request = request.clone(); 222 | request.request_id = Some(generate_request_id()); 223 | 224 | let ret = grpc_client 225 | .send_request::(request) 226 | .in_current_span() 227 | .await; 228 | if let Err(e) = ret { 229 | error!("{log_tag}:ServiceInfoUpdateTask occur an error: {e:?}"); 230 | if failed_count < ServiceInfoUpdateTask::MAX_FAILED { 231 | failed_count += 1; 232 | } 233 | continue; 234 | } 235 | let response = ret.unwrap(); 236 | debug!("{log_tag}:ServiceInfoUpdateTask query service info response: {response:?}"); 237 | if !response.is_success() { 238 | let result_code = response.result_code; 239 | let error_code = response.error_code; 240 | let ret_message = response.message.unwrap_or_default(); 241 | error!("{log_tag}:ServiceInfoUpdateTask query services failed: resultCode: {result_code}, errorCode:{error_code}, message:{ret_message}"); 242 | if failed_count < ServiceInfoUpdateTask::MAX_FAILED { 243 | failed_count += 1; 244 | } 245 | continue; 246 | } 247 | let service_info = response.service_info; 248 | last_refresh_time = service_info.last_ref_time as u64; 249 | delay_time = (service_info.cache_millis 250 | * (ServiceInfoUpdateTask::DEFAULT_UPDATE_CACHE_TIME_MULTIPLE as i64)) 251 | as u64; 252 | 253 | service_info_emitter.emit(service_info).in_current_span().await; 254 | 255 | failed_count = 0; 256 | debug!("{log_tag}:ServiceInfoUpdateTask finish"); 257 | } 258 | 259 | warn!("{log_tag}:ServiceInfoUpdateTask is stopped"); 260 | }.in_current_span()); 261 | } 262 | 263 | fn stop(&self) { 264 | self.running.store(false, Ordering::Release); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /tests/proto_build.rs: -------------------------------------------------------------------------------- 1 | // Licensed to the Apache Software Foundation (ASF) under one or more 2 | // contributor license agreements. See the NOTICE file distributed with 3 | // this work for additional information regarding copyright ownership. 4 | // The ASF licenses this file to You under the Apache License, Version 2.0 5 | // (the "License"); you may not use this file except in compliance with 6 | // the License. You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | // This test helps to keep files generated and used by tonic_build(proto) update to date. 18 | // If the generated files has been changed, please commit they. 19 | #[test] 20 | #[ignore] 21 | fn build_proto() { 22 | tonic_build::configure() 23 | .build_client(true) 24 | .build_server(false) 25 | .build_transport(true) 26 | .out_dir("src") 27 | .compile_protos(&["./proto/nacos_grpc_service.proto"], &["./proto"]) 28 | .unwrap(); 29 | } 30 | --------------------------------------------------------------------------------