You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by li...@apache.org on 2023/09/01 08:17:40 UTC
[dubbo-rust] branch feat/cluster updated: feat: Add Router Module(#144) (#153)
This is an automated email from the ASF dual-hosted git repository.
liujun pushed a commit to branch feat/cluster
in repository https://gitbox.apache.org/repos/asf/dubbo-rust.git
The following commit(s) were added to refs/heads/feat/cluster by this push:
new c07b96d feat: Add Router Module(#144) (#153)
c07b96d is described below
commit c07b96d4ca8300699657b97d18ba788b81417d5c
Author: Urara <95...@users.noreply.github.com>
AuthorDate: Fri Sep 1 16:17:36 2023 +0800
feat: Add Router Module(#144) (#153)
add condition router,
add tag router,
use nacos as router config center
---
application.yaml | 35 +++-
config/src/config.rs | 6 +-
config/src/lib.rs | 1 +
config/src/router.rs | 67 ++++++
dubbo/Cargo.toml | 5 +
dubbo/src/cluster/mod.rs | 28 +--
.../cluster/router/condition/condition_router.rs | 60 ++++++
dubbo/src/cluster/router/condition/matcher.rs | 94 +++++++++
dubbo/src/cluster/router/condition/mod.rs | 3 +
.../src/cluster/router/condition/single_router.rs | 225 +++++++++++++++++++++
.../cluster/router/manager/condition_manager.rs | 75 +++++++
dubbo/src/cluster/router/manager/mod.rs | 3 +
dubbo/src/cluster/router/manager/router_manager.rs | 161 +++++++++++++++
dubbo/src/cluster/router/manager/tag_manager.rs | 27 +++
dubbo/src/cluster/router/mod.rs | 25 +++
.../src/cluster/router/nacos_config_center/mod.rs | 1 +
.../router/nacos_config_center/nacos_client.rs | 132 ++++++++++++
dubbo/src/cluster/router/router_chain.rs | 74 +++++++
dubbo/src/cluster/router/tag/mod.rs | 1 +
dubbo/src/cluster/router/tag/tag_router.rs | 70 +++++++
dubbo/src/cluster/router/utils.rs | 16 ++
dubbo/src/triple/client/builder.rs | 3 +-
22 files changed, 1084 insertions(+), 28 deletions(-)
diff --git a/application.yaml b/application.yaml
index d357db1..902b0ef 100644
--- a/application.yaml
+++ b/application.yaml
@@ -21,4 +21,37 @@ dubbo:
references:
GreeterClientImpl:
url: tri://localhost:20000
- protocol: tri
\ No newline at end of file
+ protocol: tri
+ routers:
+ consumer:
+ - service: "org.apache.dubbo.sample.tri.Greeter"
+ url: triple://localhost:20000
+ protocol: triple
+ nacos:
+ addr: "127.0.0.1:8848"
+ namespace: ""
+ app: ""
+ conditions:
+ - scope: "service"
+ force: false
+ runtime: true
+ enabled: true
+ key: "org.apache.dubbo.sample.tri.Greeter"
+ conditions:
+ - method=greet => port=8889
+ - scope: "service"
+ force: true
+ runtime: true
+ enabled: true
+ key: "user.UserService"
+ conditions:
+ - method=get_s => port=2003
+ tags:
+ force: true
+ enabled: true
+ key: shop-detail
+ tags:
+ - name: gray
+ matches:
+ - key: env
+ value: gray
\ No newline at end of file
diff --git a/config/src/config.rs b/config/src/config.rs
index 646873d..cf4e441 100644
--- a/config/src/config.rs
+++ b/config/src/config.rs
@@ -17,7 +17,7 @@
use std::{collections::HashMap, env, path::PathBuf};
-use crate::{protocol::Protocol, registry::RegistryConfig};
+use crate::{protocol::Protocol, registry::RegistryConfig, router::RouterConfig};
use dubbo_logger::tracing;
use dubbo_utils::yaml_util::yaml_file_parser;
use once_cell::sync::OnceCell;
@@ -44,6 +44,9 @@ pub struct RootConfig {
#[serde(default)]
pub registries: HashMap<String, RegistryConfig>,
+ #[serde(default)]
+ pub routers: RouterConfig,
+
#[serde(default)]
pub data: HashMap<String, String>,
}
@@ -63,6 +66,7 @@ impl RootConfig {
protocols: HashMap::new(),
registries: HashMap::new(),
provider: ProviderConfig::new(),
+ routers: RouterConfig::default(),
data: HashMap::new(),
}
}
diff --git a/config/src/lib.rs b/config/src/lib.rs
index 0748c66..6fa3880 100644
--- a/config/src/lib.rs
+++ b/config/src/lib.rs
@@ -21,4 +21,5 @@ pub mod config;
pub mod protocol;
pub mod provider;
pub mod registry;
+pub mod router;
pub mod service;
diff --git a/config/src/router.rs b/config/src/router.rs
new file mode 100644
index 0000000..98aa14a
--- /dev/null
+++ b/config/src/router.rs
@@ -0,0 +1,67 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
+pub struct ConditionRouterConfig {
+ pub scope: String,
+ pub force: bool,
+ pub runtime: bool,
+ pub enabled: bool,
+ pub key: String,
+ pub conditions: Vec<String>,
+}
+
+#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
+pub struct TagRouterConfig {
+ pub force: bool,
+ pub enabled: bool,
+ pub key: String,
+ pub tags: Vec<Tag>,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Default, Debug)]
+pub struct ConsumerConfig {
+ pub service: String,
+ pub url: String,
+ pub protocol: String,
+}
+
+#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
+pub struct Tag {
+ pub name: String,
+ pub matches: Vec<TagMatchRule>,
+}
+
+#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
+pub struct TagMatchRule {
+ pub key: String,
+ pub value: String,
+}
+
+impl ConditionRouterConfig {
+ pub fn new(config: &String) -> Self {
+ serde_yaml::from_str(config).expect("parse error")
+ }
+}
+
+#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
+pub struct EnableAuth {
+ pub auth_username: String,
+ pub auth_password: String,
+}
+
+#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
+pub struct NacosConfig {
+ pub addr: String,
+ pub namespace: String,
+ pub app: String,
+ pub enable_auth: Option<EnableAuth>,
+ pub enable_auth_plugin_http: Option<bool>,
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
+pub struct RouterConfig {
+ pub consumer: Option<Vec<ConsumerConfig>>,
+ pub nacos: Option<NacosConfig>,
+ pub conditions: Option<Vec<ConditionRouterConfig>>,
+ pub tags: Option<TagRouterConfig>,
+}
diff --git a/dubbo/Cargo.toml b/dubbo/Cargo.toml
index 51ccc19..736f0e8 100644
--- a/dubbo/Cargo.toml
+++ b/dubbo/Cargo.toml
@@ -40,8 +40,13 @@ urlencoding.workspace = true
lazy_static.workspace = true
dubbo-base.workspace = true
dubbo-logger.workspace = true
+once_cell.workspace = true
dubbo-config = { path = "../config", version = "0.3.0" }
#对象存储
state = { version = "0.5", features = ["tls"] }
+
+regex = "1.9.1"
+nacos-sdk = { version = "0.3.0", features = ["default"] }
+serde_yaml = "0.9.22"
\ No newline at end of file
diff --git a/dubbo/src/cluster/mod.rs b/dubbo/src/cluster/mod.rs
index afc3cc9..e6a5ffa 100644
--- a/dubbo/src/cluster/mod.rs
+++ b/dubbo/src/cluster/mod.rs
@@ -30,6 +30,7 @@ use crate::{
pub mod directory;
pub mod loadbalance;
+pub mod router;
pub trait Directory: Debug {
fn list(&self, invocation: Arc<RpcInvocation>) -> Vec<BoxInvoker>;
@@ -116,8 +117,9 @@ pub struct MockDirectory {
impl MockDirectory {
pub fn new() -> MockDirectory {
+ // let router_chain = get_global_router_manager().read().unwrap().get_router_chain(invocation);
Self {
- // router_chain: RouterChain::default(),
+ // router_chain
}
}
}
@@ -130,27 +132,3 @@ impl Directory for MockDirectory {
// self.router_chain.route(u, invo);
}
}
-
-// #[derive(Debug, Default)]
-// pub struct RouterChain {
-// router: HashMap<String, BoxRouter>,
-// invokers: Arc<Vec<BoxInvoker>>,
-// }
-
-// impl RouterChain {
-// pub fn route(&mut self, url: Url, invo: Arc<RpcInvocation>) -> Arc<Vec<BoxInvoker>> {
-// let r = self.router.get("mock").unwrap();
-// r.route(self.invokers.clone(), url, invo)
-// }
-// }
-
-// pub trait Router: Debug {
-// fn route(
-// &self,
-// invokers: Arc<Vec<BoxInvoker>>,
-// url: Url,
-// invo: Arc<RpcInvocation>,
-// ) -> Arc<Vec<BoxInvoker>>;
-// }
-
-// pub type BoxRouter = Box<dyn Router + Sync + Send>;
diff --git a/dubbo/src/cluster/router/condition/condition_router.rs b/dubbo/src/cluster/router/condition/condition_router.rs
new file mode 100644
index 0000000..f39cd1c
--- /dev/null
+++ b/dubbo/src/cluster/router/condition/condition_router.rs
@@ -0,0 +1,60 @@
+use crate::{
+ cluster::router::{condition::single_router::ConditionSingleRouter, Router},
+ codegen::RpcInvocation,
+};
+use dubbo_base::Url;
+use std::{
+ fmt::Debug,
+ sync::{Arc, RwLock},
+};
+
+#[derive(Default, Debug, Clone)]
+pub struct ConditionRouter {
+ //condition router for service scope
+ pub service_routers: Option<Arc<RwLock<ConditionSingleRouters>>>,
+ //condition router for application scope
+ pub application_routers: Option<Arc<RwLock<ConditionSingleRouters>>>,
+}
+
+impl Router for ConditionRouter {
+ fn route(&self, invokers: Vec<Url>, url: Url, invo: Arc<RpcInvocation>) -> Vec<Url> {
+ let mut invokers_result = invokers.clone();
+ if let Some(routers) = self.application_routers.clone() {
+ for router in &routers.read().unwrap().routers {
+ invokers_result = router.route(invokers_result, url.clone(), invo.clone())
+ }
+ }
+ if let Some(routers) = self.service_routers.clone() {
+ for router in &routers.read().unwrap().routers {
+ invokers_result = router.route(invokers_result, url.clone(), invo.clone())
+ }
+ }
+ invokers_result
+ }
+}
+
+impl ConditionRouter {
+ pub fn new(
+ service_routers: Option<Arc<RwLock<ConditionSingleRouters>>>,
+ application_routers: Option<Arc<RwLock<ConditionSingleRouters>>>,
+ ) -> Self {
+ Self {
+ service_routers,
+ application_routers,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct ConditionSingleRouters {
+ pub routers: Vec<ConditionSingleRouter>,
+}
+
+impl ConditionSingleRouters {
+ pub fn new(routers: Vec<ConditionSingleRouter>) -> Self {
+ Self { routers }
+ }
+ pub fn is_null(&self) -> bool {
+ self.routers.is_empty()
+ }
+}
diff --git a/dubbo/src/cluster/router/condition/matcher.rs b/dubbo/src/cluster/router/condition/matcher.rs
new file mode 100644
index 0000000..8c9177e
--- /dev/null
+++ b/dubbo/src/cluster/router/condition/matcher.rs
@@ -0,0 +1,94 @@
+use crate::codegen::RpcInvocation;
+use regex::Regex;
+use std::{collections::HashSet, error::Error, option::Option, sync::Arc};
+
+#[derive(Clone, Debug, Default)]
+pub struct ConditionMatcher {
+ _key: String,
+ matches: HashSet<String>,
+ mismatches: HashSet<String>,
+}
+
+impl ConditionMatcher {
+ pub fn new(_key: String) -> Self {
+ ConditionMatcher {
+ _key,
+ matches: HashSet::new(),
+ mismatches: HashSet::new(),
+ }
+ }
+
+ pub fn is_match(
+ &self,
+ value: Option<String>,
+ invocation: Arc<RpcInvocation>,
+ _is_when: bool,
+ ) -> Result<bool, Box<dyn Error>> {
+ match value {
+ None => {
+ // if key does not present in whichever of url, invocation or attachment based on the matcher type, then return false.
+ Ok(false)
+ }
+ Some(val) => {
+ if !self.matches.is_empty() && self.mismatches.is_empty() {
+ for match_ in self.matches.iter() {
+ if self.do_pattern_match(match_, &val, invocation.clone())? {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+ } else if !self.mismatches.is_empty() && self.matches.is_empty() {
+ for mismatch in self.mismatches.iter() {
+ if self.do_pattern_match(mismatch, &val, invocation.clone())? {
+ return Ok(false);
+ }
+ }
+ Ok(true)
+ } else if !self.matches.is_empty() && !self.mismatches.is_empty() {
+ for mismatch in self.mismatches.iter() {
+ if self.do_pattern_match(mismatch, &val, invocation.clone())? {
+ return Ok(false);
+ }
+ }
+ for match_ in self.matches.iter() {
+ if self.do_pattern_match(match_, &val, invocation.clone())? {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+ } else {
+ Ok(false)
+ }
+ }
+ }
+ }
+ pub fn get_matches(&mut self) -> &mut HashSet<String> {
+ &mut self.matches
+ }
+ pub fn get_mismatches(&mut self) -> &mut HashSet<String> {
+ &mut self.mismatches
+ }
+
+ fn do_pattern_match(
+ &self,
+ pattern: &String,
+ value: &String,
+ _invocation: Arc<RpcInvocation>,
+ ) -> Result<bool, Box<dyn Error>> {
+ if pattern.contains("*") {
+ return Ok(star_matcher(pattern, value));
+ }
+ Ok(pattern.eq(value))
+ }
+}
+
+pub fn star_matcher(pattern: &str, input: &str) -> bool {
+ // 将*替换为任意字符的正则表达式
+ let pattern = pattern.replace("*", ".*");
+ let regex = Regex::new(&pattern).unwrap();
+ regex.is_match(input)
+}
+
+pub fn _range_matcher(val: i32, min: i32, max: i32) -> bool {
+ min <= val && val <= max
+}
diff --git a/dubbo/src/cluster/router/condition/mod.rs b/dubbo/src/cluster/router/condition/mod.rs
new file mode 100644
index 0000000..7285b88
--- /dev/null
+++ b/dubbo/src/cluster/router/condition/mod.rs
@@ -0,0 +1,3 @@
+pub mod condition_router;
+pub mod matcher;
+pub mod single_router;
diff --git a/dubbo/src/cluster/router/condition/single_router.rs b/dubbo/src/cluster/router/condition/single_router.rs
new file mode 100644
index 0000000..e290b15
--- /dev/null
+++ b/dubbo/src/cluster/router/condition/single_router.rs
@@ -0,0 +1,225 @@
+use dubbo_base::Url;
+use dubbo_logger::tracing::info;
+use regex::Regex;
+use std::{
+ collections::HashMap,
+ sync::{Arc, RwLock},
+};
+
+use crate::{
+ cluster::router::{condition::matcher::ConditionMatcher, utils::to_original_map, Router},
+ codegen::RpcInvocation,
+ invocation::Invocation,
+};
+
+#[derive(Debug, Clone, Default)]
+pub struct ConditionSingleRouter {
+ pub name: String,
+ pub when_condition: HashMap<String, Arc<RwLock<ConditionMatcher>>>,
+ pub then_condition: HashMap<String, Arc<RwLock<ConditionMatcher>>>,
+ pub enabled: bool,
+ pub force: bool,
+}
+
+impl Router for ConditionSingleRouter {
+ fn route(&self, invokers: Vec<Url>, url: Url, invocation: Arc<RpcInvocation>) -> Vec<Url> {
+ if !self.enabled {
+ return invokers;
+ };
+ let mut result = Vec::new();
+ if self.match_when(url.clone(), invocation.clone()) {
+ for invoker in &invokers.clone() {
+ if self.match_then(invoker.clone(), invocation.clone()) {
+ result.push(invoker.clone());
+ }
+ }
+ if result.is_empty() && self.force == false {
+ invokers
+ } else {
+ result
+ }
+ } else {
+ invokers
+ }
+ }
+}
+
+impl ConditionSingleRouter {
+ pub fn new(rule: String, force: bool, enabled: bool) -> Self {
+ let mut router = Self {
+ name: "condition".to_string(),
+ when_condition: HashMap::new(),
+ then_condition: HashMap::new(),
+ enabled,
+ force,
+ };
+ if enabled {
+ router.init(rule).expect("parse rule error");
+ }
+ router
+ }
+
+ fn init(&mut self, rule: String) -> Result<(), Box<dyn std::error::Error>> {
+ match rule.trim().is_empty() {
+ true => Err("Illegal route rule!".into()),
+ false => {
+ let r = rule.replace("consumer.", "").replace("provider.", "");
+ let i = r.find("=>").unwrap_or_else(|| r.len());
+ let when_rule = r[..i].trim().to_string();
+ let then_rule = r[(i + 2)..].trim().to_string();
+ let when = if when_rule.is_empty() || when_rule == "true" {
+ HashMap::new()
+ } else {
+ self.parse_rule(&when_rule)?
+ };
+ let then = if then_rule.is_empty() || then_rule == "false" {
+ HashMap::new()
+ } else {
+ self.parse_rule(&then_rule)?
+ };
+ self.when_condition = when;
+ self.then_condition = then;
+ Ok(())
+ }
+ }
+ }
+
+ fn parse_rule(
+ &mut self,
+ rule: &str,
+ ) -> Result<HashMap<String, Arc<RwLock<ConditionMatcher>>>, Box<dyn std::error::Error>> {
+ let mut conditions: HashMap<String, Arc<RwLock<ConditionMatcher>>> = HashMap::new();
+ let mut current_matcher: Option<Arc<RwLock<ConditionMatcher>>> = None;
+ let regex = Regex::new(r"([&!=,]*)\s*([^&!=,\s]+)").unwrap();
+ for cap in regex.captures_iter(rule) {
+ let separator = &cap[1];
+ let content = &cap[2];
+
+ match separator {
+ "" => {
+ let current_key = content.to_string();
+ current_matcher =
+ Some(Arc::new(RwLock::new(self.get_matcher(current_key.clone()))));
+ conditions.insert(
+ current_key.clone(),
+ current_matcher.as_ref().unwrap().clone(),
+ );
+ }
+ "&" => {
+ let current_key = content.to_string();
+ current_matcher = conditions.get(¤t_key).cloned();
+ if current_matcher.is_none() {
+ let matcher = Arc::new(RwLock::new(self.get_matcher(current_key.clone())));
+ conditions.insert(current_key.clone(), matcher.clone());
+ current_matcher = Some(matcher);
+ }
+ }
+ "=" => {
+ if let Some(matcher) = ¤t_matcher {
+ let mut matcher_borrowed = matcher.write().unwrap();
+ matcher_borrowed
+ .get_matches()
+ .insert(content.to_string().clone());
+ } else {
+ return Err("Error: ...".into());
+ }
+ }
+ "!=" => {
+ if let Some(matcher) = ¤t_matcher {
+ let mut matcher_borrowed = matcher.write().unwrap();
+ matcher_borrowed
+ .get_mismatches()
+ .insert(content.to_string().clone());
+ } else {
+ return Err("Error: ...".into());
+ }
+ }
+ "," => {
+ if let Some(matcher) = ¤t_matcher {
+ let mut matcher_borrowed = matcher.write().unwrap();
+ if matcher_borrowed.get_matches().is_empty()
+ && matcher_borrowed.get_mismatches().is_empty()
+ {
+ return Err("Error: ...".into());
+ }
+ drop(matcher_borrowed);
+ let mut matcher_borrowed_mut = matcher.write().unwrap();
+ matcher_borrowed_mut
+ .get_matches()
+ .insert(content.to_string().clone());
+ } else {
+ return Err("Error: ...".into());
+ }
+ }
+ _ => {
+ return Err("Error: ...".into());
+ }
+ }
+ }
+ Ok(conditions)
+ }
+
+ // pub fn is_runtime(&self) -> bool {
+ // // same as the Java version
+ // }
+
+ pub fn get_matcher(&self, key: String) -> ConditionMatcher {
+ ConditionMatcher::new(key)
+ }
+
+ pub fn match_when(&self, url: Url, invocation: Arc<RpcInvocation>) -> bool {
+ if self.when_condition.is_empty() {
+ true
+ } else {
+ false
+ };
+ self.do_match(url, &self.when_condition, invocation, true)
+ }
+
+ pub fn match_then(&self, url: Url, invocation: Arc<RpcInvocation>) -> bool {
+ if self.when_condition.is_empty() {
+ true
+ } else {
+ false
+ };
+ self.do_match(url, &self.then_condition, invocation, false)
+ }
+
+ pub fn do_match(
+ &self,
+ url: Url,
+ conditions: &HashMap<String, Arc<RwLock<ConditionMatcher>>>,
+ invocation: Arc<RpcInvocation>,
+ is_when: bool,
+ ) -> bool {
+ let sample: HashMap<String, String> = to_original_map(url);
+ for (key, condition_matcher) in conditions {
+ let matcher = condition_matcher.read().unwrap();
+ let value = get_value(key, &sample, invocation.clone());
+ match matcher.is_match(value, invocation.clone(), is_when) {
+ Ok(result) => {
+ if !result {
+ return false;
+ }
+ }
+ Err(error) => {
+ info!("Error occurred: {:?}", error);
+ return false;
+ }
+ }
+ }
+ true
+ }
+}
+
+fn get_value(
+ key: &String,
+ sample: &HashMap<String, String>,
+ invocation: Arc<RpcInvocation>,
+) -> Option<String> {
+ if key == "method" {
+ let method_param = invocation.get_method_name();
+ return Some(method_param);
+ }
+ sample.get(key).cloned()
+}
diff --git a/dubbo/src/cluster/router/manager/condition_manager.rs b/dubbo/src/cluster/router/manager/condition_manager.rs
new file mode 100644
index 0000000..4207b79
--- /dev/null
+++ b/dubbo/src/cluster/router/manager/condition_manager.rs
@@ -0,0 +1,75 @@
+use crate::cluster::router::condition::{
+ condition_router::{ConditionRouter, ConditionSingleRouters},
+ single_router::ConditionSingleRouter,
+};
+use dubbo_config::router::ConditionRouterConfig;
+use std::{
+ collections::HashMap,
+ sync::{Arc, RwLock},
+};
+
+#[derive(Debug, Clone, Default)]
+pub struct ConditionRouterManager {
+ //Application-level routing applies globally, while service-level routing only affects a specific service.
+ pub routers_service: HashMap<String, Arc<RwLock<ConditionSingleRouters>>>,
+ pub routers_application: Arc<RwLock<ConditionSingleRouters>>,
+}
+
+impl ConditionRouterManager {
+ pub fn get_router(&self, service_name: String) -> Option<ConditionRouter> {
+ let routers_service = self.routers_service.get(&service_name);
+ match routers_service {
+ Some(routers_service) => {
+ if self.routers_application.read().unwrap().is_null() {
+ return Some(ConditionRouter::new(Some(routers_service.clone()), None));
+ }
+ Some(ConditionRouter::new(
+ Some(routers_service.clone()),
+ Some(self.routers_application.clone()),
+ ))
+ }
+ None => {
+ if self.routers_application.read().unwrap().is_null() {
+ return None;
+ }
+ Some(ConditionRouter::new(
+ None,
+ Some(self.routers_application.clone()),
+ ))
+ }
+ }
+ }
+
+ pub fn update(&mut self, config: ConditionRouterConfig) {
+ let force = config.force;
+ let scope = config.scope;
+ let key = config.key;
+ let enable = config.enabled;
+ let mut routers = Vec::new();
+ for condition in config.conditions {
+ routers.push(ConditionSingleRouter::new(condition, force, enable));
+ }
+ match scope.as_str() {
+ "application" => {
+ self.routers_application.write().unwrap().routers = routers;
+ }
+ "service" => {
+ if let Some(x) = self.routers_service.get(&key) {
+ x.write().unwrap().routers = routers
+ } else {
+ self.routers_service.insert(
+ key,
+ Arc::new(RwLock::new(ConditionSingleRouters::new(routers))),
+ );
+ }
+ }
+ _ => {}
+ }
+ }
+
+ pub fn _parse_rules(&mut self, configs: Vec<ConditionRouterConfig>) {
+ for config in configs {
+ self.update(config)
+ }
+ }
+}
diff --git a/dubbo/src/cluster/router/manager/mod.rs b/dubbo/src/cluster/router/manager/mod.rs
new file mode 100644
index 0000000..025f6c1
--- /dev/null
+++ b/dubbo/src/cluster/router/manager/mod.rs
@@ -0,0 +1,3 @@
+mod condition_manager;
+pub mod router_manager;
+mod tag_manager;
diff --git a/dubbo/src/cluster/router/manager/router_manager.rs b/dubbo/src/cluster/router/manager/router_manager.rs
new file mode 100644
index 0000000..a2a6658
--- /dev/null
+++ b/dubbo/src/cluster/router/manager/router_manager.rs
@@ -0,0 +1,161 @@
+use crate::{
+ cluster::router::{
+ manager::{condition_manager::ConditionRouterManager, tag_manager::TagRouterManager},
+ nacos_config_center::nacos_client::NacosClient,
+ router_chain::RouterChain,
+ },
+ invocation::{Invocation, RpcInvocation},
+};
+use dubbo_base::Url;
+use dubbo_config::{
+ get_global_config,
+ router::{ConditionRouterConfig, NacosConfig, TagRouterConfig},
+};
+use dubbo_logger::tracing::{info, trace};
+use once_cell::sync::OnceCell;
+use std::{
+ collections::HashMap,
+ sync::{Arc, RwLock},
+};
+
+pub static GLOBAL_ROUTER_MANAGER: OnceCell<Arc<RwLock<RouterManager>>> = OnceCell::new();
+
+pub struct RouterManager {
+ pub condition_router_manager: ConditionRouterManager,
+ pub tag_router_manager: TagRouterManager,
+ pub nacos: Option<NacosClient>,
+ pub consumer: HashMap<String, Url>,
+}
+
+impl RouterManager {
+ pub fn get_router_chain(&self, invocation: Arc<RpcInvocation>) -> RouterChain {
+ let service = invocation.get_target_service_unique_name().clone();
+ let condition_router = self.condition_router_manager.get_router(service.clone());
+ let tag_router = self.tag_router_manager.get_router();
+ let mut chain = RouterChain::new();
+ match self.consumer.get(service.as_str()) {
+ None => {}
+ Some(url) => {
+ chain.set_condition_router(condition_router);
+ chain.set_tag_router(tag_router);
+ chain.self_url = url.clone();
+ }
+ }
+ chain
+ }
+
+ pub fn notify(&mut self, event: RouterConfigChangeEvent) {
+ match event.router_kind.as_str() {
+ "condition" => {
+ let config: ConditionRouterConfig =
+ serde_yaml::from_str(event.content.as_str()).unwrap();
+ self.condition_router_manager.update(config)
+ }
+ "tag" => {
+ let config: TagRouterConfig = serde_yaml::from_str(event.content.as_str()).unwrap();
+ self.tag_router_manager.update(config)
+ }
+ _ => {
+ info!("other router change event")
+ }
+ }
+ }
+
+ pub fn init_nacos(&mut self, config: NacosConfig) {
+ self.nacos = Some(NacosClient::new_init_client(config));
+ self.init_router_managers_for_nacos();
+ }
+
+ pub fn init_router_managers_for_nacos(&mut self) {
+ let config = self
+ .nacos
+ .as_ref()
+ .unwrap()
+ .get_tag_config("application".to_string());
+ match config {
+ None => {}
+ Some(tag_config) => {
+ self.tag_router_manager.init();
+ self.tag_router_manager.update(tag_config)
+ }
+ }
+ for (service_name, _) in &self.consumer {
+ let config = self
+ .nacos
+ .as_ref()
+ .unwrap()
+ .get_condition_config(service_name.clone());
+ match config {
+ None => {}
+ Some(condition_config) => self.condition_router_manager.update(condition_config),
+ }
+ }
+ }
+
+ pub fn init(&mut self) {
+ let config = get_global_config().routers.clone();
+ let consumer_configs = get_global_config()
+ .routers
+ .consumer
+ .clone()
+ .unwrap_or(Vec::new());
+ for consumer_config in consumer_configs {
+ self.consumer.insert(
+ consumer_config.service.clone(),
+ Url::from_url(
+ format!("{}/{}", consumer_config.url, consumer_config.service).as_str(),
+ )
+ .expect("consumer配置出错!Url生成错误"),
+ );
+ }
+ match &config.nacos {
+ None => {
+ trace!("Nacos not configured, using local YAML configuration for routing");
+ let condition = config.conditions.clone();
+ match condition {
+ None => {
+ info!("Unconfigured Condition Router")
+ }
+ Some(cons) => {
+ for con in cons {
+ self.condition_router_manager.update(con)
+ }
+ }
+ }
+ let tag = config.tags.clone();
+ match tag {
+ None => {
+ info!("Unconfigured Tag Router")
+ }
+ Some(ta) => {
+ self.tag_router_manager.init();
+ self.tag_router_manager.update(ta)
+ }
+ }
+ }
+ Some(config) => {
+ self.init_nacos(config.clone());
+ }
+ }
+ }
+}
+
+pub fn get_global_router_manager() -> &'static Arc<RwLock<RouterManager>> {
+ GLOBAL_ROUTER_MANAGER.get_or_init(|| {
+ let mut router_manager = RouterManager {
+ condition_router_manager: ConditionRouterManager::default(),
+ tag_router_manager: TagRouterManager::default(),
+ nacos: None,
+ consumer: HashMap::new(),
+ };
+ router_manager.init();
+ return Arc::new(RwLock::new(router_manager));
+ })
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct RouterConfigChangeEvent {
+ pub service_name: String,
+ pub router_kind: String,
+ pub content: String,
+}
diff --git a/dubbo/src/cluster/router/manager/tag_manager.rs b/dubbo/src/cluster/router/manager/tag_manager.rs
new file mode 100644
index 0000000..ce30c92
--- /dev/null
+++ b/dubbo/src/cluster/router/manager/tag_manager.rs
@@ -0,0 +1,27 @@
+use crate::cluster::router::tag::tag_router::TagRouter;
+use dubbo_config::router::TagRouterConfig;
+use std::sync::{Arc, RwLock};
+
+#[derive(Debug, Clone, Default)]
+pub struct TagRouterManager {
+ pub tag_router: Option<Arc<RwLock<TagRouter>>>,
+}
+
+impl TagRouterManager {
+ pub fn init(&mut self) {
+ self.tag_router = Some(Arc::new(RwLock::new(TagRouter::default())))
+ }
+
+ pub fn get_router(&self) -> Option<Arc<RwLock<TagRouter>>> {
+ self.tag_router.clone()
+ }
+
+ pub fn update(&mut self, config: TagRouterConfig) {
+ self.tag_router
+ .as_ref()
+ .unwrap()
+ .write()
+ .unwrap()
+ .parse_config(config)
+ }
+}
diff --git a/dubbo/src/cluster/router/mod.rs b/dubbo/src/cluster/router/mod.rs
new file mode 100644
index 0000000..84ceae1
--- /dev/null
+++ b/dubbo/src/cluster/router/mod.rs
@@ -0,0 +1,25 @@
+pub mod condition;
+pub mod manager;
+pub mod nacos_config_center;
+pub mod router_chain;
+pub mod tag;
+pub mod utils;
+
+use crate::invocation::RpcInvocation;
+use dubbo_base::Url;
+use std::{fmt::Debug, sync::Arc};
+
+pub trait Router: Debug + Clone {
+ fn route(&self, invokers: Vec<Url>, url: Url, invo: Arc<RpcInvocation>) -> Vec<Url>;
+}
+
+// pub type BoxRouter = Box<dyn Router + Sync + Send>;
+
+#[derive(Debug, Default, Clone)]
+pub struct MockRouter {}
+
+impl Router for MockRouter {
+ fn route(&self, invokers: Vec<Url>, _url: Url, _invo: Arc<RpcInvocation>) -> Vec<Url> {
+ invokers
+ }
+}
diff --git a/dubbo/src/cluster/router/nacos_config_center/mod.rs b/dubbo/src/cluster/router/nacos_config_center/mod.rs
new file mode 100644
index 0000000..7878fa9
--- /dev/null
+++ b/dubbo/src/cluster/router/nacos_config_center/mod.rs
@@ -0,0 +1 @@
+pub mod nacos_client;
diff --git a/dubbo/src/cluster/router/nacos_config_center/nacos_client.rs b/dubbo/src/cluster/router/nacos_config_center/nacos_client.rs
new file mode 100644
index 0000000..1600e9b
--- /dev/null
+++ b/dubbo/src/cluster/router/nacos_config_center/nacos_client.rs
@@ -0,0 +1,132 @@
+use crate::cluster::router::manager::router_manager::{
+ get_global_router_manager, RouterConfigChangeEvent,
+};
+use dubbo_config::router::{ConditionRouterConfig, NacosConfig, TagRouterConfig};
+use dubbo_logger::{tracing, tracing::info};
+use nacos_sdk::api::{
+ config::{ConfigChangeListener, ConfigResponse, ConfigService, ConfigServiceBuilder},
+ props::ClientProps,
+};
+use std::sync::{Arc, RwLock};
+
+pub struct NacosClient {
+ pub client: Arc<RwLock<dyn ConfigService>>,
+}
+
+unsafe impl Send for NacosClient {}
+
+unsafe impl Sync for NacosClient {}
+
+pub struct ConfigChangeListenerImpl;
+
+impl NacosClient {
+ pub fn new_init_client(config: NacosConfig) -> Self {
+ let server_addr = config.addr;
+ let namespace = config.namespace;
+ let app = config.app;
+ match config.enable_auth {
+ None => {
+ info!("disable nacos auth!");
+ info!("nacos init,addr:{}", server_addr);
+ let client = Arc::new(RwLock::new(
+ ConfigServiceBuilder::new(
+ ClientProps::new()
+ .server_addr(server_addr)
+ .namespace(namespace)
+ .app_name(app),
+ )
+ .build()
+ .expect("NacosClient build failed!Please check NacosConfig"),
+ ));
+ Self { client }
+ }
+ Some(auth) => {
+ info!("enable nacos auth!");
+ info!("nacos init,addr:{}", server_addr);
+ let client = Arc::new(RwLock::new(
+ ConfigServiceBuilder::new(
+ ClientProps::new()
+ .server_addr(server_addr)
+ .namespace(namespace)
+ .app_name(app)
+ .auth_username(auth.auth_username)
+ .auth_password(auth.auth_password),
+ )
+ // .enable_auth_plugin_http()
+ .build()
+ .expect("NacosClient build failed!Please check NacosConfig"),
+ ));
+ return Self { client };
+ }
+ }
+ }
+ pub fn get_condition_config(&self, data_id: String) -> Option<ConditionRouterConfig> {
+ let config_resp = self
+ .client
+ .read()
+ .unwrap()
+ .get_config(data_id.clone(), "condition".to_string());
+ return match config_resp {
+ Ok(config_resp) => {
+ self.add_listener(data_id.clone(), "condition".to_string());
+ let string = config_resp.content();
+ Some(serde_yaml::from_str(string).unwrap())
+ }
+ Err(_err) => None,
+ };
+ }
+
+ pub fn get_tag_config(&self, data_id: String) -> Option<TagRouterConfig> {
+ let config_resp = self
+ .client
+ .read()
+ .unwrap()
+ .get_config(data_id.clone(), "tag".to_string());
+ return match config_resp {
+ Ok(config_resp) => {
+ self.add_listener(data_id.clone(), "tag".to_string());
+ let string = config_resp.content();
+ let result = serde_yaml::from_str(string);
+ match result {
+ Ok(config) => {
+ info!("success to get TagRouter config and parse success");
+ Some(config)
+ }
+ _ => {
+ info!("failed to parse TagRouter rule");
+ None
+ }
+ }
+ }
+ Err(_err) => None,
+ };
+ }
+ pub fn add_listener(&self, data_id: String, group: String) {
+ let res_listener = self
+ .client
+ .write()
+ .expect("failed to create nacos config listener")
+ .add_listener(data_id, group, Arc::new(ConfigChangeListenerImpl {}));
+ match res_listener {
+ Ok(_) => {
+ info!("listening the config success");
+ }
+ Err(err) => tracing::error!("listen config error {:?}", err),
+ }
+ }
+}
+
+impl ConfigChangeListener for ConfigChangeListenerImpl {
+ fn notify(&self, config_resp: ConfigResponse) {
+ let content_type = config_resp.content_type();
+ let event = RouterConfigChangeEvent {
+ service_name: config_resp.data_id().clone(),
+ router_kind: config_resp.group().clone(),
+ content: config_resp.content().clone(),
+ };
+ if content_type == "yaml" {
+ get_global_router_manager().write().unwrap().notify(event);
+ }
+ info!("notify config={:?}", config_resp);
+ }
+}
diff --git a/dubbo/src/cluster/router/router_chain.rs b/dubbo/src/cluster/router/router_chain.rs
new file mode 100644
index 0000000..930f0f2
--- /dev/null
+++ b/dubbo/src/cluster/router/router_chain.rs
@@ -0,0 +1,74 @@
+use crate::{
+ cluster::router::{
+ condition::condition_router::ConditionRouter, tag::tag_router::TagRouter, Router,
+ },
+ invocation::RpcInvocation,
+};
+use dubbo_base::Url;
+use std::sync::{Arc, RwLock};
+
+#[derive(Debug, Default, Clone)]
+pub struct RouterChain {
+ pub condition_router: Option<ConditionRouter>,
+ pub tag_router: Option<Arc<RwLock<TagRouter>>>,
+ pub self_url: Url,
+}
+
+impl RouterChain {
+ pub fn new() -> Self {
+ RouterChain {
+ condition_router: None,
+ tag_router: None,
+ self_url: Url::new(),
+ }
+ }
+ pub fn set_condition_router(&mut self, router: Option<ConditionRouter>) {
+ self.condition_router = router;
+ }
+ pub fn set_tag_router(&mut self, router: Option<Arc<RwLock<TagRouter>>>) {
+ self.tag_router = router;
+ }
+ pub fn route(&self, invokers: Vec<Url>, invocation: Arc<RpcInvocation>) -> Vec<Url> {
+ let mut result = invokers.clone();
+ match &self.tag_router {
+ None => {}
+ Some(router) => {
+ result =
+ router
+ .read()
+ .unwrap()
+ .route(result, self.self_url.clone(), invocation.clone())
+ }
+ }
+ match &self.condition_router {
+ Some(router) => {
+ result = router.route(result, self.self_url.clone(), invocation.clone())
+ }
+ None => {}
+ }
+ result
+ }
+}
+
+#[test]
+fn test() {
+ use crate::cluster::router::manager::router_manager::get_global_router_manager;
+
+ let u1 = Url::from_url("triple://127.0.0.1:8888/org.apache.dubbo.sample.tri.Greeter").unwrap();
+ let u2 = Url::from_url("triple://127.0.0.1:8889/org.apache.dubbo.sample.tri.Greeter").unwrap();
+ let u3 = Url::from_url("triple://127.0.0.1:8800/org.apache.dubbo.sample.tri.Greeter").unwrap();
+ let u4 = Url::from_url("triple://127.0.2.1:880/org.apache.dubbo.sample.tri.Greeter").unwrap();
+ let u5 = Url::from_url("triple://127.0.1.1:8888/org.apache.dubbo.sample.tri.Greeter").unwrap();
+ let invos = vec![u1, u2, u3, u4, u5];
+ let invo = Arc::new(
+ RpcInvocation::default()
+ .with_method_name("greet".to_string())
+ .with_service_unique_name("org.apache.dubbo.sample.tri.Greeter".to_string()),
+ );
+ let x = get_global_router_manager()
+ .read()
+ .unwrap()
+ .get_router_chain(invo.clone());
+ let result = x.route(invos, invo.clone());
+ dbg!(result);
+}
diff --git a/dubbo/src/cluster/router/tag/mod.rs b/dubbo/src/cluster/router/tag/mod.rs
new file mode 100644
index 0000000..6ac5b21
--- /dev/null
+++ b/dubbo/src/cluster/router/tag/mod.rs
@@ -0,0 +1 @@
+pub mod tag_router;
diff --git a/dubbo/src/cluster/router/tag/tag_router.rs b/dubbo/src/cluster/router/tag/tag_router.rs
new file mode 100644
index 0000000..d1a962e
--- /dev/null
+++ b/dubbo/src/cluster/router/tag/tag_router.rs
@@ -0,0 +1,70 @@
+use crate::{
+ cluster::router::{utils::to_original_map, Router},
+ codegen::RpcInvocation,
+};
+use dubbo_base::Url;
+use dubbo_config::router::TagRouterConfig;
+use std::{collections::HashMap, fmt::Debug, sync::Arc};
+
+#[derive(Debug, Clone, Default)]
+pub struct TagRouter {
+ pub tag_rules: HashMap<String, HashMap<String, String>>,
+ pub force: bool,
+ pub enabled: bool,
+}
+
+impl TagRouter {
+ pub fn parse_config(&mut self, config: TagRouterConfig) {
+ self.tag_rules = HashMap::new();
+ self.force = config.force;
+ self.enabled = config.enabled;
+ for tag in &config.tags {
+ let mut tags = HashMap::new();
+ for rule in &tag.matches {
+ tags.insert(rule.key.clone(), rule.value.clone());
+ }
+ self.tag_rules.insert(tag.name.clone(), tags);
+ }
+ }
+
+ pub fn match_tag(&self, params: HashMap<String, String>) -> Option<String> {
+ let mut tag_result = None;
+ for (tag, tag_rules) in &self.tag_rules {
+ for (key, value) in tag_rules {
+ match params.get(key.as_str()) {
+ None => {}
+ Some(val) => {
+ if val == value {
+ tag_result = Some(tag.clone())
+ }
+ }
+ }
+ }
+ }
+ tag_result
+ }
+}
+
+impl Router for TagRouter {
+ fn route(&self, invokers: Vec<Url>, url: Url, _invocation: Arc<RpcInvocation>) -> Vec<Url> {
+ if !self.enabled {
+ return invokers;
+ };
+ let self_param = to_original_map(url);
+ let invocation_tag = self.match_tag(self_param);
+ let mut invokers_result = Vec::new();
+ for invoker in &invokers {
+ let invoker_param = to_original_map(invoker.clone());
+ let invoker_tag = self.match_tag(invoker_param);
+ if invoker_tag == invocation_tag {
+ invokers_result.push(invoker.clone())
+ }
+ }
+ if invokers_result.is_empty() {
+ if !self.force {
+ return invokers;
+ }
+ }
+ invokers_result
+ }
+}
diff --git a/dubbo/src/cluster/router/utils.rs b/dubbo/src/cluster/router/utils.rs
new file mode 100644
index 0000000..2ca50fc
--- /dev/null
+++ b/dubbo/src/cluster/router/utils.rs
@@ -0,0 +1,16 @@
+use dubbo_base::Url;
+use std::{collections::HashMap, string::String};
+
+pub fn to_original_map(url: Url) -> HashMap<String, String> {
+ let mut result: HashMap<String, String> = HashMap::new();
+ result.insert("scheme".parse().unwrap(), url.scheme);
+ result.insert("location".parse().unwrap(), url.location);
+ result.insert("ip".parse().unwrap(), url.ip);
+ result.insert("port".parse().unwrap(), url.port);
+ result.insert("service_name".parse().unwrap(), url.service_name);
+ result.insert("service_key".parse().unwrap(), url.service_key);
+ for (key, value) in url.params {
+ result.insert(key, value);
+ }
+ result
+}
diff --git a/dubbo/src/triple/client/builder.rs b/dubbo/src/triple/client/builder.rs
index d68067a..b8118e2 100644
--- a/dubbo/src/triple/client/builder.rs
+++ b/dubbo/src/triple/client/builder.rs
@@ -30,6 +30,7 @@ use dubbo_base::Url;
pub type ClientBoxService =
BoxCloneService<http::Request<SdkBody>, http::Response<crate::BoxBody>, crate::Error>;
+#[allow(dead_code)]
#[derive(Clone, Debug, Default)]
pub struct ClientBuilder {
pub timeout: Option<u64>,
@@ -56,7 +57,7 @@ impl ClientBuilder {
connector: "",
directory: Some(Arc::new(Box::new(StaticDirectory::new(&host)))),
direct: true,
- host: host.clone().to_string(),
+ host: host.to_string(),
}
}